diff --git a/.github/workflows/BuildJobs.yml b/.github/workflows/BuildJobs.yml index 15634fbb81..48109238a7 100644 --- a/.github/workflows/BuildJobs.yml +++ b/.github/workflows/BuildJobs.yml @@ -98,7 +98,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - target: [build, release] + target: [release] steps: - name: Checkout diff --git a/automation/run-e2e/lib/dev.mjs b/automation/run-e2e/lib/dev.mjs index bf6bbcf327..99a7784172 100644 --- a/automation/run-e2e/lib/dev.mjs +++ b/automation/run-e2e/lib/dev.mjs @@ -14,10 +14,12 @@ export async function dev() { const parseArgsOptions = { string: ["browser"], - boolean: ["with-preps"], + boolean: ["with-preps", "update-project", "setup-project"], default: { browser: "chromium", - "with-preps": false + "with-preps": false, + "update-project": true, + "setup-project": false }, configuration: { // https://github.com/yargs/yargs-parser#boolean-negation @@ -33,9 +35,11 @@ export async function dev() { process.env.PATH += `${delimiter}${packageBinariesPath}`; const options = parseArgs(process.argv.slice(2), parseArgsOptions); - if (options.withPreps) { + if (options.withPreps || options.setupProject) { // Download test project from github await setupTestProject(); + } + if (options.withPreps || options.updateProject) { // Run update project hook await updateTestProject(); @@ -51,12 +55,10 @@ export async function dev() { await enquirer.prompt({ type: "confirm", - name: "__ingore__", + name: "__ignore__", result: () => "continue", message: "Press Enter to continue" }); - } else { - console.log(c.yellow("Skip preparations")); } const url = process.env.URL ?? "http://127.0.0.1:8080"; diff --git a/packages/modules/data-widgets/CHANGELOG.md b/packages/modules/data-widgets/CHANGELOG.md index ae43ffee6a..68a787ef49 100644 --- a/packages/modules/data-widgets/CHANGELOG.md +++ b/packages/modules/data-widgets/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Breaking changes + +- We removing grid wide filtering in favor of new, in widget, configuration for more flexible filtering. + ## [2.32.1] DataWidgets - 2025-05-28 ### [2.30.6] Datagrid diff --git a/packages/modules/data-widgets/package.json b/packages/modules/data-widgets/package.json index 8958513ccd..6bf8716182 100644 --- a/packages/modules/data-widgets/package.json +++ b/packages/modules/data-widgets/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/data-widgets", "moduleName": "Data Widgets", - "version": "2.32.1", + "version": "3.0.0", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", "private": true, diff --git a/packages/pluggableWidgets/accessibility-helper-web/package.json b/packages/pluggableWidgets/accessibility-helper-web/package.json index 6de7a467d5..6d5e27bbde 100644 --- a/packages/pluggableWidgets/accessibility-helper-web/package.json +++ b/packages/pluggableWidgets/accessibility-helper-web/package.json @@ -40,6 +40,9 @@ "update-changelog": "rui-update-changelog-widget", "verify": "rui-verify-package-format" }, + "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*" + }, "devDependencies": { "@mendix/automation-utils": "workspace:*", "@mendix/eslint-config-web-widgets": "workspace:*", diff --git a/packages/pluggableWidgets/area-chart-web/package.json b/packages/pluggableWidgets/area-chart-web/package.json index 12fba4e761..bde6f35018 100644 --- a/packages/pluggableWidgets/area-chart-web/package.json +++ b/packages/pluggableWidgets/area-chart-web/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@mendix/shared-charts": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2", "plotly.js-dist-min": "^3.0.0" }, diff --git a/packages/pluggableWidgets/badge-button-web/package.json b/packages/pluggableWidgets/badge-button-web/package.json index f173a39710..4751c972c7 100644 --- a/packages/pluggableWidgets/badge-button-web/package.json +++ b/packages/pluggableWidgets/badge-button-web/package.json @@ -42,6 +42,7 @@ "verify": "rui-verify-package-format" }, "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2" }, "devDependencies": { diff --git a/packages/pluggableWidgets/badge-web/package.json b/packages/pluggableWidgets/badge-web/package.json index 3132be6eae..c7b7fd1cc6 100644 --- a/packages/pluggableWidgets/badge-web/package.json +++ b/packages/pluggableWidgets/badge-web/package.json @@ -42,6 +42,7 @@ "verify": "rui-verify-package-format" }, "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2" }, "devDependencies": { diff --git a/packages/pluggableWidgets/bar-chart-web/package.json b/packages/pluggableWidgets/bar-chart-web/package.json index 8322efe083..7eebe2a857 100644 --- a/packages/pluggableWidgets/bar-chart-web/package.json +++ b/packages/pluggableWidgets/bar-chart-web/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@mendix/shared-charts": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2", "plotly.js-dist-min": "^3.0.0" }, diff --git a/packages/pluggableWidgets/bubble-chart-web/package.json b/packages/pluggableWidgets/bubble-chart-web/package.json index ff8c1c0d1e..4c02385671 100644 --- a/packages/pluggableWidgets/bubble-chart-web/package.json +++ b/packages/pluggableWidgets/bubble-chart-web/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@mendix/shared-charts": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2", "plotly.js-dist-min": "^3.0.0" }, diff --git a/packages/pluggableWidgets/carousel-web/package.json b/packages/pluggableWidgets/carousel-web/package.json index ebd02c4a1d..0f1e38f3c8 100644 --- a/packages/pluggableWidgets/carousel-web/package.json +++ b/packages/pluggableWidgets/carousel-web/package.json @@ -42,6 +42,7 @@ "verify": "rui-verify-package-format" }, "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*", "@types/react-test-renderer": "^18.0.7", "classnames": "^2.3.2", "react-test-renderer": "^18.2.0", diff --git a/packages/pluggableWidgets/charts-web/package.json b/packages/pluggableWidgets/charts-web/package.json index 023649b350..a94db9d421 100644 --- a/packages/pluggableWidgets/charts-web/package.json +++ b/packages/pluggableWidgets/charts-web/package.json @@ -58,7 +58,9 @@ "@mendix/heatmap-chart-web": "workspace:*", "@mendix/line-chart-web": "workspace:*", "@mendix/pie-doughnut-chart-web": "workspace:*", - "@mendix/time-series-chart-web": "workspace:*" + "@mendix/time-series-chart-web": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*" }, "devDependencies": { "@mendix/automation-utils": "workspace:*", diff --git a/packages/pluggableWidgets/column-chart-web/package.json b/packages/pluggableWidgets/column-chart-web/package.json index 2f721c318e..51134f2d13 100644 --- a/packages/pluggableWidgets/column-chart-web/package.json +++ b/packages/pluggableWidgets/column-chart-web/package.json @@ -41,6 +41,7 @@ }, "dependencies": { "@mendix/shared-charts": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", "classnames": "^2.3.2", "plotly.js-dist-min": "^3.0.0" }, diff --git a/packages/pluggableWidgets/custom-chart-web/package.json b/packages/pluggableWidgets/custom-chart-web/package.json index 0477f3e763..52e0a21ec3 100644 --- a/packages/pluggableWidgets/custom-chart-web/package.json +++ b/packages/pluggableWidgets/custom-chart-web/package.json @@ -45,7 +45,9 @@ }, "dependencies": { "@mendix/shared-charts": "workspace:*", + "@mendix/widget-plugin-component-kit": "workspace:*", "@mendix/widget-plugin-mobx-kit": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*", "classnames": "^2.3.2", "plotly.js-dist-min": "^3.0.0" }, diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/package.json b/packages/pluggableWidgets/datagrid-date-filter-web/package.json index e6f5efcfef..34a8e0f801 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-date-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-date-filter-web", "widgetName": "DatagridDateFilter", - "version": "2.11.3", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "datagrid-date-filter-web/main" + "branchName": "datagrid-date-filter-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts index 134c00a117..27bcfee192 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts @@ -1,3 +1,4 @@ +import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { betweenIcon, betweenIconDark, @@ -23,22 +24,23 @@ import { import { ContainerProps, ImageProps, + structurePreviewPalette, StructurePreviewProps, - text, - structurePreviewPalette + text } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; -import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { DatagridDateFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridDateFilterProps"; -export function getProperties( - values: DatagridDateFilterPreviewProps, - defaultProperties: Properties, - platform: "web" | "desktop" -): Properties { +export function getProperties(values: DatagridDateFilterPreviewProps, defaultProperties: Properties): Properties { if (!values.adjustable) { hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption"); } + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, values, "attributes"); + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); + } + if (values.defaultFilter !== "between") { hidePropertiesIn(defaultProperties, values, [ "defaultStartDate", @@ -49,13 +51,7 @@ export function getProperties( } else { hidePropertiesIn(defaultProperties, values, ["defaultValue", "valueAttribute"]); } - if (platform === "web") { - if (!values.advanced) { - hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]); - } - } else { - hidePropertyIn(defaultProperties, values, "advanced"); - } + return defaultProperties; } diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx index d3e977451d..a4e74e28eb 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx @@ -1,13 +1,22 @@ +import { withFilterAPI } from "@mendix/widget-plugin-filtering/helpers/withFilterAPI"; import { withPreloader } from "@mendix/widget-plugin-platform/hoc/withPreloader"; import { createElement, ReactElement } from "react"; import { DatagridDateFilterContainerProps } from "../typings/DatagridDateFilterProps"; -import { Container } from "./components/DateFilterContainer"; -import { withDateFilterAPI } from "./hocs/withDateFilterAPI"; +import { DateFilterContainer } from "./components/DateFilterContainer"; +import { withLinkedDateStore } from "./hocs/withLinkedDateStore"; +import { withParentProvidedDateStore } from "./hocs/withParentProvidedDateStore"; import { isLoadingDefaultValues } from "./utils/widget-utils"; -const container = withPreloader(Container, isLoadingDefaultValues); -const Widget = withDateFilterAPI(container); +const Container = withPreloader(DateFilterContainer, isLoadingDefaultValues); + +const FilterAuto = withParentProvidedDateStore(Container); + +const FilterLinked = withFilterAPI(withLinkedDateStore(Container)); export default function DatagridDateFilter(props: DatagridDateFilterContainerProps): ReactElement | null { - return ; + if (props.attrChoice === "auto") { + return ; + } + + return ; } diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml index 39ed04bf46..bc9800a53f 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml @@ -8,9 +8,32 @@ - - Enable advanced options + + Filter attributes + + Auto + Custom + + + + Datasource to Filter + + + + Attributes + Select the attributes that the end-user may use for filtering. + + + + Attribute + + + + + + + Default value diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/DateFilterContainer.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/DateFilterContainer.tsx index 8ca21e03b2..ae0e2fac2f 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/DateFilterContainer.tsx +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/DateFilterContainer.tsx @@ -12,7 +12,7 @@ interface ContainerProps extends DatagridDateFilterContainerProps { parentChannelName?: string; } -export const Container: (props: ContainerProps) => React.ReactElement = observer(function Container(props) { +export const DateFilterContainer: (props: ContainerProps) => React.ReactElement = observer(function Container(props) { const staticProps = useSetup({ defaultEndValue: props.defaultEndDate?.value, defaultFilter: props.defaultFilter, diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx index 6cfbed5d16..e50e1ec773 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx @@ -1,25 +1,19 @@ -import "@testing-library/jest-dom"; -import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context"; -import { - HeaderFiltersStore, - HeaderFiltersStoreProps -} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { DateInputFilterStore } from "@mendix/widget-plugin-filtering/stores/input/DateInputFilterStore"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; import { actionValue, dynamicValue, EditableValueBuilder, ListAttributeValueBuilder } from "@mendix/widget-plugin-test-utils"; -import { render, fireEvent, screen, act } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { act, fireEvent, render, screen } from "@testing-library/react"; +import { AttributeMetaData } from "mendix"; import { createContext, createElement } from "react"; -import DatagridDateFilter from "../../DatagridDateFilter"; import { DatagridDateFilterContainerProps } from "../../../typings/DatagridDateFilterProps"; import { MXGlobalObject, MXSessionConfig } from "../../../typings/global"; - -interface StaticInfo { - name: string; - filtersChannelName: string; -} +import DatagridDateFilter from "../../DatagridDateFilter"; function createMXObjectMock( code: string, @@ -51,12 +45,8 @@ const commonProps: DatagridDateFilterContainerProps = { name: "filter-test", defaultFilter: "equal" as const, adjustable: true, - advanced: false -}; - -const headerFilterStoreInfo: StaticInfo = { - name: commonProps.name, - filtersChannelName: "" + attrChoice: "auto", + attributes: [] }; const mxObject = createMXObjectMock("en_US", "en-US"); @@ -69,14 +59,26 @@ describe("Date Filter", () => { describe("with single attribute", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() } - ] + const dateTimeAttribute = new ListAttributeValueBuilder() + .withType("DateTime") + .withFilterable(true) + .build() as unknown as AttributeMetaData; + + const dateFilterStore = new DateInputFilterStore([dateTimeAttribute], null); + + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: "datagrid/1", + provider: { + hasError: false, + value: { type: "direct", store: dateFilterStore } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context + + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( + filterAPI ); (window as any).mx = mxObject; @@ -144,27 +146,34 @@ describe("Date Filter", () => { describe("with double attributes", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attr1") - .withType("DateTime") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attr2") - .withType("DateTime") - .withFilterable(true) - .build() - } - ] + const dateTimeAttributes = [ + new ListAttributeValueBuilder() + .withId("attr1") + .withType("DateTime") + .withFilterable(true) + .build() as unknown as AttributeMetaData, + new ListAttributeValueBuilder() + .withId("attr2") + .withType("DateTime") + .withFilterable(true) + .build() as unknown as AttributeMetaData + ]; + + const dateFilterStore = new DateInputFilterStore(dateTimeAttributes, null); + + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: "datagrid/1", + provider: { + hasError: false, + value: { type: "direct", store: dateFilterStore } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context + + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( + filterAPI ); (window as any).mx = mxObject; @@ -182,73 +191,6 @@ describe("Date Filter", () => { }); }); - describe("with wrong attribute's type", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("Decimal").withFilterable(true).build() } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - (window as any).mx = mxObject; - }); - - it("renders error message", () => { - const { container } = render(); - - expect(container.querySelector(".alert")?.textContent).toEqual( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong multiple attributes' types", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attr1") - .withType("String") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attr2") - .withType("Decimal") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - (window as any).mx = mxObject; - }); - - it("renders error message", () => { - const { container } = render(); - - expect(container.querySelector(".alert")?.textContent).toEqual( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - describe("with no context", () => { beforeAll(() => { (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; @@ -267,14 +209,26 @@ describe("Date Filter", () => { describe("with multiple instances", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() } - ] + const dateTimeAttribute = new ListAttributeValueBuilder() + .withType("DateTime") + .withFilterable(true) + .build() as unknown as AttributeMetaData; + + const dateFilterStore = new DateInputFilterStore([dateTimeAttribute], null); + + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: "datagrid/1", + provider: { + hasError: false, + value: { type: "direct", store: dateFilterStore } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context + + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( + filterAPI ); (window as any).mx = mxObject; @@ -296,14 +250,26 @@ describe("Date Filter", () => { describe("with session config", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() } - ] + const dateTimeAttribute = new ListAttributeValueBuilder() + .withType("DateTime") + .withFilterable(true) + .build() as unknown as AttributeMetaData; + + const dateFilterStore = new DateInputFilterStore([dateTimeAttribute], null); + + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: "datagrid/1", + provider: { + hasError: false, + value: { type: "direct", store: dateFilterStore } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context + + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( + filterAPI ); }); diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/typings.ts b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/typings.ts new file mode 100644 index 0000000000..e787bc9de5 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/typings.ts @@ -0,0 +1,6 @@ +import { Date_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface"; + +export interface DateFilterProps { + filterStore: Date_InputFilterInterface; + parentChannelName?: string; +} diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx deleted file mode 100644 index 38b85272f1..0000000000 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { useDateFilterAPI, Date_FilterAPIv2 } from "@mendix/widget-plugin-filtering/helpers/useDateFilterAPI"; -import { createElement } from "react"; - -export function withDateFilterAPI

( - Component: (props: P & Date_FilterAPIv2) => React.ReactElement -): (props: P) => React.ReactElement { - return function FilterAPIProvider(props) { - const api = useDateFilterAPI(""); - - if (api.hasError) { - return {api.error.message}; - } - - return ( - - ); - }; -} diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withLinkedDateStore.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withLinkedDateStore.tsx new file mode 100644 index 0000000000..684ecebf48 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withLinkedDateStore.tsx @@ -0,0 +1,28 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { DateStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/DateStoreProvider"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { AttributeMetaData } from "mendix"; +import { createElement, FC } from "react"; +import { DateFilterProps } from "../components/typings"; + +interface RequiredProps { + attributes: Array<{ + attribute: AttributeMetaData; + }>; + name: string; +} + +export function withLinkedDateStore

( + Component: FC

+): FC

{ + return function DateStoreProviderHost(props) { + const { store } = useSetup( + () => + new DateStoreProvider(props.filterAPI, { + attributes: props.attributes.map(obj => obj.attribute), + dataKey: props.name + }) + ); + return ; + }; +} diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withParentProvidedDateStore.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withParentProvidedDateStore.tsx new file mode 100644 index 0000000000..4580e31a74 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withParentProvidedDateStore.tsx @@ -0,0 +1,49 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "@mendix/widget-plugin-filtering/errors"; +import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta"; +import { isDateFilter } from "@mendix/widget-plugin-filtering/stores/input/store-utils"; +import { createElement, useRef } from "react"; +import { DateFilterProps } from "../components/typings"; + +export function withParentProvidedDateStore

( + Component: (props: P & DateFilterProps) => React.ReactElement +): (props: P) => React.ReactElement { + return function FilterAPIProvider(props: P): React.ReactElement { + const api = useDateFilterAPI(); + if (api.hasError) { + return {api.error.message}; + } + + return ( + + ); + }; +} + +export function useDateFilterAPI(): Result { + const ctx = useFilterAPI(); + const dateAPI = useRef(); + + if (ctx.hasError) { + return error(ctx.error); + } + + const api = ctx.value; + + if (api.provider.hasError) { + return error(api.provider.error); + } + + const store = api.provider.value.type === "direct" ? api.provider.value.store : null; + + if (store === null) { + return error(EMISSINGSTORE); + } + + if (store.storeType !== "input" || !isDateFilter(store)) { + return error(EStoreTypeMisMatch("date filter", store.storeType !== "input" ? "select" : store.arg1.type)); + } + + return value((dateAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); +} diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml b/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml index b9f5344be8..78fe98c72a 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts index 2855ea37b7..970ae14f7d 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts +++ b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts @@ -4,16 +4,27 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix"; + +export type AttrChoiceEnum = "auto" | "linked"; + +export interface AttributesType { + attribute: AttributeMetaData; +} export type DefaultFilterEnum = "between" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty"; +export interface AttributesPreviewType { + attribute: string; +} + export interface DatagridDateFilterContainerProps { name: string; class: string; style?: CSSProperties; tabIndex?: number; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesType[]; defaultValue?: DynamicValue; defaultStartDate?: DynamicValue; defaultEndDate?: DynamicValue; @@ -40,7 +51,8 @@ export interface DatagridDateFilterPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesPreviewType[]; defaultValue: string; defaultStartDate: string; defaultEndDate: string; diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/jest.config.js b/packages/pluggableWidgets/datagrid-dropdown-filter-web/jest.config.js index c2a5ad5aaa..1e8aa46092 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/jest.config.js +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/jest.config.js @@ -1,8 +1,35 @@ +const pwtCfg = require("@mendix/pluggable-widgets-tools/test-config/jest.config"); + module.exports = { - ...require("@mendix/pluggable-widgets-tools/test-config/jest.config"), /** * `nanoevents` package is ESM module and because ESM is not supported by Jest yet * we mark `nanoevents` as a module that should be transformed by ts-jest. */ - transformIgnorePatterns: ["node_modules/(?!nanoevents)/"] + transformIgnorePatterns: ["node_modules/(?!nanoevents)/"], + modulePathIgnorePatterns: ["/dist/"], + transform: { + "^.+\\.(t|j)sx?$": [ + "@swc/jest", + { + jsc: { + transform: { + react: { + runtime: "automatic" + } + } + } + } + ] + }, + moduleDirectories: ["node_modules", "src"], + moduleNameMapper: { + ...pwtCfg.moduleNameMapper, + "big.js": "big.js", + "(.+)\\.js": "$1" + }, + extensionsToTreatAsEsm: [".ts"], + testEnvironment: "jest-environment-jsdom", + collectCoverage: !process.env.CI, + coverageProvider: "v8", + testMatch: ["/src/**/*.spec.{js,jsx,ts,tsx}"] }; diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json index ce7b24e2b5..cc24950035 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-dropdown-filter-web", "widgetName": "DatagridDropdownFilter", - "version": "2.10.1", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "datagrid-dropdown-filter-web/main" + "branchName": "datagrid-dropdown-filter-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", @@ -41,6 +41,7 @@ "verify": "rui-verify-package-format" }, "dependencies": { + "@mendix/widget-plugin-dropdown-filter": "workspace:^", "@mendix/widget-plugin-external-events": "workspace:*", "@mendix/widget-plugin-filtering": "workspace:*", "classnames": "^2.3.2" diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts index cd2dbe6373..fd528df8a8 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts @@ -1,4 +1,4 @@ -import { hidePropertyIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; +import { hidePropertiesIn, hidePropertyIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { chevronDownIcon, chevronDownIconDark } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons"; import { ContainerProps, @@ -13,8 +13,10 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul const showSelectedItemsStyle = values.filterable && values.multiSelect; const showSelectionMethod = showSelectedItemsStyle && values.selectedItemsStyle === "boxes"; - if (values.auto) { - hidePropertyIn(defaultProperties, values, "filterOptions"); + if (values.baseType === "attr") { + defaultProperties = attrGroupProperties(values, defaultProperties); + } else { + hidePropertiesIn(defaultProperties, values, ["attr", "attrChoice", "filterOptions", "auto"]); } if (values.filterable) { @@ -33,6 +35,21 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul return defaultProperties; } +function attrGroupProperties(values: DatagridDropdownFilterPreviewProps, defaultProperties: Properties): Properties { + hidePropertiesIn(defaultProperties, values, ["refEntity", "refOptions", "refCaption", "fetchOptionsLazy"]); + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); + hidePropertyIn(defaultProperties, values, "attr"); + } + + if (values.auto) { + hidePropertyIn(defaultProperties, values, "filterOptions"); + } + + return defaultProperties; +} + export const getPreview = (values: DatagridDropdownFilterPreviewProps, isDarkMode: boolean): StructurePreviewProps => { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; return { diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx index 9d42df5e76..59596a465a 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx @@ -1,10 +1,10 @@ import { enableStaticRendering } from "mobx-react-lite"; -enableStaticRendering(true); - import { createElement, ReactElement } from "react"; import { DatagridDropdownFilterPreviewProps } from "../typings/DatagridDropdownFilterProps"; import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style"; -import { Select } from "@mendix/widget-plugin-filtering/controls/select/Select"; +import { Select } from "@mendix/widget-plugin-dropdown-filter/controls/select/Select"; + +enableStaticRendering(true); function Preview(props: DatagridDropdownFilterPreviewProps): ReactElement { return ( diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx index 5435f96422..b116cc4667 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx @@ -1,42 +1,17 @@ import { createElement, ReactElement } from "react"; import { withPreloader } from "@mendix/widget-plugin-platform/hoc/withPreloader"; import { DatagridDropdownFilterContainerProps } from "../typings/DatagridDropdownFilterProps"; -import { StaticFilterContainer } from "./components/StaticFilterContainer"; -import { withSelectFilterAPI, Select_FilterAPIv2 } from "./hocs/withSelectFilterAPI"; -import { RefFilterContainer } from "./components/RefFilterContainer"; +import { AttrFilter } from "./components/AttrFilter"; +import { RefFilter } from "./components/RefFilter"; -function Container(props: DatagridDropdownFilterContainerProps & Select_FilterAPIv2): React.ReactElement { - const commonProps = { - ariaLabel: props.ariaLabel?.value, - className: props.class, - tabIndex: props.tabIndex, - styles: props.style, - onChange: props.onChange, - valueAttribute: props.valueAttribute, - parentChannelName: props.parentChannelName, - name: props.name, - multiselect: props.multiSelect, - emptyCaption: props.emptyOptionCaption?.value, - defaultValue: props.defaultValue?.value, - filterable: props.filterable, - selectionMethod: props.selectionMethod, - selectedItemsStyle: props.selectedItemsStyle, - clearable: props.clearable - }; - - if (props.filterStore.storeType === "refselect") { - return ; +function Container(props: DatagridDropdownFilterContainerProps): ReactElement { + if (props.baseType === "attr") { + return ; } - return ( - - ); + return ; } -const container = withPreloader(Container, props => props.defaultValue?.status === "loading"); - -const Widget = withSelectFilterAPI(container); +const DatagridDropdownFilter = withPreloader(Container, props => props.defaultValue?.status === "loading"); -export default function DatagridDropdownFilter(props: DatagridDropdownFilterContainerProps): ReactElement { - return ; -} +export default DatagridDropdownFilter; diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml index 5e32a4a329..1da95f1c1b 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml @@ -7,16 +7,41 @@ https://docs.mendix.com/appstore/modules/data-grid-2#7-2-drop-down-filter - + + + Filter by + + + Attribute + Association + + + + Datasource to Filter + + + + + + Attribute config + "Auto" works only when the widget is placed in a Data grid column. + + Auto + Custom + + + + Attribute + + + + + + Automatic options Show options based on the references or the enumeration values and captions. - - Default value - Empty option caption will be shown by default or if configured default value matches none of the options - - Options @@ -34,6 +59,40 @@ + + + + + Entity + Set the entity to enable filtering over association. + + + + + + + Selectable objects + The options to show in the Drop-down filter widget. + + + Caption + + + + + + + Use lazy load + Lazy loading enables faster parent loading, but with personalization enabled, value restoration will be limited. + + + + + + Default value + Empty option caption will be shown by default or if configured default value matches none of the options + + Filterable diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/DatagridDropdownFilter.spec.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/DatagridDropdownFilter.spec.tsx new file mode 100644 index 0000000000..a96f465594 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/DatagridDropdownFilter.spec.tsx @@ -0,0 +1,64 @@ +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; +import { listAttribute } from "@mendix/widget-plugin-test-utils"; +import "@testing-library/jest-dom"; +import { render, waitFor } from "@testing-library/react"; +import { AssociationMetaData, AttributeMetaData } from "mendix"; +import { createContext, createElement } from "react"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; +import DatagridDropdownFilter from "../DatagridDropdownFilter"; + +const commonProps: DatagridDropdownFilterContainerProps = { + class: "filter-custom-class", + tabIndex: 0, + name: "filter-test", + attrChoice: "auto", + baseType: "attr", + attr: undefined as unknown as AttributeMetaData, + auto: false, + filterOptions: [], + refEntity: undefined as unknown as AssociationMetaData, + fetchOptionsLazy: false, + filterable: false, + multiSelect: false, + clearable: false, + selectedItemsStyle: "text", + selectionMethod: "checkbox" +}; + +describe("Dropdown Filter", () => { + describe("with single instance", () => { + describe("with single attribute", () => { + beforeEach(() => { + const attribute = listAttribute(() => "a"); + attribute.type = "Enum"; + attribute.universe = ["a", "b", "c"]; + + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: "datagrid/1", + provider: { + hasError: false, + value: { type: "direct", store: new EnumFilterStore([attribute], null) } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] + }; + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( + filterAPI + ); + }); + + it("renders correctly", async () => { + const { asFragment, getByRole } = render(); + await waitFor(() => getByRole("combobox")); + expect(asFragment()).toMatchSnapshot(); + }); + + afterAll(() => { + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; + }); + }); + }); +}); diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/__snapshots__/DatagridDropdownFilter.spec.tsx.snap similarity index 86% rename from packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap rename to packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/__snapshots__/DatagridDropdownFilter.spec.tsx.snap index 28606ed0ee..a0f6df8d4d 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/__tests__/__snapshots__/DatagridDropdownFilter.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Dropdown Filter with single instance with single attribute DOM structure renders correctly 1`] = ` +exports[`Dropdown Filter with single instance with single attribute renders correctly 1`] = `

diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx new file mode 100644 index 0000000000..198f99f302 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx @@ -0,0 +1,34 @@ +import { EnumFilterContainer } from "@mendix/widget-plugin-dropdown-filter/containers/EnumFilterContainer"; +import { withFilterAPI } from "@mendix/widget-plugin-filtering/helpers/withFilterAPI"; +import { ReactElement, createElement } from "react"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; +import { withLinkedEnumStore } from "../hocs/withLinkedEnumStore"; +import { withParentProvidedEnumStore } from "../hocs/withParentProvidedEnumStore"; +import { EnumFilterProps } from "./typings"; + +export function AttrFilter(props: DatagridDropdownFilterContainerProps): ReactElement { + if (props.attrChoice === "auto") { + return ; + } + + return ; +} + +const AutoAttrFilter = withParentProvidedEnumStore(Connector); + +const LinkedAttrFilter = withFilterAPI(withLinkedEnumStore(Connector)); + +function Connector(props: DatagridDropdownFilterContainerProps & EnumFilterProps): ReactElement { + return ( + + ); +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx new file mode 100644 index 0000000000..3f61855887 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx @@ -0,0 +1,22 @@ +import { createElement, ReactElement } from "react"; +import { withLinkedRefStore } from "../hocs/withLinkedRefStore"; +import { RefFilterContainer } from "@mendix/widget-plugin-dropdown-filter/containers/RefFilterContainer"; +import { RefFilterProps } from "./typings"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; + +function Connector(props: DatagridDropdownFilterContainerProps & RefFilterProps): ReactElement { + return ( + + ); +} + +export const RefFilter = withLinkedRefStore(Connector); diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx deleted file mode 100644 index 2306dd4a38..0000000000 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import "@testing-library/jest-dom"; -import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context"; -import { - HeaderFiltersStore, - HeaderFiltersStoreProps -} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; -import { dynamicValue, ListAttributeValueBuilder } from "@mendix/widget-plugin-test-utils"; -import { createContext, createElement } from "react"; -import DatagridDropdownFilter from "../../DatagridDropdownFilter"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; - -interface StaticInfo { - name: string; - filtersChannelName: string; -} - -const commonProps = { - class: "filter-custom-class", - tabIndex: 0, - name: "filter-test", - advanced: false, - groupKey: "dropdown-filter", - filterable: false, - clearable: true, - selectionMethod: "checkbox" as const, - selectedItemsStyle: "text" as const -}; - -const headerFilterStoreInfo: StaticInfo = { - name: commonProps.name, - filtersChannelName: "" -}; - -const consoleError = global.console.error; -jest.spyOn(global.console, "error").mockImplementation((...args: any[]) => { - const [msg] = args; - - if (typeof msg === "string" && msg.startsWith("downshift:")) { - return; - } - - consoleError(...args); -}); - -describe("Dropdown Filter", () => { - describe("with single instance", () => { - afterEach(() => { - delete (global as any)["com.mendix.widgets.web.UUID"]; - }); - - describe("with single attribute", () => { - function mockCtx(universe: string[]): void { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(universe) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - } - beforeEach(() => { - mockCtx(["enum_value_1", "enum_value_2"]); - }); - - describe("with auto options", () => { - it("loads correct values from universe", async () => { - const filter = render( - - ); - - const trigger = filter.getByRole("combobox"); - - await fireEvent.click(trigger); - - const items = filter.getAllByRole("option"); - - items.forEach((item, index) => { - if (index === 0) { - return; - } - expect(item.textContent).toEqual(`enum_value_${index}`); - }); - }); - }); - - describe("DOM structure", () => { - it("renders correctly", () => { - const { asFragment } = render( - - ); - - expect(asFragment()).toMatchSnapshot(); - }); - }); - - describe("with defaultValue", () => { - it("initialize component with defaultValue", () => { - render( - ("enum_value_1")} - /> - ); - - expect(screen.getByRole("combobox")).toHaveAccessibleName("enum_value_1"); - }); - - it("don't sync defaultValue with state when defaultValue changes from undefined to string", async () => { - const { rerender } = render( - ("")} - /> - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); - }); - - // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. - rerender( - ("")} - /> - ); - rerender( - ("enum_value_1")} - /> - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); - }); - }); - - it("don't sync defaultValue with state when defaultValue changes from string to undefined", async () => { - mockCtx(["xyz", "abc"]); - const { rerender } = render( - ("xyz")} - /> - ); - - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); - - // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. - rerender( - ("xyz")} - /> - ); - rerender( - - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); - }); - }); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with multiple attributes", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withUniverse([true, false]) - .withType("Boolean") - .withFilterable(true) - .withFormatter( - value => (value ? "Yes" : "No"), - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - describe("with auto options", () => { - it("loads correct values from universes", async () => { - const filter = render( - - ); - - const trigger = filter.getByRole("combobox"); - await fireEvent.click(trigger); - - expect(filter.getAllByRole("option").map(item => item.textContent)).toStrictEqual([ - "None", - "enum_value_1", - "enum_value_2", - "Yes", - "No" - ]); - }); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong attribute's type", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder().withType("String").withFilterable(true).build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong multiple attributes' types", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("String") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("Decimal") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with no context", () => { - beforeAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "The filter widget must be placed inside the column or header of the Data grid 2.0 or inside header of the Gallery widget." - ); - }); - }); - - describe("with invalid values", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - ("wrong value"), - value: dynamicValue("enum_value_3") - } - ]} - /> - ); - - expect(container.querySelector(".alert")?.textContent).toBe("Invalid option value: 'enum_value_3'"); - }); - }); - - describe("with multiple invalid values", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withUniverse([true, false]) - .withType("Boolean") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - ("wrong enum value"), - value: dynamicValue("enum_value_3") - }, - { - caption: dynamicValue("wrong boolean value"), - value: dynamicValue("no") - } - ]} - /> - ); - - expect(container.querySelector(".alert")?.textContent).toBe("Invalid option value: 'enum_value_3'"); - }); - }); - }); - - describe("with multiple instances", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders with a unique id", () => { - const { asFragment: fragment1 } = render( - - ); - const { asFragment: fragment2 } = render( - - ); - - expect(fragment1().querySelector("button")?.getAttribute("aria-controls")).not.toBe( - fragment2().querySelector("button")?.getAttribute("aria-controls") - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - delete (global as any)["com.mendix.widgets.web.UUID"]; - }); - }); -}); diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts new file mode 100644 index 0000000000..a909d01a47 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts @@ -0,0 +1,12 @@ +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; +import { RefFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/RefFilterStore"; + +export interface EnumFilterProps { + filterStore: EnumFilterStore; + parentChannelName?: string; +} + +export interface RefFilterProps { + filterStore: RefFilterStore; + parentChannelName?: string; +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx new file mode 100644 index 0000000000..f246242a5e --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx @@ -0,0 +1,22 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { createElement, FC } from "react"; +import { EnumStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/EnumStoreProvider"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { AttributeMetaData } from "mendix"; +import { EnumFilterProps } from "../components/typings"; + +interface RequiredProps { + attr: AttributeMetaData; + name: string; +} + +export function withLinkedEnumStore

( + Component: FC

+): FC

{ + return function ProviderHost(props) { + const { store } = useSetup( + () => new EnumStoreProvider(props.filterAPI, { attributes: [props.attr], dataKey: props.name }) + ); + return ; + }; +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx new file mode 100644 index 0000000000..e07634f95d --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx @@ -0,0 +1,90 @@ +import { useEffect, createElement } from "react"; +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { RefFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/RefFilterStore"; +import { FilterAPI, useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { BaseStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/BaseStoreProvider"; +import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; +import { GateProvider } from "@mendix/widget-plugin-mobx-kit/GateProvider"; +import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst"; +import { ListValue, ListAttributeValue, AssociationMetaData } from "mendix"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { RefFilterProps } from "../components/typings"; + +type WidgetProps = Pick; + +export interface RequiredProps { + name: string; + refEntity: AssociationMetaData; + refOptions: ListValue; + refCaption: ListAttributeValue; + searchAttrId: ListAttributeValue["id"]; +} + +type Component

= (props: P) => React.ReactElement; + +export function withLinkedRefStore

(Cmp: Component

): Component

{ + function StoreProvider(props: P & { filterAPI: FilterAPI }): React.ReactElement { + const gate = useGate(props); + const provider = useSetup(() => new RefStoreProvider(props.filterAPI, gate)); + return ; + } + return function FilterAPIProvider(props) { + const api = useFilterAPI(); + + if (api.hasError) { + return {api.error.message}; + } + + return ; + }; +} + +function mapProps(props: WidgetProps): RequiredProps { + if (!props.refEntity) { + throw new Error("RefFilterStoreProvider: refEntity is required"); + } + + if (!props.refOptions) { + throw new Error("RefFilterStoreProvider: refOptions is required"); + } + + if (!props.refCaption) { + throw new Error("RefFilterStoreProvider: refCaption is required"); + } + return { + name: props.name, + refEntity: props.refEntity, + refOptions: props.refOptions, + refCaption: props.refCaption, + searchAttrId: props.refCaption.id + }; +} + +function useGate(props: WidgetProps): DerivedPropsGate { + const gp = useConst(() => new GateProvider(mapProps(props))); + useEffect(() => { + gp.setProps(mapProps(props)); + }); + return gp.gate; +} + +class RefStoreProvider extends BaseStoreProvider { + protected _store: RefFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, gate: DerivedPropsGate) { + super(); + this.filterAPI = filterAPI; + this.dataKey = gate.props.name; + this._store = new RefFilterStore({ + gate, + initCond: this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + }); + } + + get store(): RefFilterStore { + return this._store; + } +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx new file mode 100644 index 0000000000..e80b89aed2 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx @@ -0,0 +1,48 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { useRef, createElement } from "react"; +import { useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "@mendix/widget-plugin-filtering/errors"; +import { Result, error, value } from "@mendix/widget-plugin-filtering/result-meta"; +import { EnumFilterProps } from "../components/typings"; + +export function withParentProvidedEnumStore

( + Component: (props: P & EnumFilterProps) => React.ReactElement +): (props: P) => React.ReactElement { + return function FilterAPIProvider(props: P): React.ReactElement { + const api = useEnumFilterAPI(); + if (api.hasError) { + return {api.error.message}; + } + + return ( + + ); + }; +} + +function useEnumFilterAPI(): Result { + const ctx = useFilterAPI(); + const slctAPI = useRef(); + + if (ctx.hasError) { + return error(ctx.error); + } + + const api = ctx.value; + + if (api.provider.hasError) { + return error(api.provider.error); + } + + const store = api.provider.value.type === "direct" ? api.provider.value.store : null; + + if (store === null) { + return error(EMISSINGSTORE); + } + + if (store.storeType !== "select") { + return error(EStoreTypeMisMatch("dropdown filter", store.arg1.type)); + } + + return value((slctAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx deleted file mode 100644 index f8f6a74f88..0000000000 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { Select_FilterAPIv2, useSelectFilterAPI } from "@mendix/widget-plugin-filtering/helpers/useSelectFilterAPI"; -import { createElement } from "react"; - -export { Select_FilterAPIv2 }; - -export function withSelectFilterAPI

( - Component: (props: P & Select_FilterAPIv2) => React.ReactElement -): (props: P) => React.ReactElement { - return function FilterAPIProvider(props: P): React.ReactElement { - const api = useSelectFilterAPI(props); - if (api.hasError) { - return {api.error.message}; - } - - return ( - - ); - }; -} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml index 800d1df062..fc38a0b891 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts index c1c4cd5e41..a443bd6589 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts @@ -4,7 +4,11 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, AssociationMetaData, AttributeMetaData, DynamicValue, EditableValue, ListValue, ListAttributeValue } from "mendix"; + +export type BaseTypeEnum = "attr" | "ref"; + +export type AttrChoiceEnum = "auto" | "linked"; export interface FilterOptionsType { caption: DynamicValue; @@ -25,9 +29,16 @@ export interface DatagridDropdownFilterContainerProps { class: string; style?: CSSProperties; tabIndex?: number; + baseType: BaseTypeEnum; + attrChoice: AttrChoiceEnum; + attr: AttributeMetaData; auto: boolean; - defaultValue?: DynamicValue; filterOptions: FilterOptionsType[]; + refEntity: AssociationMetaData; + refOptions?: ListValue; + refCaption?: ListAttributeValue; + fetchOptionsLazy: boolean; + defaultValue?: DynamicValue; filterable: boolean; multiSelect: boolean; emptyOptionCaption?: DynamicValue; @@ -50,9 +61,16 @@ export interface DatagridDropdownFilterPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; + baseType: BaseTypeEnum; + attrChoice: AttrChoiceEnum; + attr: string; auto: boolean; - defaultValue: string; filterOptions: FilterOptionsPreviewType[]; + refEntity: string; + refOptions: {} | { caption: string } | { type: string } | null; + refCaption: string; + fetchOptionsLazy: boolean; + defaultValue: string; filterable: boolean; multiSelect: boolean; emptyOptionCaption: string; diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/package.json b/packages/pluggableWidgets/datagrid-number-filter-web/package.json index 391eabb0e4..3ec5aaaadf 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-number-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-number-filter-web", "widgetName": "DatagridNumberFilter", - "version": "2.9.3", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "datagrid-number-filter-web/main" + "branchName": "datagrid-number-filter-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts index 4c52aa7205..a6c2f1d30f 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts @@ -1,10 +1,4 @@ -import { - ContainerProps, - ImageProps, - structurePreviewPalette, - StructurePreviewProps, - text -} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; +import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { emptyIcon, emptyIconDark, @@ -23,25 +17,26 @@ import { smallerThanIcon, smallerThanIconDark } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons"; -import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; +import { + ContainerProps, + ImageProps, + structurePreviewPalette, + StructurePreviewProps, + text +} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; import { DatagridNumberFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridNumberFilterProps"; -export function getProperties( - values: DatagridNumberFilterPreviewProps, - defaultProperties: Properties, - platform: "web" | "desktop" -): Properties { +export function getProperties(values: DatagridNumberFilterPreviewProps, defaultProperties: Properties): Properties { if (!values.adjustable) { hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption"); } - if (platform === "web") { - if (!values.advanced) { - hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]); - } - } else { - hidePropertyIn(defaultProperties, values, "advanced"); + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, values, "attributes"); + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); } + return defaultProperties; } diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx index f306da389c..067fd752ff 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx @@ -1,13 +1,22 @@ -import { ReactElement, createElement } from "react"; +import { withFilterAPI } from "@mendix/widget-plugin-filtering/helpers/withFilterAPI"; import { withPreloader } from "@mendix/widget-plugin-platform/hoc/withPreloader"; +import { ReactElement, createElement } from "react"; import { DatagridNumberFilterContainerProps } from "../typings/DatagridNumberFilterProps"; import { NumberFilterContainer } from "./components/NumberFilterContainer"; +import { withLinkedNumberStore } from "./hocs/withLinkedNumberStore"; +import { withParentProvidedNumberStore } from "./hocs/withParentProvidedNumberStore"; import { isLoadingDefaultValues } from "./utils/widget-utils"; -import { withNumberFilterAPI } from "./hocs/withNumberFilterAPI"; -const container = withPreloader(NumberFilterContainer, isLoadingDefaultValues); -const Widget = withNumberFilterAPI(container); +const Container = withPreloader(NumberFilterContainer, isLoadingDefaultValues); + +const FilterAuto = withParentProvidedNumberStore(Container); + +const FilterLinked = withFilterAPI(withLinkedNumberStore(Container)); export default function DatagridNumberFilter(props: DatagridNumberFilterContainerProps): ReactElement { - return ; + if (props.attrChoice === "auto") { + return ; + } + + return ; } diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml index 5c024b076f..e628073200 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml @@ -8,9 +8,35 @@ - - Enable advanced options + + Filter attributes + + Auto + Custom + + + + Datasource to Filter + + + + Attributes + Select the attributes that the end-user may use for filtering. + + + + Attribute + + + + + + + + + + Default value diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/NumberFilterContainer.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/NumberFilterContainer.tsx index 791cac71d1..6c77a8f437 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/NumberFilterContainer.tsx +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/NumberFilterContainer.tsx @@ -1,8 +1,9 @@ import { useOnResetValueEvent, useOnSetValueEvent } from "@mendix/widget-plugin-external-events/hooks"; +import { NumberFilterController } from "@mendix/widget-plugin-filtering/controllers/input/NumberInputController"; import { FilterFnList, InputWithFilters } from "@mendix/widget-plugin-filtering/controls"; import { useBasicSync } from "@mendix/widget-plugin-filtering/helpers/useBasicSync"; -import { useNumberFilterController } from "@mendix/widget-plugin-filtering/helpers/useNumberFilterController"; import { Number_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { observer } from "mobx-react-lite"; import { createElement, useRef } from "react"; @@ -28,20 +29,23 @@ const filters: FilterFnList = Object.entries(filterDefs).map( export interface ContainerProps extends DatagridNumberFilterContainerProps { filterStore: Number_InputFilterInterface; - parentChannelName: string | undefined; + parentChannelName?: string; } function Container(props: ContainerProps): React.ReactElement { const id = (useRef().current ??= `NumberFilter${generateUUID()}`); - const controller = useNumberFilterController({ - filter: props.filterStore, - changeDelay: props.delay, - defaultFilter: props.defaultFilter, - adjustableFilterFunction: props.adjustable, - defaultValue: props.defaultValue?.value, - disableInputs: fn => fn === "empty" || fn === "notEmpty" - }); + const controller = useSetup( + () => + new NumberFilterController({ + filter: props.filterStore, + changeDelay: props.delay, + defaultFilter: props.defaultFilter, + adjustableFilterFunction: props.adjustable, + defaultValue: props.defaultValue?.value, + disableInputs: fn => fn === "empty" || fn === "notEmpty" + }) + ); useBasicSync(props, props.filterStore); @@ -52,6 +56,7 @@ function Container(props: ContainerProps): React.ReactElement { }); useOnSetValueEvent({ widgetName: props.name, listener: controller.handleSetValue }); + return ( { afterEach(() => (console.warn as jest.Mock).mockRestore()); +const CHANNEL_NAME = "datagrid/1"; + +const setContext = (store: NumberInputFilterStore) => { + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: CHANNEL_NAME, + provider: { + hasError: false, + value: { type: "direct", store } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] + }; + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(filterAPI); +}; + describe("Number Filter", () => { describe("with single instance", () => { afterEach(() => { @@ -60,25 +67,16 @@ describe("Number Filter", () => { describe("with single attribute", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withType("Long") - .withFormatter( - value => (value ? value.toString() : ""), - (value: string) => ({ valid: true, value }) - ) - .withFilterable(true) - .build() - } - ], - parentChannelName: headerFilterStoreInfo.filtersChannelName - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + const attr = new ListAttributeValueBuilder() + .withType("Long") + .withFormatter( + value => (value ? value.toString() : ""), + (value: string) => ({ valid: true, value }) + ) + .withFilterable(true) + .build(); + + setContext(new NumberInputFilterStore([attr as unknown as AttributeMetaData], null)); }); it("renders correctly", () => { @@ -124,7 +122,7 @@ describe("Number Filter", () => { // Trigger reset event const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "reset.value", false); + plugin.emit(CHANNEL_NAME, "reset.value", false); }); expect(input).toHaveValue(""); @@ -179,7 +177,7 @@ describe("Number Filter", () => { // Trigger reset event const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "reset.value", true); + plugin.emit(CHANNEL_NAME, "reset.value", true); }); expect(input).toHaveValue("123"); @@ -194,41 +192,33 @@ describe("Number Filter", () => { describe("with multiple attributes", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("Long") - .withFormatter( - value => value, - () => { - // noop - } - ) - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("Decimal") - .withFormatter( - value => value, - () => { - // noop - } - ) - .withFilterable(true) - .build() - } - ], - parentChannelName: headerFilterStoreInfo.filtersChannelName - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + const attrs = [ + new ListAttributeValueBuilder() + .withId("attribute1") + .withType("Long") + .withFormatter( + value => value, + () => { + // noop + } + ) + .withFilterable(true) + .build(), + + new ListAttributeValueBuilder() + .withId("attribute2") + .withType("Decimal") + .withFormatter( + value => value, + () => { + // noop + } + ) + .withFilterable(true) + .build() + ] as unknown as Array>; + + setContext(new NumberInputFilterStore(attrs, null)); }); it("renders correctly", () => { @@ -255,7 +245,7 @@ describe("Number Filter", () => { const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "reset.value", false); + plugin.emit(CHANNEL_NAME, "reset.value", false); }); expect(input).toHaveValue(""); @@ -294,67 +284,6 @@ describe("Number Filter", () => { }); }); - describe("with wrong attribute's type", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("Boolean").withFilterable(true).build() } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { asFragment } = render(); - - expect(asFragment()).toMatchSnapshot(); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong multiple attributes' types", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("String") - .withSortable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("HashString") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { asFragment } = render(); - - expect(asFragment()).toMatchSnapshot(); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - describe("with no context", () => { beforeAll(() => { (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; @@ -370,26 +299,18 @@ describe("Number Filter", () => { describe("with multiple instances", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withType("Long") - .withFormatter( - value => value, - () => { - // noop - } - ) - .withFilterable(true) - .build() + const attr = new ListAttributeValueBuilder() + .withType("Long") + .withFormatter( + value => value, + () => { + // noop } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + ) + .withFilterable(true) + .build() as unknown as AttributeMetaData; + + setContext(new NumberInputFilterStore([attr], null)); }); it("renders with a unique id", () => { diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/__snapshots__/DatagridNumberFilter.spec.tsx.snap b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/__snapshots__/DatagridNumberFilter.spec.tsx.snap index 3948802c5b..53816b7c0d 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/__snapshots__/DatagridNumberFilter.spec.tsx.snap +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/__snapshots__/DatagridNumberFilter.spec.tsx.snap @@ -95,23 +95,3 @@ exports[`Number Filter with single instance with single attribute renders correc `; - -exports[`Number Filter with single instance with wrong attribute's type renders error message 1`] = ` - -

- Unable to get filter store. Check parent widget configuration. -
- -`; - -exports[`Number Filter with single instance with wrong multiple attributes' types renders error message 1`] = ` - -
- Unable to get filter store. Check parent widget configuration. -
-
-`; diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/typings.ts b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/typings.ts new file mode 100644 index 0000000000..fdf12b134f --- /dev/null +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/typings.ts @@ -0,0 +1,6 @@ +import { Number_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface"; + +export interface NumberFilterProps { + filterStore: Number_InputFilterInterface; + parentChannelName?: string; +} diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedNumberStore.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedNumberStore.tsx new file mode 100644 index 0000000000..1de0c5a44e --- /dev/null +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedNumberStore.tsx @@ -0,0 +1,29 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { NumberStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/NumberStoreProvider"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { Big } from "big.js"; +import { AttributeMetaData } from "mendix"; +import { createElement, FC } from "react"; +import { NumberFilterProps } from "../components/typings"; + +interface RequiredProps { + attributes: Array<{ + attribute: AttributeMetaData; + }>; + name: string; +} + +export function withLinkedNumberStore

( + Component: FC

+): FC

{ + return function NumberStoreProviderHost(props) { + const { store } = useSetup( + () => + new NumberStoreProvider(props.filterAPI, { + attributes: props.attributes.map(obj => obj.attribute), + dataKey: props.name + }) + ); + return ; + }; +} diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx deleted file mode 100644 index e937dfd70a..0000000000 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { createElement } from "react"; -import { Number_FilterAPIv2, useNumberFilterAPI } from "@mendix/widget-plugin-filtering/helpers/useNumberFilterAPI"; - -export function withNumberFilterAPI

( - Component: (props: P & Number_FilterAPIv2) => React.ReactElement -): (props: P) => React.ReactElement { - return function FilterAPIProvider(props) { - const api = useNumberFilterAPI(""); - - if (api.hasError) { - return {api.error.message}; - } - - return ( - - ); - }; -} diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withParentProvidedNumberStore.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withParentProvidedNumberStore.tsx new file mode 100644 index 0000000000..528dacf8b6 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withParentProvidedNumberStore.tsx @@ -0,0 +1,49 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "@mendix/widget-plugin-filtering/errors"; +import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta"; +import { isNumberFilter } from "@mendix/widget-plugin-filtering/stores/input/store-utils"; +import { createElement, useRef } from "react"; +import { NumberFilterProps } from "../components/typings"; + +export function withParentProvidedNumberStore

( + Component: (props: P & NumberFilterProps) => React.ReactElement +): (props: P) => React.ReactElement { + return function FilterAPIProvider(props: P): React.ReactElement { + const api = useNumberFilterAPI(); + if (api.hasError) { + return {api.error.message}; + } + + return ( + + ); + }; +} + +export function useNumberFilterAPI(): Result { + const ctx = useFilterAPI(); + const numberAPI = useRef(); + + if (ctx.hasError) { + return error(ctx.error); + } + + const api = ctx.value; + + if (api.provider.hasError) { + return error(api.provider.error); + } + + const store = api.provider.value.type === "direct" ? api.provider.value.store : null; + + if (store === null) { + return error(EMISSINGSTORE); + } + + if (store.storeType !== "input" || !isNumberFilter(store)) { + return error(EStoreTypeMisMatch("number filter", store.storeType !== "input" ? "select" : store.arg1.type)); + } + + return value((numberAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); +} diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml b/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml index c64b0565c4..e039d03a35 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts index 0c2f405af5..8b9a872dff 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts +++ b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts @@ -4,17 +4,28 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix"; import { Big } from "big.js"; +export type AttrChoiceEnum = "auto" | "linked"; + +export interface AttributesType { + attribute: AttributeMetaData; +} + export type DefaultFilterEnum = "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty"; +export interface AttributesPreviewType { + attribute: string; +} + export interface DatagridNumberFilterContainerProps { name: string; class: string; style?: CSSProperties; tabIndex?: number; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesType[]; defaultValue?: DynamicValue; defaultFilter: DefaultFilterEnum; placeholder?: DynamicValue; @@ -37,7 +48,8 @@ export interface DatagridNumberFilterPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesPreviewType[]; defaultValue: string; defaultFilter: DefaultFilterEnum; placeholder: string; diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/package.json b/packages/pluggableWidgets/datagrid-text-filter-web/package.json index c0784d23d3..05ab7925c5 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-text-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-text-filter-web", "widgetName": "DatagridTextFilter", - "version": "2.9.2", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "datagrid-text-filter-web/9d04c79" + "branchName": "datagrid-text-filter-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", @@ -45,6 +45,7 @@ "@mendix/widget-plugin-external-events": "workspace:*", "@mendix/widget-plugin-filtering": "workspace:*", "@mendix/widget-plugin-hooks": "workspace:*", + "@mendix/widget-plugin-mobx-kit": "workspace:^", "@mendix/widget-plugin-platform": "workspace:*", "classnames": "^2.3.2", "mobx": "6.12.3", diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts index 2414af2eae..787ae0b8c9 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts @@ -1,10 +1,4 @@ -import { - ContainerProps, - ImageProps, - StructurePreviewProps, - text, - structurePreviewPalette -} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; +import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { containsIcon, containsIconDark, @@ -29,25 +23,27 @@ import { startsWithIcon, startsWithIconDark } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons"; -import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; +import { + ContainerProps, + ImageProps, + structurePreviewPalette, + StructurePreviewProps, + text +} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; import { DatagridTextFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridTextFilterProps"; -export function getProperties( - values: DatagridTextFilterPreviewProps, - defaultProperties: Properties, - platform: "web" | "desktop" -): Properties { +export function getProperties(values: DatagridTextFilterPreviewProps, defaultProperties: Properties): Properties { if (!values.adjustable) { hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption"); } - if (platform === "web") { - if (!values.advanced) { - hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]); - } - } else { - hidePropertyIn(defaultProperties, values, "advanced"); + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, values, "attributes"); } + + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); + return defaultProperties; } diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx index 5772f0eb2f..da3932e825 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx @@ -1,13 +1,22 @@ +import { withFilterAPI } from "@mendix/widget-plugin-filtering/helpers/withFilterAPI"; import { withPreloader } from "@mendix/widget-plugin-platform/hoc/withPreloader"; import { createElement, ReactElement } from "react"; import { DatagridTextFilterContainerProps } from "../typings/DatagridTextFilterProps"; import { TextFilterContainer } from "./components/TextFilterContainer"; -import { withTextFilterAPI } from "./hocs/withTextFilterAPI"; +import { withLinkedStringStore } from "./hocs/withLinkedStringStore"; +import { withParentProvidedStringStore } from "./hocs/withParentProvidedStringStore"; import { isLoadingDefaultValues } from "./utils/widget-utils"; -const container = withPreloader(TextFilterContainer, isLoadingDefaultValues); -const Widget = withTextFilterAPI(container); +const Container = withPreloader(TextFilterContainer, isLoadingDefaultValues); + +const FilterAuto = withParentProvidedStringStore(Container); + +const FilterLinked = withFilterAPI(withLinkedStringStore(Container)); export default function DatagridTextFilter(props: DatagridTextFilterContainerProps): ReactElement { - return ; + if (props.attrChoice === "auto") { + return ; + } + + return ; } diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml index d8ed8c4cfc..893a20f660 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml @@ -8,9 +8,32 @@ - - Enable advanced options + + Filter attributes + + Auto + Custom + + + + Datasource to Filter + + + + Attributes + Select the attributes that the end-user may use for filtering. + + + + Attribute + + + + + + + Default value diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/TextFilterContainer.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/TextFilterContainer.tsx index 390e05ea8f..e56fabd28a 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/TextFilterContainer.tsx +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/TextFilterContainer.tsx @@ -1,12 +1,13 @@ +import { useOnResetValueEvent, useOnSetValueEvent } from "@mendix/widget-plugin-external-events/hooks"; +import { StringFilterController } from "@mendix/widget-plugin-filtering/controllers/input/StringInputController"; import { FilterFnList, InputWithFilters } from "@mendix/widget-plugin-filtering/controls"; import { useBasicSync } from "@mendix/widget-plugin-filtering/helpers/useBasicSync"; -import { useStringFilterController } from "@mendix/widget-plugin-filtering/helpers/useStringFilterController"; import { String_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { observer } from "mobx-react-lite"; import { createElement, useRef } from "react"; import { DatagridTextFilterContainerProps, DefaultFilterEnum } from "../../typings/DatagridTextFilterProps"; -import { useOnResetValueEvent, useOnSetValueEvent } from "@mendix/widget-plugin-external-events/hooks"; const filterDefs: Record = { contains: "Contains", @@ -30,7 +31,7 @@ const filters: FilterFnList = Object.entries(filterDefs).map( ); export interface ContainerProps extends DatagridTextFilterContainerProps { - parentChannelName: string | undefined; + parentChannelName?: string; filterStore: String_InputFilterInterface; } @@ -38,14 +39,17 @@ export const TextFilterContainer: (props: ContainerProps) => React.ReactElement function TextFilterContainer(props) { const id = (useRef().current ??= `TextFilter${generateUUID()}`); - const controller = useStringFilterController({ - filter: props.filterStore, - defaultFilter: props.defaultFilter, - adjustableFilterFunction: props.adjustable, - defaultValue: props.defaultValue?.value, - changeDelay: props.delay, - disableInputs: fn => fn === "empty" || fn === "notEmpty" - }); + const controller = useSetup( + () => + new StringFilterController({ + filter: props.filterStore, + defaultFilter: props.defaultFilter, + adjustableFilterFunction: props.adjustable, + defaultValue: props.defaultValue?.value, + changeDelay: props.delay, + disableInputs: fn => fn === "empty" || fn === "notEmpty" + }) + ); useBasicSync(props, props.filterStore); diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx index 7726d55c48..620873af79 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx @@ -1,27 +1,22 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; import "@testing-library/jest-dom"; -import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context"; -import { - HeaderFiltersStore, - HeaderFiltersStoreProps -} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; +import { AttributeMetaData } from "mendix"; + +import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin"; +import { StringInputFilterStore } from "@mendix/widget-plugin-filtering/stores/input/StringInputFilterStore"; import { actionValue, dynamicValue, EditableValueBuilder, ListAttributeValueBuilder } from "@mendix/widget-plugin-test-utils"; -import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin"; -import { render, screen, act } from "@testing-library/react"; +import { act, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { resetIdCounter } from "downshift"; import { createContext, createElement } from "react"; -import DatagridTextFilter from "../../DatagridTextFilter"; import { DatagridTextFilterContainerProps } from "../../../typings/DatagridTextFilterProps"; -import { resetIdCounter } from "downshift"; - -interface StaticInfo { - name: string; - filtersChannelName: string; -} +import DatagridTextFilter from "../../DatagridTextFilter"; const commonProps: DatagridTextFilterContainerProps = { class: "filter-custom-class", @@ -29,13 +24,9 @@ const commonProps: DatagridTextFilterContainerProps = { name: "filter-test", defaultFilter: "equal" as const, adjustable: true, - advanced: false, - delay: 1000 -}; - -const headerFilterStoreInfo: StaticInfo = { - name: commonProps.name, - filtersChannelName: "datagrid1" + delay: 1000, + attrChoice: "auto", + attributes: [] }; jest.useFakeTimers(); @@ -49,6 +40,22 @@ beforeEach(() => { afterEach(() => (console.warn as jest.Mock).mockRestore()); +const CHANNEL_NAME = "datagrid/1"; + +const setContext = (store: StringInputFilterStore) => { + const filterAPI: FilterAPI = { + version: 3, + parentChannelName: CHANNEL_NAME, + provider: { + hasError: false, + value: { type: "direct", store } + }, + filterObserver: {} as ObservableFilterHost, + sharedInitFilter: [] + }; + (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(filterAPI); +}; + describe("Text Filter", () => { describe("with single instance", () => { afterEach(() => { @@ -57,25 +64,16 @@ describe("Text Filter", () => { describe("with defaultValue prop", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withType("String") - .withFormatter( - value => value, - value => ({ valid: true, value }) - ) - .withFilterable(true) - .build() - } - ], - parentChannelName: "datagrid1" - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + const attr = new ListAttributeValueBuilder() + .withType("String") + .withFormatter( + value => value, + value => ({ valid: true, value }) + ) + .withFilterable(true) + .build() as AttributeMetaData; + + setContext(new StringInputFilterStore([attr], null)); }); it("don't sync value when defaultValue changes from undefined to string", async () => { @@ -128,7 +126,7 @@ describe("Text Filter", () => { // Trigger reset event const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "reset.value", true); + plugin.emit(CHANNEL_NAME, "reset.value", true); }); expect(input).toHaveValue("a string"); @@ -158,7 +156,7 @@ describe("Text Filter", () => { // Trigger reset event const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "set.value", true, { + plugin.emit(CHANNEL_NAME, "set.value", true, { stringValue: "another string" }); }); @@ -170,25 +168,16 @@ describe("Text Filter", () => { describe("with single attribute", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withType("String") - .withFormatter( - value => value, - value => ({ valid: true, value }) - ) - .withFilterable(true) - .build() - } - ], - parentChannelName: "datagrid1" - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + const attr = new ListAttributeValueBuilder() + .withType("String") + .withFormatter( + value => value, + value => ({ valid: true, value }) + ) + .withFilterable(true) + .build() as AttributeMetaData; + + setContext(new StringInputFilterStore([attr], null)); }); beforeEach(() => { @@ -242,7 +231,7 @@ describe("Text Filter", () => { // Trigger reset event const plugin = requirePlugin(); act(() => { - plugin.emit("datagrid1", "reset.value", false); + plugin.emit(CHANNEL_NAME, "reset.value", false); }); expect(input).toHaveValue(""); @@ -256,104 +245,31 @@ describe("Text Filter", () => { describe("with multiple attributes", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("String") - .withFormatter( - value => value, - () => { - // - } - ) - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("HashString") - .withFormatter( - value => value, - () => { - // - } - ) - .withFilterable(true) - .build() + const attr1 = new ListAttributeValueBuilder() + .withId("attribute1") + .withType("String") + .withFormatter( + value => value, + value => ({ valid: true, value }) + ) + .withFilterable(true) + .build() as AttributeMetaData; + const attr2 = new ListAttributeValueBuilder() + .withId("attribute2") + .withType("HashString") + .withFormatter( + value => value, + () => { + // } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); + ) + .withFilterable(true) + .build() as AttributeMetaData; - it("renders correctly", () => { - const { asFragment } = render(); - - expect(asFragment()).toMatchSnapshot(); + setContext(new StringInputFilterStore([attr1, attr2], null)); }); - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong attribute's type", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { filter: new ListAttributeValueBuilder().withType("Decimal").withFilterable(true).build() } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { asFragment } = render(); - - expect(asFragment()).toMatchSnapshot(); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong multiple attributes' types", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("Decimal") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("Long") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { + it("renders correctly", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); @@ -379,26 +295,17 @@ describe("Text Filter", () => { describe("with multiple instances", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withType("String") - .withFormatter( - value => value, - () => { - // - } - ) - .withFilterable(true) - .build() + const attr = new ListAttributeValueBuilder() + .withType("String") + .withFormatter( + value => value, + () => { + // } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); + ) + .withFilterable(true) + .build() as AttributeMetaData; + setContext(new StringInputFilterStore([attr], null)); }); it("renders with a unique id", () => { diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/__snapshots__/DatagridTextFilter.spec.tsx.snap b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/__snapshots__/DatagridTextFilter.spec.tsx.snap index 53e78eb5dc..f008a41676 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/__snapshots__/DatagridTextFilter.spec.tsx.snap +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/__snapshots__/DatagridTextFilter.spec.tsx.snap @@ -95,23 +95,3 @@ exports[`Text Filter with single instance with single attribute renders correctl `; - -exports[`Text Filter with single instance with wrong attribute's type renders error message 1`] = ` - -

- Unable to get filter store. Check parent widget configuration. -
- -`; - -exports[`Text Filter with single instance with wrong multiple attributes' types renders error message 1`] = ` - -
- Unable to get filter store. Check parent widget configuration. -
-
-`; diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/typings.ts b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/typings.ts new file mode 100644 index 0000000000..8a9aa825dd --- /dev/null +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/typings.ts @@ -0,0 +1,6 @@ +import { String_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface"; + +export interface StringFilterProps { + filterStore: String_InputFilterInterface; + parentChannelName?: string; +} diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedStringStore.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedStringStore.tsx new file mode 100644 index 0000000000..7be53be6e1 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedStringStore.tsx @@ -0,0 +1,28 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { StringStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/StringStoreProvider"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { AttributeMetaData } from "mendix"; +import { createElement, FC } from "react"; +import { StringFilterProps } from "../components/typings"; + +interface RequiredProps { + attributes: Array<{ + attribute: AttributeMetaData; + }>; + name: string; +} + +export function withLinkedStringStore

( + Component: FC

+): FC

{ + return function StringStoreProviderHost(props) { + const { store } = useSetup( + () => + new StringStoreProvider(props.filterAPI, { + attributes: props.attributes.map(obj => obj.attribute), + dataKey: props.name + }) + ); + return ; + }; +} diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withParentProvidedStringStore.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withParentProvidedStringStore.tsx new file mode 100644 index 0000000000..51566d5ce7 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withParentProvidedStringStore.tsx @@ -0,0 +1,49 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "@mendix/widget-plugin-filtering/errors"; +import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta"; +import { isStringFilter } from "@mendix/widget-plugin-filtering/stores/input/store-utils"; +import { createElement, useRef } from "react"; +import { StringFilterProps } from "../components/typings"; + +export function withParentProvidedStringStore

( + Component: (props: P & StringFilterProps) => React.ReactElement +): (props: P) => React.ReactElement { + return function FilterAPIProvider(props: P): React.ReactElement { + const api = useStringFilterAPI(); + if (api.hasError) { + return {api.error.message}; + } + + return ( + + ); + }; +} + +export function useStringFilterAPI(): Result { + const ctx = useFilterAPI(); + const textAPI = useRef(); + + if (ctx.hasError) { + return error(ctx.error); + } + + const api = ctx.value; + + if (api.provider.hasError) { + return error(api.provider.error); + } + + const store = api.provider.value.type === "direct" ? api.provider.value.store : null; + + if (store === null) { + return error(EMISSINGSTORE); + } + + if (store.storeType !== "input" || !isStringFilter(store)) { + return error(EStoreTypeMisMatch("text filter", store.storeType !== "input" ? "select" : store.arg1.type)); + } + + return value((textAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); +} diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx deleted file mode 100644 index c54deea135..0000000000 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { String_FilterAPIv2, useStringFilterAPI } from "@mendix/widget-plugin-filtering/helpers/useStringFilterAPI"; -import { createElement } from "react"; - -export function withTextFilterAPI

( - Component: (props: P & String_FilterAPIv2) => React.ReactElement -): (props: P) => React.ReactElement { - return function FilterAPIProvider(props) { - const api = useStringFilterAPI(""); - - if (api.hasError) { - return {api.error.message}; - } - - return ( - - ); - }; -} diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml b/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml index 35bb2cb963..48e78f1246 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts index dfd4193e6a..170eb35aa8 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts +++ b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts @@ -4,16 +4,27 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix"; + +export type AttrChoiceEnum = "auto" | "linked"; + +export interface AttributesType { + attribute: AttributeMetaData; +} export type DefaultFilterEnum = "contains" | "startsWith" | "endsWith" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty"; +export interface AttributesPreviewType { + attribute: string; +} + export interface DatagridTextFilterContainerProps { name: string; class: string; style?: CSSProperties; tabIndex?: number; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesType[]; defaultValue?: DynamicValue; defaultFilter: DefaultFilterEnum; placeholder?: DynamicValue; @@ -36,7 +47,8 @@ export interface DatagridTextFilterPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; - advanced: boolean; + attrChoice: AttrChoiceEnum; + attributes: AttributesPreviewType[]; defaultValue: string; defaultFilter: DefaultFilterEnum; placeholder: string; diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js index 5bd4fc8537..8a390bba23 100644 --- a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js +++ b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js @@ -113,14 +113,14 @@ test.describe("capabilities: hiding", () => { const textAreaValue = await textArea.inputValue(); expect(JSON.parse(textAreaValue)).toEqual({ name: "datagrid5", - schemaVersion: 2, + schemaVersion: 3, settingsHash: "1530160614", columns: [ { columnId: "0", hidden: true }, { columnId: "1", hidden: false } ], columnFilters: [], - groupFilters: [], + customFilters: [], sortOrder: [], columnOrder: ["0", "1"] }); diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png index 0a3f6a8448..a97e654db7 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png index 945873f2bf..5556b728cc 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png index 07246c61d3..0d3fb8260e 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png index 04cf57bb3e..aa2e6d8dd5 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png index 5c0d86c012..10f3cf42ee 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png index ffd3b40c8b..1d4e5f58a2 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png index fa7d4360a2..ab39eeeb3f 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringMulti.spec.js b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringMulti.spec.js index c8779c9818..551dec57a9 100644 --- a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringMulti.spec.js +++ b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringMulti.spec.js @@ -57,7 +57,7 @@ test.describe("datagrid-web filtering multi select", () => { await expect(await rows()).toHaveCount(6); await option("Public librarian").click(); await expect(await rows()).toHaveCount(10); - await page.getByRole("columnheader", { name: "Roles (ref set)" }).getByRole("combobox").click(); + await roleSelect().click(); const columnTexts = await column(3).allTextContents(); expectedColumnText.forEach((text, index) => { expect(columnTexts[index]).toBe(text); diff --git a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png index ae86d2875f..8ba40e8125 100644 Binary files a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png differ diff --git a/packages/pluggableWidgets/datagrid-web/package.json b/packages/pluggableWidgets/datagrid-web/package.json index 806f74f8aa..66c56b4aca 100644 --- a/packages/pluggableWidgets/datagrid-web/package.json +++ b/packages/pluggableWidgets/datagrid-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-web", "widgetName": "Datagrid", - "version": "2.30.6", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "datagrid-web/referenceSet" + "branchName": "datagrid-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", @@ -42,6 +42,7 @@ }, "dependencies": { "@floating-ui/react": "^0.26.27", + "@mendix/widget-plugin-component-kit": "workspace:*", "@mendix/widget-plugin-external-events": "workspace:*", "@mendix/widget-plugin-filtering": "workspace:*", "@mendix/widget-plugin-grid": "workspace:*", diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts index bdf7464d68..411f28f843 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts @@ -74,21 +74,6 @@ export function getProperties( "hidable" ]); } - - if (!column.filterAssociation) { - hideNestedPropertiesIn(defaultProperties, values, "columns", index, [ - "filterAssociationOptions", - "filterAssociationOptionLabel", - "fetchOptionsLazy", - "filterCaptionType", - "filterAssociationOptionLabelAttr" - ]); - } - if (column.filterCaptionType === "attribute") { - hidePropertyIn(defaultProperties, values, "columns", index, "filterAssociationOptionLabel"); - } else { - hidePropertyIn(defaultProperties, values, "columns", index, "filterAssociationOptionLabelAttr"); - } }); if (values.pagination === "buttons") { hidePropertyIn(defaultProperties, values, "showNumberOfRows"); @@ -152,8 +137,6 @@ export function getProperties( "columnsHidable", "configurationAttribute", "onConfigurationChange", - "filterList", - "filtersPlaceholder", "filterSectionTitle" ]); } @@ -209,11 +192,6 @@ export const getPreview = ( draggable: false, dynamicText: "Dynamic text", filter: { widgetCount: 0, renderer: () => null }, - filterAssociation: "", - filterAssociationOptionLabel: "", - filterAssociationOptionLabelAttr: "", - filterAssociationOptions: {}, - filterCaptionType: "attribute", header: "Column", hidable: "no", resizable: false, @@ -227,8 +205,7 @@ export const getPreview = ( minWidth: "auto", minWidthLimit: 100, allowEventPropagation: true, - exportValue: "", - fetchOptionsLazy: true + exportValue: "" } ]; const columns = rowLayout({ diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx index 1bef235a15..ba1df3f098 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx @@ -35,11 +35,7 @@ const initColumns: ColumnsPreviewType[] = [ draggable: false, dynamicText: "Dynamic Text", filter: { renderer: () =>

, widgetCount: 0 }, - filterAssociation: "", - filterAssociationOptionLabel: "", - filterAssociationOptionLabelAttr: "", - filterAssociationOptions: {}, - filterCaptionType: "expression", + header: "Column", hidable: "no", resizable: false, @@ -53,8 +49,7 @@ const initColumns: ColumnsPreviewType[] = [ minWidth: "auto", minWidthLimit: 100, allowEventPropagation: true, - exportValue: "", - fetchOptionsLazy: true + exportValue: "" } ]; diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx index 62e6570404..78abad3963 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx @@ -82,7 +82,7 @@ const Container = observer((props: Props): ReactElement => { headerTitle={props.filterSectionTitle?.value} headerContent={ props.filtersPlaceholder && ( - + {props.filtersPlaceholder} ) diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml index b15ff7391c..8782abf1a6 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml @@ -121,44 +121,6 @@ - - - Entity - Set the entity to enable filtering over association with the Drop-down filter widget. - - - - - - - Selectable objects - The options to show in the Drop-down filter widget. - - - Use lazy load - Lazy loading enables faster data grid loading, but with personalization enabled, value restoration will be limited. - - - Option caption type - - - Attribute - Expression - - - - Option caption - - - - - Option caption - - - - - - Can sort @@ -316,6 +278,10 @@ On selection change + + Filters placeholder + + @@ -363,36 +329,6 @@ - - - - Filters - The list of attributes is used by the filter widgets that are placed in the placeholder above the data grid. This enables filtering across multiple attributes or columns instead of limiting to a single column. - - - - Filter attribute - - - - - - - - - - - - - - - - - Filters placeholder - - - - diff --git a/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx b/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx deleted file mode 100644 index 85d345545f..0000000000 --- a/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { performance } from "node:perf_hooks"; -import { dynamic, list, listAttribute, obj } from "@mendix/widget-plugin-test-utils"; -import "@testing-library/jest-dom"; -import { configure } from "@testing-library/dom"; -import { render } from "@testing-library/react"; -import { Big } from "big.js"; -import { createElement, Profiler, ReactElement } from "react"; -import { ColumnsType, DatagridContainerProps } from "../../typings/DatagridProps"; -import Datagrid from "../Datagrid"; -import { data } from "./snapshot"; -import { userEvent } from "@testing-library/user-event"; - -configure({ - getElementError: (message: string) => { - const error = new Error(message.slice(0, 2048)); - error.name = "TestingLibraryElementError"; - return error; - } -}); - -const withBig = data.map(row => row.map(value => (typeof value === "number" ? new Big(value) : value))); -const dataMap = new Map(withBig.map((rowData, index) => [obj(`${index + 1}`), rowData])); -const items = Array.from(dataMap.keys()); - -const mockColumn = (columnIndex: number): ColumnsType => { - const column: ColumnsType = { - showContentAs: "attribute", - attribute: listAttribute(obj => { - const row = dataMap.get(obj) ?? []; - return row[columnIndex] ?? "none"; - }), - header: dynamic(`Column ${columnIndex + 1}`), - visible: dynamic(true), - sortable: true, - resizable: true, - draggable: true, - hidable: "yes", - allowEventPropagation: true, - width: "autoFill", - minWidth: "auto", - minWidthLimit: 100, - size: 1, - alignment: "left", - wrapText: false, - fetchOptionsLazy: true, - filterCaptionType: "attribute" - }; - - return column; -}; - -describe("Datagrid", () => { - beforeEach(() => { - const mockIntersectionObserver = jest.fn(); - mockIntersectionObserver.mockReturnValue({ - observe: () => null, - unobserve: () => null, - disconnect: () => null, - takeRecords: () => [] - }); - window.IntersectionObserver = mockIntersectionObserver; - }); - it("should not take more then 9s to hide 3 column", async () => { - const props: DatagridContainerProps = { - advanced: false, - name: "datagrid", - datasource: list(items), - columns: [...Array(30).keys()].map(mockColumn), - columnsFilterable: true, - pageSize: items.length, - pagination: "buttons", - pagingPosition: "bottom", - itemSelectionMethod: "checkbox", - itemSelectionMode: "toggle", - class: "perf-test", - refreshInterval: 0, - showSelectAllToggle: false, - showPagingButtons: "always", - showEmptyPlaceholder: "none", - onClickTrigger: "single", - columnsSortable: true, - columnsHidable: true, - columnsResizable: true, - columnsDraggable: true, - filterList: [], - configurationStorageType: "attribute", - configurationAttribute: undefined, - loadingType: "spinner", - storeFiltersInPersonalization: true, - showNumberOfRows: false - }; - const user = userEvent.setup(); - let renderCount = 0; - const onRender = (): number => ++renderCount; - const WithProfiler = (): ReactElement => ( - - - - ); - const start = performance.now(); - const { getByRole, getAllByRole } = render(); - expect(getByRole("grid")).toBeVisible(); - // Checking that we have 100 rows with 31 (30 from pros + 1 for selector) columns. - expect(getAllByRole("gridcell")).toHaveLength(100 * 31); - const btn = getByRole("button", { name: "Column selector" }); - await user.click(btn); - const [col1, col2, col3] = getAllByRole("menuitem"); - await user.click(col1); - await user.click(col2); - await user.click(col3); - expect(renderCount).toBeGreaterThanOrEqual(5); - expect(renderCount).toBeLessThanOrEqual(10); - expect(getAllByRole("gridcell")).toHaveLength(100 * 28); - const end = performance.now(); - if (!process.env.CI) { - console.debug(`Test completed in ${end - start}ms`); - console.debug(`Render count ${renderCount}`); - } - // Why `20`s? I don't know. This is just enough time - // to pass this test with current datagrid implementation. - // As soon as this time will go down we can lower this value. - // As of now this test is mainly to catch performance regressions - // and actually see if any changes leading to performance improvements. - expect(end - start).toBeLessThan(20_000); - }); -}); diff --git a/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx b/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx deleted file mode 100644 index cde33fdd50..0000000000 --- a/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import classNames from "classnames"; -import { createElement, ReactElement, useState, useEffect, useRef } from "react"; - -/** - * StickySentinel - A small hidden element that uses "IntersectionObserver" - * to detect the "scrolled" state of the grid. By toggling the "container-stuck" class - * on this element, we can force "position: sticky" for column headers. - */ -export function StickySentinel(): ReactElement { - const sentinelRef = useRef(null); - const [ratio, setRatio] = useState(1); - - useEffect(() => { - const target = sentinelRef.current; - - if (target === null) { - return; - } - - return createObserver(target, setRatio); - }, []); - - return ( -
- ); -} - -function createObserver(target: Element, onIntersectionChange: (ratio: number) => void): () => void { - const options = { threshold: [0, 1] }; - - const observer = new IntersectionObserver(([entry]) => { - if (entry.intersectionRatio === 0 || entry.intersectionRatio === 1) { - onIntersectionChange(entry.intersectionRatio); - } - }, options); - - observer.observe(target); - - return () => observer.unobserve(target); -} diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx index 840cf55d2d..651c04806a 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx @@ -1,35 +1,27 @@ import { getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context"; -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; import { getGlobalSelectionContext, SelectionHelper, useCreateSelectionContextValue } from "@mendix/widget-plugin-grid/selection"; import { createElement, memo, ReactElement, ReactNode } from "react"; +import { RootGridStore } from "../helpers/state/RootGridStore"; interface WidgetHeaderContextProps { children?: ReactNode; - filtersStore: HeaderFiltersStore; selectionHelper?: SelectionHelper; + rootStore: RootGridStore; } const SelectionContext = getGlobalSelectionContext(); const FilterContext = getGlobalFilterContextObject(); -function FilterAPIProvider(props: { filtersStore: HeaderFiltersStore; children?: ReactNode }): ReactElement { - return {props.children}; -} - -function SelectionStatusProvider(props: { selectionHelper?: SelectionHelper; children?: ReactNode }): ReactElement { - const value = useCreateSelectionContextValue(props.selectionHelper); - return {props.children}; -} - function HeaderContainer(props: WidgetHeaderContextProps): ReactElement { + const selectionContext = useCreateSelectionContextValue(props.selectionHelper); return ( - - {props.children} - + + {props.children} + ); } diff --git a/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts b/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts index bd6a615c83..de02c76a80 100644 --- a/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts +++ b/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts @@ -4,13 +4,7 @@ import { ColumnsPreviewType, DatagridPreviewProps } from "../typings/DatagridPro export function check(values: DatagridPreviewProps): Problem[] { const errors: Problem[] = []; - const columnChecks = [ - checkAssociationSettings, - checkFilteringSettings, - checkDisplaySettings, - checkSortingSettings, - checkHidableSettings - ]; + const columnChecks = [checkDisplaySettings, checkSortingSettings, checkHidableSettings]; values.columns.forEach((column: ColumnsPreviewType, index) => { for (const check of columnChecks) { @@ -28,51 +22,6 @@ export function check(values: DatagridPreviewProps): Problem[] { const columnPropPath = (prop: string, index: number): string => `columns/${index + 1}/${prop}`; -const checkAssociationSettings = ( - values: DatagridPreviewProps, - column: ColumnsPreviewType, - index: number -): Problem | undefined => { - if (!values.columnsFilterable) { - return; - } - - if (!column.filterAssociation) { - return; - } - - if (column.filterCaptionType === "expression" && !column.filterAssociationOptionLabel) { - return { - property: columnPropPath("filterAssociationOptionLabel", index), - message: `A caption is required when using associations. Please set 'Option caption' property for column (${column.header})` - }; - } - - if (column.filterCaptionType === "attribute" && !column.filterAssociationOptionLabelAttr) { - return { - property: columnPropPath("filterAssociationOptionLabelAttr", index), - message: `A caption is required when using associations. Please set 'Option caption' property for column (${column.header})` - }; - } -}; - -const checkFilteringSettings = ( - values: DatagridPreviewProps, - column: ColumnsPreviewType, - index: number -): Problem | undefined => { - if (!values.columnsFilterable) { - return; - } - - if (!column.attribute && !column.filterAssociation) { - return { - property: columnPropPath("attribute", index), - message: `An attribute or reference is required when filtering is enabled. Please select 'Attribute' or 'Reference' property for column (${column.header})` - }; - } -}; - const checkDisplaySettings = ( _values: DatagridPreviewProps, column: ColumnsPreviewType, diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts similarity index 69% rename from packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts rename to packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts index 397c73e3c6..d99e7f2d06 100644 --- a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts +++ b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts @@ -1,45 +1,45 @@ -import { compactArray, fromCompactArray, isAnd } from "@mendix/widget-plugin-filtering/condition-utils"; +import { compactArray, fromCompactArray, isAnd } from "@mendix/filter-commons/condition-utils"; +import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller"; import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; import { FilterCondition } from "mendix/filters"; import { and } from "mendix/filters/builders"; import { makeAutoObservable, reaction } from "mobx"; import { SortInstruction } from "../typings/sorting"; -import { QueryController } from "./query-controller"; interface Columns { conditions: Array; sortInstructions: SortInstruction[] | undefined; } -interface Header { +interface FiltersInput { conditions: Array; } -type StateSyncControllerSpec = { +type DatasourceParamsControllerSpec = { query: QueryController; columns: Columns; - header: Header; + customFilters: FiltersInput; }; -export class StateSyncController implements ReactiveController { +export class DatasourceParamsController implements ReactiveController { private columns: Columns; - private header: Header; private query: QueryController; + private customFilters: FiltersInput; - constructor(host: ReactiveControllerHost, spec: StateSyncControllerSpec) { + constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) { host.addController(this); this.columns = spec.columns; - this.header = spec.header; this.query = spec.query; + this.customFilters = spec.customFilters; makeAutoObservable(this, { setup: false }); } private get derivedFilter(): FilterCondition | undefined { - const { columns, header } = this; + const { columns, customFilters } = this; - return and(compactArray(columns.conditions), compactArray(header.conditions)); + return and(compactArray(columns.conditions), compactArray(customFilters.conditions)); } private get derivedSortOrder(): SortInstruction[] | undefined { @@ -68,7 +68,7 @@ export class StateSyncController implements ReactiveController { static unzipFilter( filter?: FilterCondition - ): [columns: Array, header: Array] { + ): [columns: Array, sharedFilter: Array] { if (!filter) { return [[], []]; } @@ -78,7 +78,8 @@ export class StateSyncController implements ReactiveController { if (filter.args.length !== 2) { return [[], []]; } - const [columns, header] = filter.args; - return [fromCompactArray(columns), fromCompactArray(header)]; + + const [columns, shared] = filter.args; + return [fromCompactArray(columns), fromCompactArray(shared)]; } } diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts index 28a966e029..454c47a674 100644 --- a/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts +++ b/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts @@ -1,7 +1,7 @@ +import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller"; import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; import { PaginationEnum, ShowPagingButtonsEnum } from "../../typings/DatagridProps"; -import { QueryController } from "./query-controller"; type Gate = DerivedPropsGate<{ pageSize: number; diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts index efc8dd67ad..b3660c0767 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts @@ -1,5 +1,5 @@ -import { disposeFx } from "@mendix/widget-plugin-filtering/mobx-utils"; -import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings"; +import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { FilterCondition } from "mendix/filters"; import { action, computed, makeObservable, observable } from "mobx"; import { DatagridContainerProps } from "../../../typings/DatagridProps"; @@ -13,7 +13,7 @@ import { sortInstructionsToSortRules, sortRulesToSortInstructions } from "./ColumnsSortingStore"; -import { ColumnFilterStore } from "./column/ColumnFilterStore"; +import { ColumnFilterStore, ObserverBag } from "./column/ColumnFilterStore"; import { ColumnStore } from "./column/ColumnStore"; export interface IColumnGroupStore { @@ -46,17 +46,18 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { constructor( props: Pick, info: StaticInfo, - dsViewState: Array | null + initFilter: Array, + observerBag: ObserverBag ) { this._allColumns = []; this.columnFilters = []; props.columns.forEach((columnProps, i) => { - const initCond = dsViewState?.at(i) ?? null; + const initCond = initFilter.at(i) ?? null; const column = new ColumnStore(i, columnProps, this); this._allColumnsById.set(column.columnId, column); this._allColumns[i] = column; - this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond); + this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond, observerBag); }); this.sorting = new ColumnsSortingStore( @@ -82,9 +83,9 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { } setup(): () => void { - const [disposers, dispose] = disposeFx(); + const [add, dispose] = disposeBatch(); for (const filter of this.columnFilters) { - disposers.push(filter.setup()); + add(filter.setup()); } return dispose; } @@ -92,7 +93,6 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { updateProps(props: Pick): void { props.columns.forEach((columnProps, i) => { this._allColumns[i].updateProps(columnProps); - this.columnFilters[i].updateProps(columnProps); }); if (this.visibleColumns.length < 1) { @@ -144,7 +144,7 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { get conditions(): Array { return this.columnFilters.map((store, index) => { - return this._allColumns[index].isHidden ? undefined : store.condition2; + return this._allColumns[index].isHidden ? undefined : store.condition; }); } diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts index 21c8024aca..801798b61e 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts @@ -1,6 +1,6 @@ +import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta"; -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; -import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx"; import { DatagridContainerProps } from "../../../typings/DatagridProps"; import { ColumnId } from "../../typings/GridColumn"; @@ -17,7 +17,7 @@ import { ColumnGroupStore } from "./ColumnGroupStore"; export class GridPersonalizationStore { private readonly gridName: string; private readonly gridColumnsHash: string; - private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 2; + private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 3; private readonly storeFilters: boolean; private storage: PersonalizationStorage; @@ -27,7 +27,7 @@ export class GridPersonalizationStore { constructor( props: DatagridContainerProps, private columnsStore: ColumnGroupStore, - private headerFilters: HeaderFiltersStore + private customFilters: ObservableFilterHost ) { this.gridName = props.name; this.gridColumnsHash = getHash(this.columnsStore._allColumns, this.gridName); @@ -35,7 +35,6 @@ export class GridPersonalizationStore { makeObservable(this, { settings: computed, - applySettings: action }); @@ -95,6 +94,7 @@ export class GridPersonalizationStore { private applySettings(settings: GridPersonalizationStorageSettings): void { this.columnsStore.setColumnSettings(toColumnSettings(settings)); this.columnsStore.setColumnFilterSettings(settings.columnFilters); + this.customFilters.settings = new Map(settings.customFilters); } private readSettings( @@ -137,7 +137,7 @@ export class GridPersonalizationStore { this.gridColumnsHash, this.columnsStore.columnSettings, this.storeFilters ? this.columnsStore.filterSettings : new Map(), - this.storeFilters ? this.headerFilters.settings : new Map() + this.storeFilters ? this.customFilters.settings : new Map() ); } } @@ -164,7 +164,7 @@ function toStorageFormat( gridColumnsHash: string, columnsSettings: ColumnPersonalizationSettings[], columnFilters: FiltersSettingsMap, - groupFilters: FiltersSettingsMap + customFilters: FiltersSettingsMap ): GridPersonalizationStorageSettings { const sortOrder = columnsSettings .filter(c => c.sortDir && c.sortWeight !== undefined) @@ -175,7 +175,7 @@ function toStorageFormat( return { name: gridName, - schemaVersion: 2, + schemaVersion: 3, settingsHash: gridColumnsHash, columns: columnsSettings.map(c => ({ columnId: c.columnId, @@ -185,7 +185,7 @@ function toStorageFormat( })), columnFilters: Array.from(columnFilters), - groupFilters: Array.from(groupFilters), + customFilters: Array.from(customFilters), sortOrder, columnOrder diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts index a98cbf7eff..cc7e718a08 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts @@ -1,15 +1,16 @@ -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; +import { createContextWithStub, FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost"; +import { DatasourceController } from "@mendix/widget-plugin-grid/query/DatasourceController"; +import { RefreshController } from "@mendix/widget-plugin-grid/query/RefreshController"; import { BaseControllerHost } from "@mendix/widget-plugin-mobx-kit/BaseControllerHost"; import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; -import { autorun, computed } from "mobx"; +import { autorun } from "mobx"; import { DatagridContainerProps } from "../../../typings/DatagridProps"; -import { DatasourceController } from "../../controllers/DatasourceController"; +import { DatasourceParamsController } from "../../controllers/DatasourceParamsController"; import { DerivedLoaderController } from "../../controllers/DerivedLoaderController"; import { PaginationController } from "../../controllers/PaginationController"; -import { RefreshController } from "../../controllers/RefreshController"; -import { StateSyncController } from "../../controllers/StateSyncController"; import { ProgressStore } from "../../features/data-export/ProgressStore"; import { StaticInfo } from "../../typings/static-info"; import { ColumnGroupStore } from "./ColumnGroupStore"; @@ -24,12 +25,12 @@ type Spec = { export class RootGridStore extends BaseControllerHost { columnsStore: ColumnGroupStore; - headerFiltersStore: HeaderFiltersStore; settingsStore: GridPersonalizationStore; staticInfo: StaticInfo; exportProgressCtrl: ProgressStore; loaderCtrl: DerivedLoaderController; paginationCtrl: PaginationController; + readonly autonomousFilterAPI: FilterAPI; private gate: Gate; @@ -37,28 +38,36 @@ export class RootGridStore extends BaseControllerHost { super(); const { props } = gate; - const [columnsViewState, headerViewState] = StateSyncController.unzipFilter(props.datasource.filter); + const [columnsInitFilter, sharedInitFilter] = DatasourceParamsController.unzipFilter(props.datasource.filter); this.gate = gate; this.staticInfo = { name: props.name, filtersChannelName: `datagrid/${generateUUID()}` }; + const customFilterHost = new CustomFilterHost(); const query = new DatasourceController(this, { gate }); - const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsViewState)); - const header = (this.headerFiltersStore = new HeaderFiltersStore(props, this.staticInfo, headerViewState)); - this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, this.headerFiltersStore); + this.autonomousFilterAPI = createContextWithStub({ + filterObserver: customFilterHost, + sharedInitFilter, + parentChannelName: this.staticInfo.filtersChannelName + }); + const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsInitFilter, { + customFilterHost, + sharedInitFilter + })); + this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, customFilterHost); this.paginationCtrl = new PaginationController(this, { gate, query }); this.exportProgressCtrl = exportCtrl; - new StateSyncController(this, { + new DatasourceParamsController(this, { query, columns, - header + customFilters: customFilterHost }); new RefreshController(this, { - query: computed(() => query.computedCopy), + query: query.derivedQuery, delay: props.refreshInterval * 1000 }); @@ -73,7 +82,6 @@ export class RootGridStore extends BaseControllerHost { const [add, disposeAll] = disposeBatch(); add(super.setup()); add(this.columnsStore.setup()); - add(this.headerFiltersStore.setup() ?? (() => {})); add(() => this.settingsStore.dispose()); add(autorun(() => this.updateProps(this.gate.props))); diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx index 9d0d9ae7cb..e4ada03983 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx @@ -1,90 +1,51 @@ -import { FilterAPIv2, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context"; -import { RefFilterStore, RefFilterStoreProps } from "@mendix/widget-plugin-filtering/stores/picker/RefFilterStore"; -import { StaticSelectFilterStore } from "@mendix/widget-plugin-filtering/stores/picker/StaticSelectFilterStore"; +import { FilterData } from "@mendix/filter-commons/typings/settings"; +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; +import { FilterAPI, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context"; +import { value } from "@mendix/widget-plugin-filtering/result-meta"; import { InputFilterStore, attrgroupFilterStore } from "@mendix/widget-plugin-filtering/stores/input/store-utils"; -import { ensure } from "@mendix/widget-plugin-platform/utils/ensure"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { ListAttributeListValue, ListAttributeValue } from "mendix"; import { FilterCondition } from "mendix/filters"; -import { ListAttributeValue, ListAttributeListValue } from "mendix"; -import { action, computed, makeObservable } from "mobx"; +import { computed, makeObservable } from "mobx"; import { ReactNode, createElement } from "react"; import { ColumnsType } from "../../../../typings/DatagridProps"; import { StaticInfo } from "../../../typings/static-info"; -import { FilterData } from "@mendix/widget-plugin-filtering/typings/settings"; -import { value } from "@mendix/widget-plugin-filtering/result-meta"; -import { disposeFx } from "@mendix/widget-plugin-filtering/mobx-utils"; + export interface IColumnFilterStore { renderFilterWidgets(): ReactNode; } -type FilterStore = InputFilterStore | StaticSelectFilterStore | RefFilterStore; +type FilterStore = InputFilterStore | EnumFilterStore; const { Provider } = getGlobalFilterContextObject(); export class ColumnFilterStore implements IColumnFilterStore { private _widget: ReactNode; private _filterStore: FilterStore | null = null; - private _context: FilterAPIv2; + private _context: FilterAPI; + private _observerBag: ObserverBag; - constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null) { + constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null, observerBag: ObserverBag) { + this._observerBag = observerBag; this._widget = props.filter; this._filterStore = this.createFilterStore(props, dsViewState); this._context = this.createContext(this._filterStore, info); - makeObservable(this, { - _updateStore: action, - condition2: computed, - updateProps: action + makeObservable(this, { + condition: computed }); } setup(): () => void { - const [disposers, dispose] = disposeFx(); + const [add, disposeAll] = disposeBatch(); if (this._filterStore && "setup" in this._filterStore) { - disposers.push(this._filterStore.setup()); - } - return dispose; - } - - updateProps(props: ColumnsType): void { - this._widget = props.filter; - this._updateStore(props); - } - - private _updateStore(props: ColumnsType): void { - const store = this._filterStore; - - if (store === null) { - return; + add(this._filterStore.setup()); } - - if (store.storeType === "refselect") { - store.updateProps(this.toRefselectProps(props)); - } else if (isListAttributeValue(props.attribute)) { - store.updateProps([props.attribute]); - } - } - - private toRefselectProps(props: ColumnsType): RefFilterStoreProps { - const searchAttrId = props.filterAssociationOptionLabelAttr?.id; - const caption = - props.filterCaptionType === "expression" - ? ensure(props.filterAssociationOptionLabel, errorMessage("filterAssociationOptionLabel")) - : ensure(props.filterAssociationOptionLabelAttr, errorMessage("filterAssociationOptionLabelAttr")); - - return { - ref: ensure(props.filterAssociation, errorMessage("filterAssociation")), - datasource: ensure(props.filterAssociationOptions, errorMessage("filterAssociationOptions")), - searchAttrId, - fetchOptionsLazy: props.fetchOptionsLazy, - caption - }; + return disposeAll; } private createFilterStore(props: ColumnsType, dsViewState: FilterCondition | null): FilterStore | null { - if (props.filterAssociation) { - return new RefFilterStore(this.toRefselectProps(props), dsViewState); - } - if (isListAttributeValue(props.attribute)) { return attrgroupFilterStore(props.attribute.type, [props.attribute], dsViewState); } @@ -92,14 +53,16 @@ export class ColumnFilterStore implements IColumnFilterStore { return null; } - private createContext(store: FilterStore | null, info: StaticInfo): FilterAPIv2 { + private createContext(store: FilterStore | null, info: StaticInfo): FilterAPI { return { - version: 2, + version: 3, parentChannelName: info.filtersChannelName, provider: value({ type: "direct", store - }) + }), + filterObserver: this._observerBag.customFilterHost, + sharedInitFilter: this._observerBag.sharedInitFilter }; } @@ -107,7 +70,7 @@ export class ColumnFilterStore implements IColumnFilterStore { return {this._widget}; } - get condition2(): FilterCondition | undefined { + get condition(): FilterCondition | undefined { return this._filterStore ? this._filterStore.condition : undefined; } @@ -130,5 +93,7 @@ const isListAttributeValue = ( return !!(attribute && attribute.isList === false); }; -const errorMessage = (propName: string): string => - `Can't map ColumnsType to AssociationProperties: ${propName} is undefined`; +export interface ObserverBag { + customFilterHost: ObservableFilterHost; + sharedInitFilter: Array; +} diff --git a/packages/pluggableWidgets/datagrid-web/src/package.xml b/packages/pluggableWidgets/datagrid-web/src/package.xml index b2c27bcd1e..8bf5807932 100644 --- a/packages/pluggableWidgets/datagrid-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts index f51a4f9b4e..663dcb3b32 100644 --- a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts +++ b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts @@ -1,4 +1,4 @@ -import { FilterData } from "@mendix/widget-plugin-filtering/typings/settings"; +import { FilterData } from "@mendix/filter-commons/typings/settings"; import { ColumnId } from "./GridColumn"; import { SortDirection, SortRule } from "./sorting"; @@ -19,14 +19,14 @@ interface ColumnPersonalizationStorageSettings { export type ColumnFilterSettings = Array<[key: ColumnId, data: FilterData]>; -export type GroupFilterSettings = Array<[key: string, data: FilterData]>; +export type CustomFilterSettings = Array<[key: string, data: FilterData]>; export interface GridPersonalizationStorageSettings { name: string; - schemaVersion: 2; + schemaVersion: 3; settingsHash: string; columns: ColumnPersonalizationStorageSettings[]; - groupFilters: GroupFilterSettings; + customFilters: CustomFilterSettings; columnFilters: ColumnFilterSettings; columnOrder: ColumnId[]; sortOrder: SortRule[]; diff --git a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx index 3a9a345e20..9a347d20aa 100644 --- a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx @@ -29,9 +29,7 @@ export const column = (header = "Test", patch?: (col: ColumnsType) => void): Col visible: dynamicValue(true), minWidth: "auto", minWidthLimit: 100, - allowEventPropagation: true, - fetchOptionsLazy: true, - filterCaptionType: "attribute" + allowEventPropagation: true }; if (patch) { diff --git a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts index 1935eef553..edbce23cfd 100644 --- a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts +++ b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts @@ -4,7 +4,7 @@ * @author Mendix Widgets Framework Team */ import { ComponentType, CSSProperties, ReactNode } from "react"; -import { ActionValue, DynamicValue, EditableValue, ListValue, ListActionValue, ListAttributeValue, ListAttributeListValue, ListExpressionValue, ListReferenceValue, ListReferenceSetValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; +import { ActionValue, DynamicValue, EditableValue, ListValue, ListActionValue, ListAttributeValue, ListAttributeListValue, ListExpressionValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; import { Big } from "big.js"; export type ItemSelectionMethodEnum = "checkbox" | "rowClick"; @@ -15,8 +15,6 @@ export type LoadingTypeEnum = "spinner" | "skeleton"; export type ShowContentAsEnum = "attribute" | "dynamicText" | "customContent"; -export type FilterCaptionTypeEnum = "attribute" | "expression"; - export type HidableEnum = "yes" | "hidden" | "no"; export type WidthEnum = "autoFill" | "autoFit" | "manual"; @@ -35,12 +33,6 @@ export interface ColumnsType { tooltip?: ListExpressionValue; filter?: ReactNode; visible: DynamicValue; - filterAssociation?: ListReferenceValue | ListReferenceSetValue; - filterAssociationOptions?: ListValue; - fetchOptionsLazy: boolean; - filterCaptionType: FilterCaptionTypeEnum; - filterAssociationOptionLabel?: ListExpressionValue; - filterAssociationOptionLabelAttr?: ListAttributeValue; sortable: boolean; resizable: boolean; draggable: boolean; @@ -67,10 +59,6 @@ export type OnClickTriggerEnum = "single" | "double"; export type ConfigurationStorageTypeEnum = "attribute" | "localStorage"; -export interface FilterListType { - filter: ListAttributeValue; -} - export interface ColumnsPreviewType { showContentAs: ShowContentAsEnum; attribute: string; @@ -81,12 +69,6 @@ export interface ColumnsPreviewType { tooltip: string; filter: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; visible: string; - filterAssociation: string; - filterAssociationOptions: {} | { caption: string } | { type: string } | null; - fetchOptionsLazy: boolean; - filterCaptionType: FilterCaptionTypeEnum; - filterAssociationOptionLabel: string; - filterAssociationOptionLabelAttr: string; sortable: boolean; resizable: boolean; draggable: boolean; @@ -101,10 +83,6 @@ export interface ColumnsPreviewType { wrapText: boolean; } -export interface FilterListPreviewType { - filter: string; -} - export interface DatagridContainerProps { name: string; class: string; @@ -132,6 +110,7 @@ export interface DatagridContainerProps { onClickTrigger: OnClickTriggerEnum; onClick?: ListActionValue; onSelectionChange?: ActionValue; + filtersPlaceholder?: ReactNode; columnsSortable: boolean; columnsResizable: boolean; columnsDraggable: boolean; @@ -139,8 +118,6 @@ export interface DatagridContainerProps { configurationStorageType: ConfigurationStorageTypeEnum; configurationAttribute?: EditableValue; storeFiltersInPersonalization: boolean; - filterList: FilterListType[]; - filtersPlaceholder?: ReactNode; filterSectionTitle?: DynamicValue; exportDialogLabel?: DynamicValue; cancelExportLabel?: DynamicValue; @@ -181,6 +158,7 @@ export interface DatagridPreviewProps { onClickTrigger: OnClickTriggerEnum; onClick: {} | null; onSelectionChange: {} | null; + filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; columnsSortable: boolean; columnsResizable: boolean; columnsDraggable: boolean; @@ -189,8 +167,6 @@ export interface DatagridPreviewProps { configurationAttribute: string; storeFiltersInPersonalization: boolean; onConfigurationChange: {} | null; - filterList: FilterListPreviewType[]; - filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; filterSectionTitle: string; exportDialogLabel: string; cancelExportLabel: string; diff --git a/packages/pluggableWidgets/document-viewer-web/package.json b/packages/pluggableWidgets/document-viewer-web/package.json index 23b2732bb8..92209a7e8f 100644 --- a/packages/pluggableWidgets/document-viewer-web/package.json +++ b/packages/pluggableWidgets/document-viewer-web/package.json @@ -35,6 +35,8 @@ "test": "echo 'FIXME: Add unit tests'" }, "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*", "classnames": "^2.3.2", "docx-preview": "^0.3.5", "pdfjs-dist": "^5.0.375", diff --git a/packages/pluggableWidgets/dropdown-sort-web/jest.config.cjs b/packages/pluggableWidgets/dropdown-sort-web/jest.config.cjs new file mode 100644 index 0000000000..22d837ab37 --- /dev/null +++ b/packages/pluggableWidgets/dropdown-sort-web/jest.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + ...require("@mendix/pluggable-widgets-tools/test-config/jest.enzyme-free.config.js"), + /** + * `nanoevents` package is ESM module and because ESM is not supported by Jest yet + * we mark `nanoevents` as a module that should be transformed by ts-jest. + */ + transformIgnorePatterns: ["node_modules/(?!nanoevents)/"] +}; diff --git a/packages/pluggableWidgets/dropdown-sort-web/package.json b/packages/pluggableWidgets/dropdown-sort-web/package.json index 02a0b022b8..d35973294b 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/package.json +++ b/packages/pluggableWidgets/dropdown-sort-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/dropdown-sort-web", "widgetName": "DropdownSort", - "version": "1.2.2", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "dropdown-sort-web/main" + "branchName": "dropdown-sort-web/data-widgets-3.0" }, "scripts": { "build": "pluggable-widgets-tools build:ts", @@ -36,7 +36,7 @@ "lint": "eslint src/ package.json", "release": "pluggable-widgets-tools release:ts", "start": "pluggable-widgets-tools start:server", - "test": "pluggable-widgets-tools test:unit:web", + "test": "jest", "update-changelog": "rui-update-changelog-widget", "verify": "rui-verify-package-format" }, @@ -55,6 +55,7 @@ "@mendix/widget-plugin-sorting": "workspace:*", "@mendix/widget-plugin-test-utils": "workspace:*", "@types/enzyme": "^3.10.13", - "classnames": "^2.3.2" + "classnames": "^2.3.2", + "jest": "^29.7.0" } } diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.editorPreview.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.editorPreview.tsx index e2ed2b254a..b5c6aed298 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.editorPreview.tsx +++ b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.editorPreview.tsx @@ -1,11 +1,14 @@ +import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style"; +import { withSortAPI } from "@mendix/widget-plugin-sorting/react/hocs/withSortAPI"; import { createElement, ReactElement } from "react"; -import { SortComponent } from "./components/SortComponent"; import { DropdownSortPreviewProps } from "../typings/DropdownSortProps"; -import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style"; +import { SortComponent } from "./components/SortComponent"; + +const DropdownPreview = withSortAPI(SortComponent); export function preview(props: DropdownSortPreviewProps): ReactElement { return ( - ().current ??= `DropdownSort${generateUUID()}`); - const sortProps = useSortControl( - { ...props, emptyOptionCaption: props.emptyOptionCaption?.value }, - props.sortStore - ); +function Container(props: DropdownSortContainerProps & { sortStore: BasicSortStore }): ReactElement { + const id = useConst(() => `DropdownSort${generateUUID()}`); + + const sortProps = useSortSelect({ + emptyOptionCaption: props.emptyOptionCaption?.value, + sortStore: props.sortStore + }); return ( ; -} +export const DropdownSort = withSortAPI(withLinkedSortStore(observer(Container))); diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml index 09af998512..9dbb977d8a 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml +++ b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml @@ -8,6 +8,36 @@ + + Datasource to sort + + + + Attributes + Select the attributes that the end-user may use for sorting + + + + Attribute + + + + + + + + + + + + + + Caption + + + + + Empty option caption diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx index 723c659a9c..e6691c0167 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx +++ b/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx @@ -4,23 +4,21 @@ import classNames from "classnames"; import { createElement, CSSProperties, ReactElement, useCallback, useRef, useState } from "react"; import { createPortal } from "react-dom"; -export interface SortOption { - caption: string; - value: string | null; -} - interface SortComponentProps { className?: string; placeholder?: string; id?: string; - options: SortOption[]; + options: Array<{ + caption: string; + value: string; + }>; value: string | null; direction: Dir; tabIndex?: number; screenReaderButtonCaption?: string; screenReaderInputCaption?: string; styles?: CSSProperties; - onSelect?: (option: SortOption) => void; + onSelect?: (value: string) => void; onDirectionClick?: () => void; } @@ -33,8 +31,8 @@ export function SortComponent(props: SortComponentProps): ReactElement { const position = usePositionObserver(componentRef.current, show); const onClick = useCallback( - (option: SortOption) => { - onSelect?.(option); + (option: { value: string }) => { + onSelect?.(option.value); setShow(false); }, [onSelect] @@ -92,7 +90,9 @@ export function SortComponent(props: SortComponentProps): ReactElement { const containerClick = useCallback(() => { setShow(show => !show); setTimeout(() => { - (optionsRef.current?.querySelector("li.filter-selected") as HTMLElement)?.focus(); + const selectedElement = optionsRef.current?.querySelector("li.filter-selected") as HTMLElement; + const firstElement = optionsRef.current?.querySelector("li") as HTMLElement; + (selectedElement || firstElement)?.focus(); }, 10); }, []); @@ -125,6 +125,7 @@ export function SortComponent(props: SortComponentProps): ReactElement { aria-expanded={show} aria-controls={`${props.id}-dropdown-list`} aria-label={props.screenReaderInputCaption} + onChange={() => {}} /> diff --git a/packages/shared/widget-plugin-filtering/src/controls/tag-picker/TagPicker.tsx b/packages/shared/widget-plugin-dropdown-filter/src/controls/tag-picker/TagPicker.tsx similarity index 100% rename from packages/shared/widget-plugin-filtering/src/controls/tag-picker/TagPicker.tsx rename to packages/shared/widget-plugin-dropdown-filter/src/controls/tag-picker/TagPicker.tsx diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hooks/useFrontendType.ts b/packages/shared/widget-plugin-dropdown-filter/src/helpers/useFrontendType.ts similarity index 100% rename from packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hooks/useFrontendType.ts rename to packages/shared/widget-plugin-dropdown-filter/src/helpers/useFrontendType.ts diff --git a/packages/shared/widget-plugin-filtering/src/helpers/usePickerJSActions.ts b/packages/shared/widget-plugin-dropdown-filter/src/helpers/usePickerJSActions.ts similarity index 85% rename from packages/shared/widget-plugin-filtering/src/helpers/usePickerJSActions.ts rename to packages/shared/widget-plugin-dropdown-filter/src/helpers/usePickerJSActions.ts index ee7a00dc1a..6c76d13a76 100644 --- a/packages/shared/widget-plugin-filtering/src/helpers/usePickerJSActions.ts +++ b/packages/shared/widget-plugin-dropdown-filter/src/helpers/usePickerJSActions.ts @@ -1,5 +1,5 @@ +import { IJSActionsControlled } from "@mendix/filter-commons/typings/IJSActionsControlled"; import { useOnResetValueEvent, useOnSetValueEvent } from "@mendix/widget-plugin-external-events/hooks"; -import { IJSActionsControlled } from "../typings/IJSActionsControlled"; export function usePickerJSActions( controller: IJSActionsControlled, diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withCustomOptionsGuard.tsx b/packages/shared/widget-plugin-dropdown-filter/src/hocs/withCustomOptionsGuard.tsx similarity index 74% rename from packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withCustomOptionsGuard.tsx rename to packages/shared/widget-plugin-dropdown-filter/src/hocs/withCustomOptionsGuard.tsx index 844527f811..221ddf56ee 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withCustomOptionsGuard.tsx +++ b/packages/shared/widget-plugin-dropdown-filter/src/hocs/withCustomOptionsGuard.tsx @@ -1,10 +1,10 @@ import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { useState, createElement } from "react"; -import { FilterOptionsType } from "../../typings/DatagridDropdownFilterProps"; -import { StaticSelectFilterStore } from "@mendix/widget-plugin-filtering/stores/picker/StaticSelectFilterStore"; +import { createElement, useState } from "react"; +import { EnumFilterStore } from "../stores/EnumFilterStore"; +import { FilterOptionsType } from "../typings/widget"; interface Props { - filterStore: StaticSelectFilterStore; + filterStore: EnumFilterStore; filterOptions: FilterOptionsType[]; } diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts b/packages/shared/widget-plugin-dropdown-filter/src/stores/BaseSelectStore.ts similarity index 65% rename from packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts rename to packages/shared/widget-plugin-dropdown-filter/src/stores/BaseSelectStore.ts index d31e08f06e..7ae086f7fc 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts +++ b/packages/shared/widget-plugin-dropdown-filter/src/stores/BaseSelectStore.ts @@ -1,6 +1,5 @@ +import { FilterData, InputData } from "@mendix/filter-commons/typings/settings"; import { action, makeObservable, observable } from "mobx"; -import { FilterData } from "../../typings/settings"; -import { isInputData } from "../utils/is-input-data"; export class BaseSelectStore { protected defaultSelected: Iterable = []; @@ -40,10 +39,33 @@ export class BaseSelectStore { } fromJSON(json: FilterData): void { - if (json === null || isInputData(json)) { + if (json === undefined || json === null || isInputData(json)) { return; } this.setSelected(json); this.blockSetDefaults = true; } } + +const fnNames = new Set([ + "empty", + "notEmpty", + "equal", + "notEqual", + "greater", + "greaterEqual", + "smaller", + "smallerEqual", + "between", + "contains", + "startsWith", + "endsWith" +]); + +export function isInputData(data: unknown): data is InputData { + if (Array.isArray(data)) { + const [name] = data; + return fnNames.has(name); + } + return false; +} diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/StaticSelectFilterStore.ts b/packages/shared/widget-plugin-dropdown-filter/src/stores/EnumFilterStore.ts similarity index 71% rename from packages/shared/widget-plugin-filtering/src/stores/picker/StaticSelectFilterStore.ts rename to packages/shared/widget-plugin-dropdown-filter/src/stores/EnumFilterStore.ts index 97b27353b9..f6e2e93edf 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/picker/StaticSelectFilterStore.ts +++ b/packages/shared/widget-plugin-dropdown-filter/src/stores/EnumFilterStore.ts @@ -1,10 +1,10 @@ -import { ListAttributeValue } from "mendix"; +import { selectedFromCond } from "@mendix/filter-commons/condition-utils"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { AttributeMetaData } from "mendix"; import { FilterCondition, LiteralExpression } from "mendix/filters"; import { attribute, equals, literal, or } from "mendix/filters/builders"; import { action, computed, makeObservable, observable } from "mobx"; -import { selectedFromCond } from "../../condition-utils"; -import { disposeFx } from "../../mobx-utils"; -import { OptionWithState } from "../../typings/OptionWithState"; +import { OptionWithState } from "../typings/OptionWithState"; import { BaseSelectStore } from "./BaseSelectStore"; import { SearchStore } from "./SearchStore"; @@ -13,13 +13,13 @@ interface CustomOption { value: string; } -export class StaticSelectFilterStore extends BaseSelectStore { +export class EnumFilterStore extends BaseSelectStore { readonly storeType = "select"; - _attributes: ListAttributeValue[] = []; + _attributes: AttributeMetaData[] = []; _customOptions: CustomOption[] = []; search: SearchStore; - constructor(attributes: ListAttributeValue[], initCond: FilterCondition | null) { + constructor(attributes: AttributeMetaData[], initCond: FilterCondition | null) { super(); this.search = new SearchStore(); this._attributes = attributes; @@ -34,7 +34,6 @@ export class StaticSelectFilterStore extends BaseSelectStore { condition: computed, setCustomOptions: action, setDefaultSelected: action, - updateProps: action, fromViewState: action }); @@ -47,14 +46,17 @@ export class StaticSelectFilterStore extends BaseSelectStore { const selected = this.selected; if (this._customOptions.length > 0) { - return this._customOptions.map(opt => ({ ...opt, selected: selected.has(opt.value) })); + return this._customOptions.map(opt => ({ + ...opt, + selected: selected.has(opt.value) + })); } const options = this._attributes.flatMap(attr => Array.from(attr.universe ?? [], value => { const stringValue = `${value}`; return { - caption: attr.formatter.format(value), + caption: attr.formatter ? attr.formatter.format(value) : stringValue, value: stringValue, selected: selected.has(stringValue) }; @@ -93,8 +95,8 @@ export class StaticSelectFilterStore extends BaseSelectStore { } setup(): () => void { - const [disposers, dispose] = disposeFx(); - disposers.push(this.search.setup()); + const [add, dispose] = disposeBatch(); + add(this.search.setup()); return dispose; } @@ -110,12 +112,8 @@ export class StaticSelectFilterStore extends BaseSelectStore { } } - updateProps(attributes: ListAttributeValue[]): void { - this._attributes = attributes; - } - checkAttrs(): TypeError | null { - const isValidAttr = (attr: ListAttributeValue): boolean => /Enum|Boolean/.test(attr.type); + const isValidAttr = (attr: AttributeMetaData): boolean => /Enum|Boolean/.test(attr.type); if (this._attributes.every(isValidAttr)) { return null; @@ -150,33 +148,26 @@ export class StaticSelectFilterStore extends BaseSelectStore { } function getFilterCondition( - listAttribute: ListAttributeValue | undefined, + listAttribute: AttributeMetaData | undefined, selected: Set ): FilterCondition | undefined { - if (!listAttribute || !listAttribute.filterable || selected.size === 0) { + if (!listAttribute) { return undefined; } - - const { id, type } = listAttribute; - const filterAttribute = attribute(id); - - const filters = [...selected] - .filter(value => listAttribute.universe?.includes(universeValue(listAttribute.type, value))) - .map(value => equals(filterAttribute, literal(universeValue(type, value)))); - - if (filters.length > 1) { - return or(...filters); + if (selected.size < 1) { + return undefined; } - const [filterValue] = filters; - return filterValue; + const attrExp = attribute(listAttribute.id); + const conditions = Array.from(selected, value => + equals(attrExp, literal(universeValue(listAttribute.type, value))) + ); + + return conditions.length > 1 ? or(...conditions) : conditions[0]; } -function universeValue(type: ListAttributeValue["type"], value: string): boolean | string { +function universeValue(type: AttributeMetaData["type"], value: string): boolean | string { if (type === "Boolean") { - if (value !== "true" && value !== "false") { - return value; - } return value === "true"; } return value; diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/OptionsSerializer.ts b/packages/shared/widget-plugin-dropdown-filter/src/stores/OptionsSerializer.ts similarity index 100% rename from packages/shared/widget-plugin-filtering/src/stores/picker/OptionsSerializer.ts rename to packages/shared/widget-plugin-dropdown-filter/src/stores/OptionsSerializer.ts diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/RefFilterStore.ts b/packages/shared/widget-plugin-dropdown-filter/src/stores/RefFilterStore.ts similarity index 69% rename from packages/shared/widget-plugin-filtering/src/stores/picker/RefFilterStore.ts rename to packages/shared/widget-plugin-dropdown-filter/src/stores/RefFilterStore.ts index c545fbb634..1a67a3d348 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/picker/RefFilterStore.ts +++ b/packages/shared/widget-plugin-dropdown-filter/src/stores/RefFilterStore.ts @@ -1,40 +1,36 @@ -import { ListAttributeValue, ListReferenceSetValue, ListReferenceValue, ListValue, ObjectItem } from "mendix"; +import { flattenRefCond, selectedFromCond } from "@mendix/filter-commons/condition-utils"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; +import { AssociationMetaData, AttributeMetaData, ListValue, ObjectItem } from "mendix"; import { ContainsCondition, EqualsCondition, FilterCondition, LiteralExpression } from "mendix/filters"; -import { association, attribute, contains, empty, equals, literal, or } from "mendix/filters/builders"; +import { association, attribute, contains, equals, literal, or } from "mendix/filters/builders"; import { action, autorun, computed, makeObservable, observable, reaction, runInAction, when } from "mobx"; -import { flattenRefCond, selectedFromCond } from "../../condition-utils"; -import { disposeFx } from "../../mobx-utils"; -import { OptionWithState } from "../../typings/OptionWithState"; +import { OptionWithState } from "../typings/OptionWithState"; import { BaseSelectStore } from "./BaseSelectStore"; import { SearchStore } from "./SearchStore"; -type ListAttributeId = ListAttributeValue["id"]; +type ListAttributeId = AttributeMetaData["id"]; export interface RefFilterStoreProps { - ref: ListReferenceValue | ListReferenceSetValue; - datasource: ListValue; - searchAttrId?: ListAttributeId; fetchOptionsLazy?: boolean; - caption: CaptionAccessor; + refEntity: AssociationMetaData; + refCaption: CaptionAccessor; + refOptions: ListValue; + searchAttrId?: ListAttributeId; } interface CaptionAccessor { get: (obj: ObjectItem) => { value: string | undefined }; } +type Gate = DerivedPropsGate; + export class RefFilterStore extends BaseSelectStore { readonly storeType = "refselect"; readonly optionsFilterable: boolean; - private datasource: ListValue; - private listRef: ListReferenceValue | ListReferenceSetValue; - private caption: CaptionAccessor; - private searchAttrId?: ListAttributeId; - /** - * As Ref filter fetch options lazily, - * we just keep condition and - * return it if options not loaded yet. - */ + private readonly gate: Gate; + private readonly searchAttrId?: ListAttributeId; private readonly initCondArray: Array; private readonly pageSize = 20; private readonly searchSize = 100; @@ -44,11 +40,11 @@ export class RefFilterStore extends BaseSelectStore { lazyMode: boolean; search: SearchStore; - constructor(props: RefFilterStoreProps, initCond: FilterCondition | null) { + constructor({ gate, initCond }: { gate: Gate; initCond: FilterCondition | null }) { super(); - this.caption = props.caption; - this.datasource = props.datasource; - this.listRef = props.ref; + const { props } = gate; + this.gate = gate; + this.lazyMode = props.fetchOptionsLazy ?? true; this.searchAttrId = props.searchAttrId; this.initCondArray = initCond ? flattenRefCond(initCond) : []; @@ -59,16 +55,14 @@ export class RefFilterStore extends BaseSelectStore { this.datasource.setLimit(0); } - makeObservable(this, { - datasource: observable.ref, - listRef: observable.ref, - caption: observable.ref, - searchAttrId: observable.ref, + makeObservable(this, { + datasource: computed, + refEntity: computed, + caption: computed, options: computed, hasMore: computed, isLoading: computed, condition: computed, - updateProps: action, fromViewState: action, fetchReady: observable, setFetchReady: action, @@ -82,6 +76,18 @@ export class RefFilterStore extends BaseSelectStore { } } + private get datasource(): ListValue { + return this.gate.props.refOptions; + } + + private get refEntity(): AssociationMetaData { + return this.gate.props.refEntity; + } + + private get caption(): CaptionAccessor { + return this.gate.props.refCaption; + } + get hasMore(): boolean { return this.datasource.hasMoreItems ?? false; } @@ -115,19 +121,18 @@ export class RefFilterStore extends BaseSelectStore { const exp = (guid: string): FilterCondition[] => { const obj = this.selectedItems.find(o => o.id === guid); - if (obj && this.listRef.type === "Reference") { - return [refEquals(this.listRef, obj)]; - } else if (obj && this.listRef.type === "ReferenceSet") { - return [refContains(this.listRef, [obj])]; + if (obj) { + try { + return [contains(association(this.refEntity.id), literal(obj))]; + } catch { + return [equals(association(this.refEntity.id), literal(obj))]; + } } const viewExp = this.initCondArray.find(e => { if (e.arg2.type !== "literal") { return false; } - if (e.arg2.valueType === "Reference") { - return e.arg2.value === guid; - } if (e.arg2.valueType === "ReferenceSet") { return e.arg2.value.at(0) === guid; } @@ -146,14 +151,14 @@ export class RefFilterStore extends BaseSelectStore { } setup(): () => void { - const [disposers, dispose] = disposeFx(); + const [add, dispose] = disposeBatch(); - disposers.push(this.search.setup()); - disposers.push(reaction(...this.searchChangeFx())); - disposers.push(autorun(...this.computeSelectedItemsFx())); + add(this.search.setup()); + add(reaction(...this.searchChangeFx())); + add(autorun(...this.computeSelectedItemsFx())); if (this.lazyMode) { - disposers.push( + add( when( () => this.fetchReady, () => this.loadMore() @@ -209,12 +214,6 @@ export class RefFilterStore extends BaseSelectStore { } } - updateProps(props: RefFilterStoreProps): void { - this.listRef = props.ref; - this.datasource = props.datasource; - this.caption = props.caption; - } - loadMore(): void { this.datasource.setLimit(this.datasource.limit + this.pageSize); } @@ -239,12 +238,3 @@ export class RefFilterStore extends BaseSelectStore { } } } - -export function refEquals(associationValue: ListReferenceValue, value: ObjectItem): EqualsCondition { - return equals(association(associationValue.id), literal(value)); -} - -export function refContains(associationValue: ListReferenceSetValue, value: ObjectItem[]): ContainsCondition { - const v = value.length ? literal(value.slice()) : empty(); - return contains(association(associationValue.id), v); -} diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/SearchStore.ts b/packages/shared/widget-plugin-dropdown-filter/src/stores/SearchStore.ts similarity index 100% rename from packages/shared/widget-plugin-filtering/src/stores/picker/SearchStore.ts rename to packages/shared/widget-plugin-dropdown-filter/src/stores/SearchStore.ts diff --git a/packages/shared/widget-plugin-dropdown-filter/src/typings/IJSActionsControlled.ts b/packages/shared/widget-plugin-dropdown-filter/src/typings/IJSActionsControlled.ts new file mode 100644 index 0000000000..50117d47ba --- /dev/null +++ b/packages/shared/widget-plugin-dropdown-filter/src/typings/IJSActionsControlled.ts @@ -0,0 +1,11 @@ +export interface IJSActionsControlled { + handleResetValue: ResetHandler; + handleSetValue: SetValueHandler; +} + +export type ResetHandler = (useDefaultValue: boolean) => void; + +export type SetValueHandler = ( + useDefaultValue: boolean, + params: { operators: any; stringValue: string; numberValue: Big.Big; dateTimeValue: Date; dateTimeValue2: Date } +) => void; diff --git a/packages/shared/widget-plugin-filtering/src/typings/OptionWithState.ts b/packages/shared/widget-plugin-dropdown-filter/src/typings/OptionWithState.ts similarity index 100% rename from packages/shared/widget-plugin-filtering/src/typings/OptionWithState.ts rename to packages/shared/widget-plugin-dropdown-filter/src/typings/OptionWithState.ts diff --git a/packages/shared/widget-plugin-dropdown-filter/src/typings/type-utils.ts b/packages/shared/widget-plugin-dropdown-filter/src/typings/type-utils.ts new file mode 100644 index 0000000000..443afb18a9 --- /dev/null +++ b/packages/shared/widget-plugin-dropdown-filter/src/typings/type-utils.ts @@ -0,0 +1 @@ +export type GConstructor = new (...args: any[]) => T; diff --git a/packages/shared/widget-plugin-dropdown-filter/src/typings/widget.ts b/packages/shared/widget-plugin-dropdown-filter/src/typings/widget.ts new file mode 100644 index 0000000000..a839b1c9c3 --- /dev/null +++ b/packages/shared/widget-plugin-dropdown-filter/src/typings/widget.ts @@ -0,0 +1,10 @@ +import { DynamicValue } from "mendix"; + +export interface FilterOptionsType { + caption: DynamicValue; + value: DynamicValue; +} + +export type SelectedItemsStyleEnum = "text" | "boxes"; + +export type SelectionMethodEnum = "checkbox" | "rowClick"; diff --git a/packages/shared/widget-plugin-dropdown-filter/tsconfig.json b/packages/shared/widget-plugin-dropdown-filter/tsconfig.json new file mode 100644 index 0000000000..052cc1cee7 --- /dev/null +++ b/packages/shared/widget-plugin-dropdown-filter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@mendix/tsconfig-web-widgets/esm-library-with-jsx", + "include": ["./src/**/*"], + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true + } +} diff --git a/packages/shared/widget-plugin-external-events/package.json b/packages/shared/widget-plugin-external-events/package.json index 193934896f..81fef7f2d5 100644 --- a/packages/shared/widget-plugin-external-events/package.json +++ b/packages/shared/widget-plugin-external-events/package.json @@ -28,7 +28,6 @@ "dev": "tsc -p tsconfig.build.json --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "pnpm build", "test": "jest" }, "dependencies": { diff --git a/packages/shared/widget-plugin-filtering/package.json b/packages/shared/widget-plugin-filtering/package.json index 93a234c82c..e54052d150 100644 --- a/packages/shared/widget-plugin-filtering/package.json +++ b/packages/shared/widget-plugin-filtering/package.json @@ -1,6 +1,6 @@ { "name": "@mendix/widget-plugin-filtering", - "version": "1.1.1", + "version": "2.0.0", "description": "Filtering API plugin.", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -28,14 +28,16 @@ "dev": "tsc --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "tsc", "test": "jest" }, "dependencies": { "@floating-ui/react": "^0.26.27", "@floating-ui/react-dom": "^2.1.2", + "@mendix/filter-commons": "workspace:*", + "@mendix/widget-plugin-dropdown-filter": "workspace:*", "@mendix/widget-plugin-external-events": "workspace:*", "@mendix/widget-plugin-hooks": "workspace:*", + "@mendix/widget-plugin-mobx-kit": "workspace:^", "@mendix/widget-plugin-platform": "workspace:*", "downshift": "^9.0.8", "mendix": "10.23.70273", diff --git a/packages/shared/widget-plugin-filtering/src/__tests__/DateInputFilterStore.spec.ts b/packages/shared/widget-plugin-filtering/src/__tests__/DateInputFilterStore.spec.ts index 25ba577ab6..b54f27e74a 100644 --- a/packages/shared/widget-plugin-filtering/src/__tests__/DateInputFilterStore.spec.ts +++ b/packages/shared/widget-plugin-filtering/src/__tests__/DateInputFilterStore.spec.ts @@ -1,5 +1,5 @@ jest.mock("mendix/filters/builders"); -import { attrId, listAttr } from "@mendix/widget-plugin-test-utils"; +import { attrId, listAttribute } from "@mendix/widget-plugin-test-utils"; import { ListAttributeValue } from "mendix"; import { and, @@ -28,7 +28,7 @@ describe("DateInputFilterStore", () => { let store: DateInputFilterStore; beforeEach(() => { - attr = listAttr(() => new Date()); + attr = listAttribute(() => new Date()); attr.id = attrId("attr_unset"); store = new DateInputFilterStore([attr], null); }); @@ -125,7 +125,7 @@ describe("DateInputFilterStore", () => { }); it("uses 'or' when have multiple attributes", () => { - const [attr1, attr2] = [listAttr(() => new Date()), listAttr(() => new Date())]; + const [attr1, attr2] = [listAttribute(() => new Date()), listAttribute(() => new Date())]; store = new DateInputFilterStore([attr1, attr2], null); const date1 = new Date("2024-09-17T01:01:01.000Z"); store.filterFunction = "equal"; @@ -146,7 +146,7 @@ describe("DateInputFilterStore", () => { let store: DateInputFilterStore; beforeEach(() => { - attr = listAttr(() => new Date()); + attr = listAttribute(() => new Date()); store = new DateInputFilterStore([attr], null); expect(store.filterFunction).toBe("equal"); }); diff --git a/packages/shared/widget-plugin-filtering/src/__tests__/RefFilterStore.spec.ts b/packages/shared/widget-plugin-filtering/src/__tests__/RefFilterStore.spec.ts deleted file mode 100644 index de11ebf61e..0000000000 --- a/packages/shared/widget-plugin-filtering/src/__tests__/RefFilterStore.spec.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { cases, list, listExpression, listReference, ListValueBuilder, obj } from "@mendix/widget-plugin-test-utils"; -import { ObjectItem } from "mendix"; -import { _resetGlobalState, autorun } from "mobx"; -import { RefFilterStore, RefFilterStoreProps } from "../stores/picker/RefFilterStore"; - -describe("RefFilterStore", () => { - afterEach(() => _resetGlobalState()); - - describe("get options()", () => { - let items: ObjectItem[]; - let mapItem: (item: ObjectItem) => string; - let store: RefFilterStore; - beforeEach(() => { - const [a, b, c] = [obj("id3n"), obj("f8x3"), obj("932c")]; - items = [a, b, c]; - mapItem = cases([a, "Alice"], [b, "Bob"], [c, "Chuck"], [undefined, "No caption"]); - store = new RefFilterStore( - { - ref: listReference(), - datasource: list(items), - caption: listExpression(mapItem) - }, - null - ); - }); - - it("returns array of options", () => { - expect(store.options).toMatchInlineSnapshot(` - [ - { - "caption": "Alice", - "selected": false, - "value": "obj_id3n", - }, - { - "caption": "Bob", - "selected": false, - "value": "obj_f8x3", - }, - { - "caption": "Chuck", - "selected": false, - "value": "obj_932c", - }, - ] - `); - }); - }); - - describe("get condition()", () => { - it.todo("return filter condition using equals"); - }); - - describe("toJSON()", () => { - let store: RefFilterStore; - beforeEach(() => { - store = new RefFilterStore( - { - ref: listReference(), - datasource: list(3), - caption: listExpression(() => "[string]") - }, - null - ); - }); - - it("returns state as JS array", () => { - expect(store.toJSON()).toEqual([]); - }); - - it("save state with one item", () => { - const guid = store.options[0].value; - store.toggle(guid); - expect(store.toJSON()).toEqual([guid]); - }); - - it("save state with multiple items", () => { - const [guid1, guid2] = [store.options[0].value, store.options[1].value]; - store.toggle(guid1); - store.toggle(guid2); - const output = store.toJSON(); - - expect(output).toHaveLength(2); - expect(output).toContain(guid1); - expect(output).toContain(guid2); - }); - }); - - describe("fromJSON()", () => { - let store: RefFilterStore; - beforeEach(() => { - store = new RefFilterStore( - { - ref: listReference(), - datasource: list([obj("id3n"), obj("f8x3"), obj("932c")]), - caption: listExpression(() => "[string]") - }, - null - ); - }); - - it("restore state from JS array", () => { - const output: Array = []; - autorun(() => output.push(store.options)); - - // Check that every option is unselected - expect(output[0].map(option => option.selected)).toEqual([false, false, false]); - // Restore state - store.fromJSON(["obj_id3n"]); - // Check that first option is selected - expect(output[1].map(option => option.selected)).toEqual([true, false, false]); - }); - - it("overrides any previous state", () => { - const output: Array = []; - - // Select each option - store.options.forEach(option => { - store.toggle(option.value); - }); - // Start observing - autorun(() => output.push(store.options)); - // Check the state - expect(output[0].map(option => option.selected)).toEqual([true, true, true]); - store.fromJSON(["obj_932c"]); - expect(output[1].map(option => option.selected)).toEqual([false, false, true]); - }); - - it("should not change state if json is null", () => { - const state = store.toJSON(); - store.fromJSON(null); - expect(store.toJSON()).toEqual(state); - }); - }); - - describe("toggle()", () => { - let store: RefFilterStore; - beforeEach(() => { - store = new RefFilterStore( - { - ref: listReference(), - datasource: list([obj("id3n"), obj("f8x3"), obj("932c")]), - caption: listExpression(() => "[string]") - }, - null - ); - }); - - it("compute new options", () => { - const output: Array = []; - const [x, y] = store.options; - - autorun(() => output.push(store.options)); - expect(output[0].map(opt => opt.selected)).toEqual([false, false, false]); - - store.toggle(x.value); - store.toggle(y.value); - expect(output[2].map(opt => opt.selected)).toEqual([true, true, false]); - - store.toggle(x.value); - expect(output[3].map(opt => opt.selected)).toEqual([false, true, false]); - }); - }); - - describe("with 'fetchOptionsLazy' flag", () => { - it("should set limit to 0 on options datasource", () => { - const datasource = list.loading(); - const props = { - ref: listReference(), - datasource, - caption: listExpression(() => "[string]"), - fetchOptionsLazy: true - }; - - new RefFilterStore(props, null); - - expect(datasource.setLimit).toHaveBeenLastCalledWith(0); - }); - - it("should not change limit if flag is false", () => { - const datasource = list.loading(); - const props = { - ref: listReference(), - datasource, - caption: listExpression(() => "[string]"), - fetchOptionsLazy: false - }; - - new RefFilterStore(props, null); - - expect(datasource.setLimit).not.toHaveBeenCalled(); - }); - }); - - describe("when datasource changed", () => { - it("compute new options", () => { - const a = obj("id3n"); - const props = { - ref: listReference(), - datasource: list.loading(), - caption: listExpression(cases([a, "Alice"], [undefined, "No caption"])) - }; - const store = new RefFilterStore(props, null); - const output: any[] = []; - autorun(() => output.push(store.options)); - - expect(output[0]).toEqual([]); - store.updateProps({ ...props, datasource: list([a]) }); - expect(output[1]).toEqual([ - { - caption: "Alice", - selected: false, - value: "obj_id3n" - } - ]); - }); - }); - - describe("json data filtering", () => { - const savedJson = ["obj_xx", "obj_xiii", "obj_deleted", "obj_yy", "obj_unknown"]; - let a: ObjectItem; - let b: ObjectItem; - let props: Omit; - beforeEach(() => { - a = obj("xx"); - b = obj("yy"); - props = { - ref: listReference(), - caption: listExpression(() => "[string]") - }; - }); - - it("skip filtering if has just part of the list", () => { - const store = new RefFilterStore({ ...props, datasource: list.loading() }, null); - // Restore state - store.fromJSON(savedJson); - // Check state - expect(store.toJSON()).toEqual(savedJson); - // Update with partial data - const datasource = new ListValueBuilder().withItems([a, b]).withHasMore(true).build(); - store.updateProps({ ...props, datasource }); - // Check that state still has unknown GUIDs - expect(store.toJSON()).toEqual(savedJson); - }); - - it("allows to restore any state if datasource is loading", () => { - const store = new RefFilterStore({ ...props, datasource: list.loading() }, null); - // Restore state - store.fromJSON(savedJson); - // Check state - expect(store.toJSON()).toEqual(savedJson); - }); - }); -}); diff --git a/packages/shared/widget-plugin-filtering/src/context.ts b/packages/shared/widget-plugin-filtering/src/context.ts index 2a44be0ed0..382df8a092 100644 --- a/packages/shared/widget-plugin-filtering/src/context.ts +++ b/packages/shared/widget-plugin-filtering/src/context.ts @@ -1,58 +1,48 @@ +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; +import { FilterCondition } from "mendix/filters"; import { Context, createContext, useContext } from "react"; -import { APIError, ENOCONTEXT } from "./errors.js"; -import { Result, error, value } from "./result-meta.js"; -import { InputFilterInterface } from "./typings/InputFilterInterface.js"; -import { PickerFilterStore } from "./typings/PickerFilterStore.js"; +import { APIError, ENOCONTEXT } from "./errors"; +import { Result, error, value } from "./result-meta"; +import { InputFilterInterface } from "./typings/InputFilterInterface"; +import { ObservableFilterHost } from "./typings/ObservableFilterHost"; -export interface FilterAPIv2 { - version: 2; +export interface FilterAPI { + version: 3; parentChannelName: string; - provider: Result; + provider: Result; + filterObserver: ObservableFilterHost; + sharedInitFilter: Array; } -/** @deprecated */ -export enum FilterType { - STRING = "string", - NUMBER = "number", - ENUMERATION = "enum", - DATE = "date" -} - -export type FilterStoreProvider = DirectProvider | KeyProvider | LegacyProvider; - -export type FilterStore = InputFilterInterface | PickerFilterStore; +export type FilterStore = InputFilterInterface | EnumFilterStore; interface DirectProvider { type: "direct"; store: FilterStore | null; } -export interface KeyProvider { - type: "key-value"; - get: (key: string) => FilterStore | null; +interface ProviderStub { + type: "stub"; + hint: "No filter store available"; } -/** @deprecated */ -export interface LegacyProvider { - type: "legacy"; - get: (type: FilterType) => FilterStore | null; -} +export const PROVIDER_STUB = Object.freeze({ type: "stub", hint: "No filter store available" } as const); -type Context_v2 = Context; +type FilterAPIContext = Context; const CONTEXT_OBJECT_PATH = "com.mendix.widgets.web.filterable.filterContext.v2" as const; declare global { interface Window { - [CONTEXT_OBJECT_PATH]: Context_v2 | undefined; + [CONTEXT_OBJECT_PATH]: FilterAPIContext | undefined; } } -export function getGlobalFilterContextObject(): Context_v2 { - return (window[CONTEXT_OBJECT_PATH] ??= createContext(null)); +export function getGlobalFilterContextObject(): FilterAPIContext { + return (window[CONTEXT_OBJECT_PATH] ??= createContext(null)); } -export function useFilterContextValue(): Result { +export function useFilterAPI(): Result { const context = getGlobalFilterContextObject(); const contextValue = useContext(context); @@ -63,15 +53,19 @@ export function useFilterContextValue(): Result { return value(contextValue); } -export function getFilterStore(provider: FilterStoreProvider, legacyType: FilterType, key: string): FilterStore | null { - switch (provider.type) { - case "direct": - return provider.store; - case "key-value": - return provider.get(key); - case "legacy": - return provider.get(legacyType); - default: - return null; - } +/** @deprecated This hook is renamed, use `useFilterAPI` instead. */ +export const useFilterContextValue = useFilterAPI; + +export function createContextWithStub(options: { + filterObserver: ObservableFilterHost; + parentChannelName: string; + sharedInitFilter: Array; +}): FilterAPI { + return { + version: 3, + parentChannelName: options.parentChannelName, + provider: value(PROVIDER_STUB), + filterObserver: options.filterObserver, + sharedInitFilter: options.sharedInitFilter + }; } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/input/NumberInputController.ts b/packages/shared/widget-plugin-filtering/src/controllers/input/NumberInputController.ts index 9c0391aa8c..f46a8abafc 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/input/NumberInputController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/input/NumberInputController.ts @@ -1,8 +1,12 @@ +import { + FilterFunctionBinary, + FilterFunctionGeneric, + FilterFunctionNonValue +} from "@mendix/filter-commons/typings/FilterFunctions"; import { debounce } from "@mendix/widget-plugin-platform/utils/debounce"; import { action, autorun, computed, makeObservable, reaction, runInAction } from "mobx"; import { createRef } from "react"; import { InputStore } from "../../stores/input/InputStore"; -import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue } from "../../typings/FilterFunctions"; import { FilterV, Number_InputFilterInterface } from "../../typings/InputFilterInterface"; export type Params = { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/input/StringInputController.ts b/packages/shared/widget-plugin-filtering/src/controllers/input/StringInputController.ts index 91aef3be74..6f58b6730b 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/input/StringInputController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/input/StringInputController.ts @@ -1,13 +1,13 @@ -import { debounce } from "@mendix/widget-plugin-platform/utils/debounce"; -import { action, autorun, computed, makeObservable, reaction, runInAction } from "mobx"; -import { createRef } from "react"; -import { InputStore } from "../../stores/input/InputStore"; import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue, FilterFunctionString -} from "../../typings/FilterFunctions"; +} from "@mendix/filter-commons/typings/FilterFunctions"; +import { debounce } from "@mendix/widget-plugin-platform/utils/debounce"; +import { action, autorun, computed, makeObservable, reaction, runInAction } from "mobx"; +import { createRef } from "react"; +import { InputStore } from "../../stores/input/InputStore"; import { FilterV, String_InputFilterInterface } from "../../typings/InputFilterInterface"; export type Params = { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts deleted file mode 100644 index c355a2e530..0000000000 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ActionValue, EditableValue } from "mendix"; -import { action, makeObservable } from "mobx"; -import { disposeFx } from "../../mobx-utils"; -import { RefFilterStore } from "../../stores/picker/RefFilterStore"; -import { PickerBaseController } from "./PickerBaseController"; - -export class RefBaseController extends PickerBaseController { - constructor(props: RefBaseControllerProps) { - super(props); - makeObservable(this, { - updateProps: action - }); - } - - setup(): () => void { - const [disposers, dispose] = disposeFx(); - - disposers.push(this.changeHelper.setup()); - - if (this.defaultValue) { - this.filterStore.setDefaultSelected(this.defaultValue); - } - - return dispose; - } - - updateProps(props: RefBaseControllerProps): void { - this.changeHelper.updateProps(props); - } -} - -export interface RefBaseControllerProps { - defaultValue?: string; - filterStore: RefFilterStore; - multiselect: boolean; - onChange?: ActionValue; - valueAttribute?: EditableValue; - emptyCaption?: string; - placeholder?: string; -} diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts deleted file mode 100644 index b9506dcd99..0000000000 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ActionValue, DynamicValue, EditableValue } from "mendix"; -import { action, autorun, makeObservable, observable } from "mobx"; -import { disposeFx } from "../../mobx-utils"; -import { StaticSelectFilterStore } from "../../stores/picker/StaticSelectFilterStore"; -import { PickerBaseController } from "./PickerBaseController"; - -export class StaticBaseController extends PickerBaseController { - filterOptions: Array>>; - - constructor(props: StaticBaseControllerProps) { - super(props); - this.filterOptions = props.filterOptions; - makeObservable(this, { - updateProps: action, - filterOptions: observable.struct - }); - } - - setup(): () => void { - const [disposers, dispose] = disposeFx(); - - disposers.push(this.changeHelper.setup()); - - disposers.push( - autorun(() => { - if (this.filterOptions.length > 0) { - const options = this.filterOptions.map(this.toStoreOption); - this.filterStore.setCustomOptions(options); - } - }) - ); - - if (this.defaultValue) { - this.filterStore.setDefaultSelected(this.defaultValue); - } - - return dispose; - } - - updateProps(props: StaticBaseControllerProps): void { - this.filterOptions = props.filterOptions; - this.changeHelper.updateProps(props); - } - - toStoreOption = (opt: CustomOption>): CustomOption => ({ - caption: `${opt.caption?.value}`, - value: `${opt.value?.value}` - }); -} - -export interface StaticBaseControllerProps { - defaultValue?: string; - filterOptions: Array>>; - filterStore: StaticSelectFilterStore; - multiselect: boolean; - onChange?: ActionValue; - valueAttribute?: EditableValue; - emptyCaption?: string; - placeholder?: string; -} - -export interface CustomOption { - caption: T; - value: T; -} diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts deleted file mode 100644 index c6410a631c..0000000000 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController"; -import { ComboboxControllerMixin } from "./mixins/ComboboxControllerMixin"; - -export class StaticComboboxController extends ComboboxControllerMixin(StaticBaseController) { - constructor(props: StaticBaseControllerProps) { - super({ ...props, multiselect: false }); - this.inputPlaceholder = props.placeholder ?? "Search"; - } -} diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts deleted file mode 100644 index 8174fbb975..0000000000 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController"; -import { SelectControllerMixin } from "./mixins/SelectControllerMixin"; - -export class StaticSelectController extends SelectControllerMixin(StaticBaseController) { - constructor(props: StaticBaseControllerProps) { - super(props); - this.emptyOption.caption = props.emptyCaption || "None"; - this.placeholder = props.emptyCaption || "Select"; - } -} diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts deleted file mode 100644 index 11bc90fd30..0000000000 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController"; -import { TagPickerControllerMixin } from "./mixins/TagPickerControllerMixin"; - -type SelectionMethodEnum = "checkbox" | "rowClick"; -type SelectedItemsStyleEnum = "text" | "boxes"; - -interface Props extends StaticBaseControllerProps { - selectionMethod: SelectionMethodEnum; - selectedItemsStyle: SelectedItemsStyleEnum; -} - -export class StaticTagPickerController extends TagPickerControllerMixin(StaticBaseController) { - selectionMethod: SelectionMethodEnum; - selectedStyle: SelectedItemsStyleEnum; - - constructor(props: Props) { - super(props); - this.inputPlaceholder = props.placeholder ?? "Search"; - this.filterSelectedOptions = props.selectionMethod === "rowClick"; - this.selectedStyle = props.selectedItemsStyle; - this.selectionMethod = this.selectedStyle === "boxes" ? props.selectionMethod : "checkbox"; - } -} diff --git a/packages/shared/widget-plugin-filtering/src/controls/filter-selector/useSelect.tsx b/packages/shared/widget-plugin-filtering/src/controls/filter-selector/useSelect.tsx index 9107994ea1..8b26f40a7b 100644 --- a/packages/shared/widget-plugin-filtering/src/controls/filter-selector/useSelect.tsx +++ b/packages/shared/widget-plugin-filtering/src/controls/filter-selector/useSelect.tsx @@ -36,7 +36,7 @@ export function useSelect(props: useSelectProps): ViewProps { items: props.options, selectedItem, itemToString, - onSelectedItemChange: ({ selectedItem }) => props.onSelect(selectedItem.value) + onSelectedItemChange: ({ selectedItem }) => props.onSelect(selectedItem?.value ?? null) }); const { refs, floatingStyles, update } = useFloating({ diff --git a/packages/shared/widget-plugin-filtering/src/controls/input/InputWithFilters.tsx b/packages/shared/widget-plugin-filtering/src/controls/input/InputWithFilters.tsx index f1293bdb27..799d47047a 100644 --- a/packages/shared/widget-plugin-filtering/src/controls/input/InputWithFilters.tsx +++ b/packages/shared/widget-plugin-filtering/src/controls/input/InputWithFilters.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import classNames from "classnames"; import { FilterSelector } from "../filter-selector/FilterSelector"; import { InputComponentProps } from "./typings"; -import { AllFunctions } from "../../typings/FilterFunctions"; +import { AllFunctions } from "@mendix/filter-commons/typings/FilterFunctions"; export function InputWithFiltersComponent(props: InputComponentProps): React.ReactElement { const { diff --git a/packages/shared/widget-plugin-filtering/src/controls/input/typings.ts b/packages/shared/widget-plugin-filtering/src/controls/input/typings.ts index 16c5a985e4..6a94b93fe0 100644 --- a/packages/shared/widget-plugin-filtering/src/controls/input/typings.ts +++ b/packages/shared/widget-plugin-filtering/src/controls/input/typings.ts @@ -1,5 +1,5 @@ +import { AllFunctions } from "@mendix/filter-commons/typings/FilterFunctions"; import { InputStore } from "../../stores/input/InputStore"; -import { AllFunctions } from "../../typings/FilterFunctions"; import { InputFilterInterface } from "../../typings/InputFilterInterface"; export interface BaseProps { diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts new file mode 100644 index 0000000000..41299b12e1 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts @@ -0,0 +1,32 @@ +import { isAnd, isTag } from "@mendix/filter-commons/condition-utils"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable"; +import { FilterCondition } from "mendix/filters"; +import { FilterAPI } from "../context"; +import { Filter } from "../typings/ObservableFilterHost"; + +export abstract class BaseStoreProvider implements ISetupable { + protected abstract _store: S; + protected abstract filterAPI: FilterAPI; + abstract readonly dataKey: string; + + protected findInitFilter(conditions: Array, key: string): FilterCondition | null { + for (const cond of conditions) { + if (cond && isAnd(cond)) { + const [tag, initFilter] = cond.args; + if (isTag(tag) && tag.arg1.value === key) { + return initFilter; + } + } + } + return null; + } + + setup(): () => void { + const [add, disposeAll] = disposeBatch(); + this.filterAPI.filterObserver.observe(this.dataKey, this._store); + add(() => this.filterAPI.filterObserver.unobserve(this.dataKey)); + add(this._store.setup?.()); + return disposeAll; + } +} diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts new file mode 100644 index 0000000000..65cfe580d5 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts @@ -0,0 +1,25 @@ +import { FilterAPI } from "../context"; +import { DateInputFilterStore } from "../stores/input/DateInputFilterStore"; +import { Date_InputFilterInterface } from "../typings/InputFilterInterface"; +import { BaseStoreProvider } from "./BaseStoreProvider"; +import { FilterSpec } from "./typings"; + +export class DateStoreProvider extends BaseStoreProvider { + protected _store: DateInputFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, spec: FilterSpec) { + super(); + this.filterAPI = filterAPI; + this.dataKey = spec.dataKey; + this._store = new DateInputFilterStore( + spec.attributes, + this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + ); + } + + get store(): Date_InputFilterInterface { + return this._store; + } +} diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/EnumStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/EnumStoreProvider.ts new file mode 100644 index 0000000000..c72ad7167b --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/EnumStoreProvider.ts @@ -0,0 +1,24 @@ +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; +import { FilterAPI } from "../context"; +import { BaseStoreProvider } from "./BaseStoreProvider"; +import { FilterSpec } from "./typings"; + +export class EnumStoreProvider extends BaseStoreProvider { + protected _store: EnumFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, spec: FilterSpec) { + super(); + this.filterAPI = filterAPI; + this.dataKey = spec.dataKey; + this._store = new EnumFilterStore( + spec.attributes, + this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + ); + } + + get store(): EnumFilterStore { + return this._store; + } +} diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts new file mode 100644 index 0000000000..da7f77e639 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts @@ -0,0 +1,25 @@ +import { FilterAPI } from "../context"; +import { NumberInputFilterStore } from "../stores/input/NumberInputFilterStore"; +import { Number_InputFilterInterface } from "../typings/InputFilterInterface"; +import { BaseStoreProvider } from "./BaseStoreProvider"; +import { FilterSpec } from "./typings"; + +export class NumberStoreProvider extends BaseStoreProvider { + protected _store: NumberInputFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, spec: FilterSpec) { + super(); + this.filterAPI = filterAPI; + this.dataKey = spec.dataKey; + this._store = new NumberInputFilterStore( + spec.attributes, + this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + ); + } + + get store(): Number_InputFilterInterface { + return this._store; + } +} diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts new file mode 100644 index 0000000000..b1270ac3e3 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts @@ -0,0 +1,25 @@ +import { FilterAPI } from "../context"; +import { StringInputFilterStore } from "../stores/input/StringInputFilterStore"; +import { String_InputFilterInterface } from "../typings/InputFilterInterface"; +import { BaseStoreProvider } from "./BaseStoreProvider"; +import { FilterSpec } from "./typings"; + +export class StringStoreProvider extends BaseStoreProvider { + protected _store: StringInputFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, spec: FilterSpec) { + super(); + this.filterAPI = filterAPI; + this.dataKey = spec.dataKey; + this._store = new StringInputFilterStore( + spec.attributes, + this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + ); + } + + get store(): String_InputFilterInterface { + return this._store; + } +} diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts new file mode 100644 index 0000000000..5cf80885d2 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts @@ -0,0 +1,8 @@ +import { AttributeMetaData, EditableValue } from "mendix"; + +type AttributeValue_2 = EditableValue["value"]; + +export interface FilterSpec { + attributes: Array>; + dataKey: string; +} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts deleted file mode 100644 index d2391337f3..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useRef } from "react"; -import { FilterType, getFilterStore, useFilterContextValue } from "../context"; -import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors"; -import { error, Result, value } from "../result-meta"; -import { isDateFilter } from "../stores/input/store-utils"; -import { Date_InputFilterInterface } from "../typings/InputFilterInterface"; - -export interface Date_FilterAPIv2 { - filterStore: Date_InputFilterInterface; - parentChannelName?: string; -} - -export function useDateFilterAPI(key: string): Result { - const ctx = useFilterContextValue(); - const dateAPI = useRef(); - - if (ctx.hasError) { - return error(ctx.error); - } - - const api = ctx.value; - - if (api.provider.hasError) { - return error(api.provider.error); - } - - if (api.provider.value.type === "key-value" && key === "") { - return error(EKEYMISSING); - } - - const store = getFilterStore(api.provider.value, FilterType.DATE, key); - - if (store === null) { - return error(EMISSINGSTORE); - } - - if (store.storeType !== "input" || !isDateFilter(store)) { - return error(EStoreTypeMisMatch("date filter", store.storeType !== "input" ? "select" : store.arg1.type)); - } - - return value((dateAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useDateSync.ts b/packages/shared/widget-plugin-filtering/src/helpers/useDateSync.ts index cc09e597a3..b1363eb67a 100644 --- a/packages/shared/widget-plugin-filtering/src/helpers/useDateSync.ts +++ b/packages/shared/widget-plugin-filtering/src/helpers/useDateSync.ts @@ -1,7 +1,11 @@ +import { + FilterFunctionBinary, + FilterFunctionGeneric, + FilterFunctionNonValue +} from "@mendix/filter-commons/typings/FilterFunctions"; import { ActionValue, EditableValue } from "mendix"; import { reaction } from "mobx"; import { useEffect, useRef } from "react"; -import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue } from "../typings/FilterFunctions"; import { InputFilterInterface } from "../typings/InputFilterInterface"; interface Props { diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts deleted file mode 100644 index 4635732692..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useRef } from "react"; -import { FilterType, getFilterStore, useFilterContextValue } from "../context"; -import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors"; -import { error, Result, value } from "../result-meta"; -import { isNumberFilter } from "../stores/input/store-utils"; -import { Number_InputFilterInterface } from "../typings/InputFilterInterface"; - -export interface Number_FilterAPIv2 { - filterStore: Number_InputFilterInterface; - parentChannelName?: string; -} - -export function useNumberFilterAPI(key: string): Result { - const ctx = useFilterContextValue(); - const numAPI = useRef(); - - if (ctx.hasError) { - return error(ctx.error); - } - - const api = ctx.value; - - if (api.provider.hasError) { - return error(api.provider.error); - } - - if (api.provider.value.type === "key-value" && key === "") { - return error(EKEYMISSING); - } - - const store = getFilterStore(api.provider.value, FilterType.NUMBER, key); - - if (store === null) { - return error(EMISSINGSTORE); - } - - if (store.storeType !== "input" || !isNumberFilter(store)) { - return error( - EStoreTypeMisMatch("number filter", store.storeType !== "input" ? "option list" : store.arg1.type) - ); - } - - return value((numAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterController.ts b/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterController.ts deleted file mode 100644 index 9100b1fd16..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterController.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useState } from "react"; -import { NumberFilterController, Params } from "../controllers/input/NumberInputController"; -import { ArgumentInterface } from "../typings/ArgumentInterface"; -import { AllFunctions } from "../typings/FilterFunctions"; -import { FilterFn, InputFilterBaseInterface } from "../typings/InputFilterInterface"; - -export function useNumberFilterController< - F extends InputFilterBaseInterface, - A extends ArgumentInterface, - Fn extends AllFunctions = FilterFn ->(params: Params): NumberFilterController { - const [ctrl] = useState(() => new NumberFilterController(params)); - - useEffect(() => ctrl.setup(), [ctrl]); - - return ctrl; -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts deleted file mode 100644 index b3a675806e..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useRef } from "react"; -import { FilterType, getFilterStore, useFilterContextValue } from "../context"; -import { APIError, EMISSINGSTORE, EStoreTypeMisMatch, OPTIONS_NOT_FILTERABLE } from "../errors"; -import { Result, error, value } from "../result-meta"; -import { PickerFilterStore } from "../typings/PickerFilterStore"; - -export interface Select_FilterAPIv2 { - filterStore: PickerFilterStore; - parentChannelName?: string; -} - -interface Props { - filterable: boolean; -} - -export function useSelectFilterAPI({ filterable }: Props): Result { - const ctx = useFilterContextValue(); - const slctAPI = useRef(); - - if (ctx.hasError) { - return error(ctx.error); - } - - const api = ctx.value; - - if (api.provider.hasError) { - return error(api.provider.error); - } - - const store = getFilterStore(api.provider.value, FilterType.ENUMERATION, ""); - - if (store === null) { - return error(EMISSINGSTORE); - } - - if (store.storeType !== "select" && store.storeType !== "refselect") { - return error(EStoreTypeMisMatch("dropdown filter", store.arg1.type)); - } - - if (store.storeType === "refselect") { - const configurationConflict = filterable && store.optionsFilterable === false; - if (configurationConflict) { - return error(OPTIONS_NOT_FILTERABLE); - } - } - - return value((slctAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useSetup.ts b/packages/shared/widget-plugin-filtering/src/helpers/useSetup.ts deleted file mode 100644 index c21157a027..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useSetup.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useEffect, useState } from "react"; - -export interface Setupable { - setup(): void | (() => void); -} - -export function useSetup(props: () => T): T { - const [obj] = useState(props); - useEffect(() => obj.setup(), [obj]); - return obj; -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useSetupUpdate.ts b/packages/shared/widget-plugin-filtering/src/helpers/useSetupUpdate.ts deleted file mode 100644 index 1c22ec422d..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useSetupUpdate.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect } from "react"; -import { Setupable, useSetup } from "./useSetup"; - -interface IUpdateModel

extends Setupable { - updateProps: (props: P) => void; -} - -export function useSetupUpdate, P>(setup: () => T, props: P): T { - const obj = useSetup(setup); - - // NOTE: Don't use dependencies. - // Model should be updated on every render. - useEffect(() => obj.updateProps(props)); - return obj; -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts deleted file mode 100644 index 377ff90477..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useRef } from "react"; -import { FilterType, getFilterStore, useFilterContextValue } from "../context"; -import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors"; -import { error, Result, value } from "../result-meta"; -import { isStringFilter } from "../stores/input/store-utils"; -import { String_InputFilterInterface } from "../typings/InputFilterInterface"; - -export interface String_FilterAPIv2 { - filterStore: String_InputFilterInterface; - parentChannelName?: string; -} - -export function useStringFilterAPI(key: string): Result { - const ctx = useFilterContextValue(); - const strAPI = useRef(); - - if (ctx.hasError) { - return error(ctx.error); - } - - const api = ctx.value; - - if (api.provider.hasError) { - return error(api.provider.error); - } - - if (api.provider.value.type === "key-value" && key === "") { - return error(EKEYMISSING); - } - - const store = getFilterStore(api.provider.value, FilterType.STRING, key); - - if (store === null) { - return error(EMISSINGSTORE); - } - - if (store.storeType !== "input" || !isStringFilter(store)) { - return error(EStoreTypeMisMatch("text filter", store.storeType !== "input" ? "option list" : store.arg1.type)); - } - - return value((strAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterController.ts b/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterController.ts deleted file mode 100644 index 35c26ad8c4..0000000000 --- a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterController.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useState } from "react"; -import { Params, StringFilterController } from "../controllers/input/StringInputController"; -import { ArgumentInterface } from "../typings/ArgumentInterface"; -import { AllFunctions } from "../typings/FilterFunctions"; -import { FilterFn, InputFilterBaseInterface } from "../typings/InputFilterInterface"; - -export function useStringFilterController< - F extends InputFilterBaseInterface, - A extends ArgumentInterface, - Fn extends AllFunctions = FilterFn ->(params: Params): StringFilterController { - const [ctrl] = useState(() => new StringFilterController(params)); - - useEffect(() => ctrl.setup(), [ctrl]); - - return ctrl; -} diff --git a/packages/shared/widget-plugin-filtering/src/helpers/withFilterAPI.tsx b/packages/shared/widget-plugin-filtering/src/helpers/withFilterAPI.tsx new file mode 100644 index 0000000000..cc28fc6f27 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/helpers/withFilterAPI.tsx @@ -0,0 +1,14 @@ +import { FC, createElement } from "react"; +import { FilterAPI, useFilterAPI } from "../context"; +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; + +export function withFilterAPI

(Component: FC

): FC

{ + return function FilterAPIProvider(props) { + const filterAPI = useFilterAPI(); + + if (filterAPI.hasError) { + return {filterAPI.error.message}; + } + return ; + }; +} diff --git a/packages/shared/widget-plugin-filtering/src/mobx-utils.ts b/packages/shared/widget-plugin-filtering/src/mobx-utils.ts deleted file mode 100644 index 0b5ab0649a..0000000000 --- a/packages/shared/widget-plugin-filtering/src/mobx-utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function disposeFx(): [disposers: Array<() => void>, dispose: () => void] { - const disposers: Array<() => void> = []; - return [ - disposers, - () => { - disposers.forEach(dispose => dispose()); - disposers.length = 0; - } - ]; -} diff --git a/packages/shared/widget-plugin-filtering/src/providers/LegacyPv.ts b/packages/shared/widget-plugin-filtering/src/providers/LegacyPv.ts deleted file mode 100644 index d9cb4f3b14..0000000000 --- a/packages/shared/widget-plugin-filtering/src/providers/LegacyPv.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { ListAttributeValue } from "mendix"; -import { FilterCondition } from "mendix/filters"; -import { action, comparer, computed, makeObservable, observable, reaction } from "mobx"; -import { FilterType as Ft, LegacyProvider } from "../context"; -import { DateInputFilterStore } from "../stores/input/DateInputFilterStore"; -import { NumberInputFilterStore } from "../stores/input/NumberInputFilterStore"; -import { StringInputFilterStore } from "../stores/input/StringInputFilterStore"; -import { StaticSelectFilterStore } from "../stores/picker/StaticSelectFilterStore"; -import { InputFilterInterface } from "../typings/InputFilterInterface"; -import { PickerFilterStore } from "../typings/PickerFilterStore"; -import { FiltersSettingsMap } from "../typings/settings"; - -type FilterMap = { - [Ft.STRING]: StringInputFilterStore | null; - [Ft.NUMBER]: NumberInputFilterStore | null; - [Ft.DATE]: DateInputFilterStore | null; - [Ft.ENUMERATION]: StaticSelectFilterStore | null; -}; - -type FilterList = [ - StringInputFilterStore | null, - NumberInputFilterStore | null, - DateInputFilterStore | null, - StaticSelectFilterStore | null -]; - -export class LegacyPv implements LegacyProvider { - readonly type = "legacy"; - private _attrs: ListAttributeValue[]; - dispose?: () => void; - filterMap: FilterMap; - filterList: FilterList; - - constructor(attrs: ListAttributeValue[], dsViewState: Array | null) { - this._attrs = attrs; - const map = (this.filterMap = createMap(attrs, dsViewState)); - this.filterList = [map[Ft.STRING], map[Ft.NUMBER], map[Ft.DATE], map[Ft.ENUMERATION]]; - makeObservable(this, { - _attrs: observable.ref, - conditions: computed, - settings: computed, - updateProps: action - }); - } - - get conditions(): Array { - return this.filterList.map(store => (store ? store.condition : undefined)); - } - - get settings(): FiltersSettingsMap { - return new Map(); - } - - set settings(_: unknown) {} - - get = (type: Ft): InputFilterInterface | PickerFilterStore | null => { - return this.filterMap[type]; - }; - - updateProps(attrs: ListAttributeValue[]): void { - this._attrs = attrs; - } - - updateFilters(attrs: ListAttributeValue[]): void { - const [str, num, dte, enm] = groupByType(attrs); - const [s1, s2, s3, s4] = this.filterList; - s1?.updateProps(str); - s2?.updateProps(num); - s3?.updateProps(dte); - s4?.updateProps(enm); - } - - setup(): () => void { - const disposers: Array<() => void> = []; - this.filterList.forEach(store => { - if (store && "setup" in store) { - disposers.push(store.setup()); - } - }); - disposers.push( - reaction( - () => this._attrs, - attrs => this.updateFilters(attrs), - { equals: comparer.shallow } - ) - ); - - return (this.dispose = () => disposers.forEach(dispose => dispose())); - } -} - -function createMap(attrs: ListAttributeValue[], dsViewState: Array | null): FilterMap { - const [ini1 = null, ini2 = null, ini3 = null, ini4 = null] = dsViewState ?? []; - const [str, num, dte, enm] = groupByType(attrs); - - const r: FilterMap = { - [Ft.STRING]: str.length > 0 ? new StringInputFilterStore(str, ini1) : null, - [Ft.NUMBER]: num.length > 0 ? new NumberInputFilterStore(num, ini2) : null, - [Ft.DATE]: dte.length > 0 ? new DateInputFilterStore(dte, ini3) : null, - [Ft.ENUMERATION]: enm.length > 0 ? new StaticSelectFilterStore(enm, ini4) : null - }; - - return r; -} - -function groupByType( - attrs: ListAttributeValue[] -): [ - Array>, - Array>, - Array>, - Array> -] { - return [ - attrs.filter(isStringAttr), - attrs.filter(isNumberAttr), - attrs.filter(isDateAttr), - attrs.filter((a): a is ListAttributeValue => isBoolAttr(a) || isEnumAttr(a)) - ]; -} - -function isDateAttr(attr: ListAttributeValue): attr is ListAttributeValue { - return attr.type === "DateTime"; -} - -function isBoolAttr(attr: ListAttributeValue): attr is ListAttributeValue { - return attr.type === "Boolean"; -} - -function isEnumAttr(attr: ListAttributeValue): attr is ListAttributeValue { - return attr.type === "Enum"; -} - -function isNumberAttr(attr: ListAttributeValue): attr is ListAttributeValue { - return attr.type === "Long" || attr.type === "Decimal" || attr.type === "Integer" || attr.type === "AutoNumber"; -} -function isStringAttr(attr: ListAttributeValue): attr is ListAttributeValue { - return attr.type === "HashString" || attr.type === "String"; -} diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts new file mode 100644 index 0000000000..2732aaf64a --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts @@ -0,0 +1,49 @@ +import { tag } from "@mendix/filter-commons/condition-utils"; +import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; +import { FilterCondition } from "mendix/filters"; +import { and } from "mendix/filters/builders"; +import { autorun, makeAutoObservable } from "mobx"; +import { Filter, ObservableFilterHost } from "../../typings/ObservableFilterHost"; + +export class CustomFilterHost implements ObservableFilterHost { + private filters: Map void]> = new Map(); + private settingsBuffer: FiltersSettingsMap = new Map(); + + constructor() { + makeAutoObservable(this); + } + + get settings(): FiltersSettingsMap { + return new Map([...this.filters].map(([key, [filter]]) => [key, filter.toJSON()])); + } + + set settings(data: FiltersSettingsMap) { + this.settingsBuffer = data; + } + + get conditions(): Array { + return [...this.filters].map(([key, [{ condition }]]) => { + return condition ? and(tag(key), condition) : undefined; + }); + } + + observe(key: string, filter: Filter): void { + this.unobserve(key); + const clear = autorun(() => { + if (this.settingsBuffer.has(key)) { + filter.fromJSON(this.settingsBuffer.get(key)); + } + }); + const dispose = (): void => { + clear(); + this.filters.delete(key); + }; + this.filters.set(key, [filter, dispose]); + } + + unobserve(key: string): void { + if (this.filters.has(key)) { + this.filters.get(key)?.[1](); + } + } +} diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts deleted file mode 100644 index 3836716e43..0000000000 --- a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ListAttributeValue } from "mendix"; -import { FilterCondition } from "mendix/filters"; -import { computed, makeObservable } from "mobx"; -import { FilterAPIv2 } from "../../context"; -import { APIError } from "../../errors"; -import { LegacyPv } from "../../providers/LegacyPv"; -import { Result, value } from "../../result-meta"; -import { FiltersSettingsMap } from "../../typings/settings"; - -export interface FilterListType { - filter: ListAttributeValue; -} - -export interface HeaderFiltersStoreProps { - filterList: FilterListType[]; - parentChannelName?: string; -} - -export interface StaticInfo { - name: string; - filtersChannelName: string; -} - -export class HeaderFiltersStore { - private provider: Result; - context: FilterAPIv2; - - constructor( - props: HeaderFiltersStoreProps, - info: StaticInfo, - dsViewState: Array | null - ) { - this.provider = this.createProvider(props, dsViewState); - this.context = { - version: 2, - parentChannelName: info.filtersChannelName ?? "", - provider: this.provider - }; - makeObservable(this, { - conditions: computed, - settings: computed - }); - } - - get conditions(): Array { - return this.provider.hasError ? [] : this.provider.value.conditions; - } - - get settings(): FiltersSettingsMap { - return this.provider.hasError ? new Map() : this.provider.value.settings; - } - - set settings(value: FiltersSettingsMap | undefined) { - if (this.provider.hasError) { - return; - } - - this.provider.value.settings = value; - } - - createProvider( - props: HeaderFiltersStoreProps, - dsViewState: Array | null - ): Result { - return value( - new LegacyPv( - props.filterList.map(f => f.filter), - dsViewState - ) - ); - } - - setup(): (() => void) | void { - if (this.provider.hasError) { - return; - } - - return this.provider.value.setup(); - } - - updateProps(): void {} -} diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts index 8dc62bfa6c..f599e940cf 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts @@ -1,5 +1,7 @@ +import { AllFunctions } from "@mendix/filter-commons/typings/FilterFunctions"; +import { FilterData, InputData } from "@mendix/filter-commons/typings/settings"; import { Big } from "big.js"; -import { ListAttributeValue } from "mendix"; +import { AttributeMetaData } from "mendix"; import { FilterCondition } from "mendix/filters"; import { and, @@ -17,14 +19,13 @@ import { startsWith } from "mendix/filters/builders"; import { action, computed, makeObservable, observable } from "mobx"; -import { AllFunctions } from "../../typings/FilterFunctions"; -import { FilterData, InputData } from "../../typings/settings"; import { Argument } from "./Argument"; type StateTuple = [Fn] | [Fn, V] | [Fn, V, V]; type Val = A["value"]; + export class BaseInputFilterStore { - protected _attributes: ListAttributeValue[] = []; + protected _attributes: AttributeMetaData[] = []; private _filterFunction: Fn; private _isFilterFunctionAdjustable: boolean = true; arg1: A; @@ -32,7 +33,7 @@ export class BaseInputFilterStore { isInitialized = false; defaultState: StateTuple>; - constructor(arg1: A, arg2: A, initFn: Fn, attributes: ListAttributeValue[]) { + constructor(arg1: A, arg2: A, initFn: Fn, attributes: AttributeMetaData[]) { this._attributes = attributes; this.defaultState = [initFn]; this._filterFunction = initFn; @@ -49,7 +50,9 @@ export class BaseInputFilterStore { setState: action, UNSAFE_setDefaults: action, UNSAFE_overwriteFilterFunction: action, - filterFunction: computed + filterFunction: computed, + clear: action, + reset: action }); } get filterFunction(): Fn { @@ -116,7 +119,7 @@ export class BaseInputFilterStore { } function getFilterCondition( - listAttribute: ListAttributeValue, + listAttribute: AttributeMetaData, value: T | undefined, valueR: T | undefined, operation: AllFunctions diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts index d04bf14a31..b7fbcfcbf5 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts @@ -1,4 +1,19 @@ -import { DateTimeFormatter, ListAttributeValue } from "mendix"; +import { + betweenToState, + isAnd, + isEmptyExp, + isNotEmptyExp, + isOr, + singularToState +} from "@mendix/filter-commons/condition-utils"; +import { + FilterFunctionBinary, + FilterFunctionGeneric, + FilterFunctionNonValue +} from "@mendix/filter-commons/typings/FilterFunctions"; +import { FilterName } from "@mendix/filter-commons/typings/mendix"; +import { FilterData, InputData } from "@mendix/filter-commons/typings/settings"; +import { AttributeMetaData, DateTimeFormatter, ListAttributeValue, SimpleFormatter } from "mendix"; import { AndCondition, FilterCondition, LiteralExpression } from "mendix/filters"; import { and, @@ -15,17 +30,14 @@ import { or } from "mendix/filters/builders"; import { action, comparer, IReactionDisposer, makeObservable, observable, reaction } from "mobx"; -import { betweenToState, isAnd, isEmptyExp, isNotEmptyExp, isOr, singularToState } from "../../condition-utils"; -import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue } from "../../typings/FilterFunctions"; import { Date_InputFilterInterface } from "../../typings/InputFilterInterface"; -import { FilterFunction } from "../../typings/mendix"; -import { FilterData, InputData } from "../../typings/settings"; import { DateArgument } from "./Argument"; import { BaseInputFilterStore } from "./BaseInputFilterStore"; type DateFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary; type StateTuple = [DateFns, Date | undefined, Date | undefined]; type InitState = [DateFns, Date | undefined, Date | undefined] | [DateFns, Date | undefined]; +type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter }; export class DateInputFilterStore extends BaseInputFilterStore @@ -36,8 +48,8 @@ export class DateInputFilterStore private readonly rangeMarkerTag = "__RANGE_MARKER__"; private computedState: StateTuple; - constructor(attributes: Array>, initCond: FilterCondition | null) { - const { formatter } = attributes[0]; + constructor(attributes: Array>, initCond: FilterCondition | null) { + const formatter = getFormatter(attributes[0]); super(new DateArgument(formatter), new DateArgument(formatter), "equal", attributes); // NOTE: some fields already become observable in `super`. makeObservable(this, { @@ -76,7 +88,7 @@ export class DateInputFilterStore updateProps(attributes: ListAttributeValue[]): void { if (!comparer.shallow(this._attributes, attributes)) { this._attributes = attributes; - const formatter = attributes.at(0)?.formatter; + const formatter = getFormatter(attributes[0] as AttributeMetaData); this.arg1.updateProps(formatter as DateTimeFormatter); this.arg2.updateProps(formatter as DateTimeFormatter); } @@ -103,7 +115,7 @@ export class DateInputFilterStore } private getCondition( - attr: ListAttributeValue, + attr: AttributeMetaData, filterFn: DateFns, v1: Date | undefined, v2: Date | undefined @@ -121,7 +133,7 @@ export class DateInputFilterStore } private getAttrCondition( - attr: ListAttributeValue, + attr: AttributeMetaData, filterFn: Exclude, date: Date | undefined ): [FilterCondition] | [] { @@ -153,7 +165,7 @@ export class DateInputFilterStore } } - private getRangeCondition(attr: ListAttributeValue, [start, end]: [Date, Date]): [FilterCondition] | [] { + private getRangeCondition(attr: AttributeMetaData, [start, end]: [Date, Date]): [FilterCondition] | [] { const attrExp = attribute(attr.id); return [ @@ -248,7 +260,7 @@ export class DateInputFilterStore } } - private mapFn = (name: FilterFunction | "between" | "empty" | "notEmpty"): DateFns => { + private mapFn = (name: FilterName | "between" | "empty" | "notEmpty"): DateFns => { switch (name) { case "day:=": return "equal"; @@ -296,3 +308,23 @@ function subDay(date: Date): Date { newDate.setUTCDate(newDate.getUTCDate() - 1); return newDate; } + +function getFormatter(attr: AttrMeta): SimpleFormatter { + if (attr.formatter) { + return attr.formatter; + } + + return { + format: v => v?.toString() ?? "", + parse: v => { + const date = Date.parse(v); + if (isNaN(date)) { + return { valid: false }; + } + return { + valid: true, + value: new Date(date) + }; + } + }; +} diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/InputStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/InputStore.ts index 2d40de0da6..3e98fb98b3 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/InputStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/InputStore.ts @@ -1,4 +1,4 @@ -import { makeObservable, observable, action } from "mobx"; +import { action, makeObservable, observable } from "mobx"; export class InputStore { value: string; @@ -11,6 +11,7 @@ export class InputStore { value: observable, isValid: observable, setValue: action, + setIsValid: action, onChange: action }); } diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts index a8fbbe08c5..7985c66ba9 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts @@ -1,17 +1,22 @@ +import { inputStateFromCond } from "@mendix/filter-commons/condition-utils"; +import { + FilterFunctionBinary, + FilterFunctionGeneric, + FilterFunctionNonValue +} from "@mendix/filter-commons/typings/FilterFunctions"; +import { FilterData, InputData } from "@mendix/filter-commons/typings/settings"; import { Big } from "big.js"; -import { ListAttributeValue } from "mendix"; +import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix"; import { FilterCondition } from "mendix/filters"; import { action, comparer, makeObservable } from "mobx"; -import { inputStateFromCond } from "../../condition-utils"; -import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue } from "../../typings/FilterFunctions"; import { Number_InputFilterInterface } from "../../typings/InputFilterInterface"; -import { FilterData, InputData } from "../../typings/settings"; import { NumberArgument } from "./Argument"; import { BaseInputFilterStore } from "./BaseInputFilterStore"; import { baseNames } from "./fn-mappers"; type NumFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary; -type Formatter = ListAttributeValue["formatter"]; +type Formatter = SimpleFormatter; +type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter }; export class NumberInputFilterStore extends BaseInputFilterStore @@ -20,9 +25,8 @@ export class NumberInputFilterStore readonly storeType = "input"; readonly type = "number"; - constructor(attributes: Array>, initCond: FilterCondition | null) { - let { formatter } = attributes[0]; - formatter = formatterFix(formatter); + constructor(attributes: AttrMeta[], initCond: FilterCondition | null) { + const formatter = formatterFix(attributes[0].formatter); super(new NumberArgument(formatter), new NumberArgument(formatter), "equal", attributes); makeObservable(this, { updateProps: action, @@ -79,11 +83,12 @@ export class NumberInputFilterStore } } -function formatterFix(formatter: Formatter): Formatter { +function formatterFix(formatter: Formatter | undefined): Formatter { // Check formatter.parse to see if it is a valid formatter. - if (formatter.parse("none")?.valid === false) { + if (formatter && formatter.parse("none")?.valid === false) { return formatter; } + // Create a new formatter that will handle the autonumber values. return { format: (value: Big) => { diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts index 45ab2a30d9..3bae4046c5 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts @@ -1,20 +1,22 @@ -import { ListAttributeValue } from "mendix"; -import { FilterCondition } from "mendix/filters"; -import { action, comparer, makeObservable } from "mobx"; -import { inputStateFromCond } from "../../condition-utils"; +import { inputStateFromCond } from "@mendix/filter-commons/condition-utils"; import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue, FilterFunctionString -} from "../../typings/FilterFunctions"; +} from "@mendix/filter-commons/typings/FilterFunctions"; +import { FilterData, InputData } from "@mendix/filter-commons/typings/settings"; +import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix"; +import { FilterCondition } from "mendix/filters"; +import { action, comparer, makeObservable } from "mobx"; import { String_InputFilterInterface } from "../../typings/InputFilterInterface"; -import { FilterData, InputData } from "../../typings/settings"; import { StringArgument } from "./Argument"; import { BaseInputFilterStore } from "./BaseInputFilterStore"; import { baseNames } from "./fn-mappers"; type StrFns = FilterFunctionString | FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary; +type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter }; + export class StringInputFilterStore extends BaseInputFilterStore implements String_InputFilterInterface @@ -22,8 +24,8 @@ export class StringInputFilterStore readonly storeType = "input"; readonly type = "string"; - constructor(attributes: Array>, initCond: FilterCondition | null) { - const { formatter } = attributes[0]; + constructor(attributes: AttrMeta[], initCond: FilterCondition | null) { + const formatter = getFormatter(attributes[0]); super(new StringArgument(formatter), new StringArgument(formatter), "equal", attributes); makeObservable(this, { updateProps: action, @@ -91,3 +93,13 @@ export class StringInputFilterStore this.isInitialized = true; } } + +function getFormatter(attr: { formatter?: SimpleFormatter }): SimpleFormatter { + return ( + attr.formatter ?? + ({ + format: v => v ?? "", + parse: v => ({ valid: true, value: v ?? "" }) + } as SimpleFormatter) + ); +} diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/fn-mappers.ts b/packages/shared/widget-plugin-filtering/src/stores/input/fn-mappers.ts index 71894c6d81..8ad3c58361 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/fn-mappers.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/fn-mappers.ts @@ -1,8 +1,12 @@ -import { FilterFunctionBinary, FilterFunctionGeneric, FilterFunctionNonValue } from "../../typings/FilterFunctions"; -import { FilterFunction } from "../../typings/mendix"; +import { + FilterFunctionBinary, + FilterFunctionGeneric, + FilterFunctionNonValue +} from "@mendix/filter-commons/typings/FilterFunctions"; +import { FilterName } from "@mendix/filter-commons/typings/mendix"; export function baseNames( - fn: FilterFunction | "between" | "empty" | "notEmpty" + fn: FilterName | "between" | "empty" | "notEmpty" ): FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary { switch (fn) { case "=": diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/store-utils.ts b/packages/shared/widget-plugin-filtering/src/stores/input/store-utils.ts index 63f382dea3..65328c72e7 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/store-utils.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/store-utils.ts @@ -1,3 +1,4 @@ +import { EnumFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/EnumFilterStore"; import { ListAttributeValue } from "mendix"; import { FilterCondition } from "mendix/filters"; import { @@ -9,7 +10,6 @@ import { import { DateInputFilterStore } from "../input/DateInputFilterStore"; import { NumberInputFilterStore } from "../input/NumberInputFilterStore"; import { StringInputFilterStore } from "../input/StringInputFilterStore"; -import { StaticSelectFilterStore } from "../picker/StaticSelectFilterStore"; export type InputFilterStore = StringInputFilterStore | NumberInputFilterStore | DateInputFilterStore; @@ -17,7 +17,7 @@ export function attrgroupFilterStore( type: ListAttributeValue["type"], attributes: ListAttributeValue[], initCond: FilterCondition | null -): InputFilterStore | StaticSelectFilterStore | null { +): InputFilterStore | EnumFilterStore | null { switch (type) { case "DateTime": return new DateInputFilterStore(attributes as Array>, initCond); @@ -34,7 +34,7 @@ export function attrgroupFilterStore( case "Boolean": case "Enum": - return new StaticSelectFilterStore(attributes, initCond); + return new EnumFilterStore(attributes, initCond); default: console.error("attrgroupFilterStore: not supported type " + type, attributes); return null; diff --git a/packages/shared/widget-plugin-filtering/src/stores/utils/is-input-data.ts b/packages/shared/widget-plugin-filtering/src/stores/utils/is-input-data.ts deleted file mode 100644 index ce484a9507..0000000000 --- a/packages/shared/widget-plugin-filtering/src/stores/utils/is-input-data.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InputData } from "../../typings/settings"; - -const fnNames = new Set([ - "empty", - "notEmpty", - "equal", - "notEqual", - "greater", - "greaterEqual", - "smaller", - "smallerEqual", - "between", - "contains", - "startsWith", - "endsWith" -]); - -export function isInputData(data: unknown): data is InputData { - if (Array.isArray(data)) { - const [name] = data; - return fnNames.has(name); - } - return false; -} diff --git a/packages/shared/widget-plugin-filtering/src/typings/InputFilterInterface.ts b/packages/shared/widget-plugin-filtering/src/typings/InputFilterInterface.ts index c58a879fea..8063c3ae3e 100644 --- a/packages/shared/widget-plugin-filtering/src/typings/InputFilterInterface.ts +++ b/packages/shared/widget-plugin-filtering/src/typings/InputFilterInterface.ts @@ -4,7 +4,7 @@ import { FilterFunctionGeneric, FilterFunctionNonValue, FilterFunctionString -} from "./FilterFunctions"; +} from "@mendix/filter-commons/typings/FilterFunctions"; import { ArgumentInterface, DateArgumentInterface, diff --git a/packages/shared/widget-plugin-filtering/src/typings/ObservableFilterHost.ts b/packages/shared/widget-plugin-filtering/src/typings/ObservableFilterHost.ts new file mode 100644 index 0000000000..0a06416b37 --- /dev/null +++ b/packages/shared/widget-plugin-filtering/src/typings/ObservableFilterHost.ts @@ -0,0 +1,17 @@ +import { FilterData, FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; +import { FilterCondition } from "mendix/filters"; + +export interface Filter { + toJSON(): FilterData; + fromJSON(data: FilterData): void; + condition: FilterCondition | undefined; + setup?: () => void | void; +} + +export interface ObservableFilterHost { + conditions: Array; + get settings(): FiltersSettingsMap; + set settings(settings: FiltersSettingsMap); + observe(key: string, filter: Filter): void; + unobserve(key: string): void; +} diff --git a/packages/shared/widget-plugin-filtering/src/typings/OptionListFilterInterface.ts b/packages/shared/widget-plugin-filtering/src/typings/OptionListFilterInterface.ts deleted file mode 100644 index 22f50c16a3..0000000000 --- a/packages/shared/widget-plugin-filtering/src/typings/OptionListFilterInterface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { OptionWithState } from "./OptionWithState"; - -export interface CustomOption { - caption: string; - value: string; -} - -export interface OptionListFilterInterface { - type: "refselect" | "select"; - storeType: "optionlist" | "refselect" | "select"; - options: OptionWithState[]; - selected: string[]; - isLoading: boolean; - hasMore: boolean; - canSearch: boolean; - - replace(value: string[]): void; - toggle(value: string): void; - loadMore(): void; - setSearch(term: string | undefined): void; - isValidValue(value: string): boolean; - reset(): void; - clear(): void; - UNSAFE_setDefaults(value?: string[]): void; - setup?(): () => void | void; - setCustomOptions(options: CustomOption[]): void; -} diff --git a/packages/shared/widget-plugin-filtering/src/typings/PickerFilterStore.ts b/packages/shared/widget-plugin-filtering/src/typings/PickerFilterStore.ts deleted file mode 100644 index d64f165603..0000000000 --- a/packages/shared/widget-plugin-filtering/src/typings/PickerFilterStore.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RefFilterStore } from "../stores/picker/RefFilterStore"; -import { StaticSelectFilterStore } from "../stores/picker/StaticSelectFilterStore"; - -export type PickerFilterStore = RefFilterStore | StaticSelectFilterStore; diff --git a/packages/shared/widget-plugin-filtering/src/typings/mendix.ts b/packages/shared/widget-plugin-filtering/src/typings/mendix.ts deleted file mode 100644 index 2544d74ee0..0000000000 --- a/packages/shared/widget-plugin-filtering/src/typings/mendix.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { FilterCondition } from "mendix/filters"; -import { FnName } from "./type-utils"; - -export type FilterFunction = FnName; diff --git a/packages/shared/widget-plugin-filtering/src/typings/type-utils.ts b/packages/shared/widget-plugin-filtering/src/typings/type-utils.ts deleted file mode 100644 index a61d66ebf5..0000000000 --- a/packages/shared/widget-plugin-filtering/src/typings/type-utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type FnName = T extends { name: infer Name } ? Name : never; - -export type Dispose = () => void; - -export type GConstructor = new (...args: any[]) => T; diff --git a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts b/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts deleted file mode 100644 index d61d99ac45..0000000000 --- a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DynamicValue } from "mendix"; -import { useRef } from "react"; - -export function useDefaultValue(defaultValueProp?: DynamicValue): T | undefined | null { - const defaultValueRef = useRef(null); - - if (defaultValueProp?.status !== "loading" && defaultValueRef.current === null) { - defaultValueRef.current = defaultValueProp?.value; - } - - return defaultValueRef.current; -} diff --git a/packages/shared/widget-plugin-grid/jest.config.cjs b/packages/shared/widget-plugin-grid/jest.config.cjs index 1a6d9d630f..0212d4ef21 100644 --- a/packages/shared/widget-plugin-grid/jest.config.cjs +++ b/packages/shared/widget-plugin-grid/jest.config.cjs @@ -17,6 +17,5 @@ module.exports = { moduleNameMapper: { "(.+)\\.js": "$1" }, - extensionsToTreatAsEsm: [".ts"], - testEnvironment: "jsdom" + extensionsToTreatAsEsm: [".ts"] }; diff --git a/packages/shared/widget-plugin-grid/package.json b/packages/shared/widget-plugin-grid/package.json index afae89651d..938ad42481 100644 --- a/packages/shared/widget-plugin-grid/package.json +++ b/packages/shared/widget-plugin-grid/package.json @@ -1,6 +1,6 @@ { "name": "@mendix/widget-plugin-grid", - "version": "0.2.0", + "version": "1.0.0", "description": "Data grid related code.", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -26,13 +26,15 @@ } }, "scripts": { - "compile": "tsc", + "build": "tsc", "dev": "tsc --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "tsc", "test": "jest" }, + "dependencies": { + "@mendix/widget-plugin-mobx-kit": "workspace:^" + }, "devDependencies": { "@mendix/eslint-config-web-widgets": "workspace:*", "@mendix/prettier-config-web-widgets": "workspace:*", diff --git a/packages/pluggableWidgets/datagrid-web/src/__tests__/DatasourceController.spec.ts b/packages/shared/widget-plugin-grid/src/__tests__/DatasourceController.spec.ts similarity index 97% rename from packages/pluggableWidgets/datagrid-web/src/__tests__/DatasourceController.spec.ts rename to packages/shared/widget-plugin-grid/src/__tests__/DatasourceController.spec.ts index 1b03961715..c2cbe59476 100644 --- a/packages/pluggableWidgets/datagrid-web/src/__tests__/DatasourceController.spec.ts +++ b/packages/shared/widget-plugin-grid/src/__tests__/DatasourceController.spec.ts @@ -2,7 +2,7 @@ import { GateProvider } from "@mendix/widget-plugin-mobx-kit/GateProvider"; import { ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/main"; import { list } from "@mendix/widget-plugin-test-utils"; import { ListValue } from "mendix"; -import { DatasourceController } from "../controllers/DatasourceController"; +import { DatasourceController } from "../query/DatasourceController"; describe("DatasourceController loading states", () => { let controller: DatasourceController; diff --git a/packages/pluggableWidgets/datagrid-web/src/__tests__/RefreshController.spec.ts b/packages/shared/widget-plugin-grid/src/__tests__/RefreshController.spec.ts similarity index 96% rename from packages/pluggableWidgets/datagrid-web/src/__tests__/RefreshController.spec.ts rename to packages/shared/widget-plugin-grid/src/__tests__/RefreshController.spec.ts index 1d6240d2b9..63833d4c97 100644 --- a/packages/pluggableWidgets/datagrid-web/src/__tests__/RefreshController.spec.ts +++ b/packages/shared/widget-plugin-grid/src/__tests__/RefreshController.spec.ts @@ -1,5 +1,5 @@ import { ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/main"; -import { RefreshController } from "../controllers/RefreshController"; +import { RefreshController } from "../query/RefreshController"; describe("RefreshController", () => { let host: ReactiveControllerHost; diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceController.ts b/packages/shared/widget-plugin-grid/src/query/DatasourceController.ts similarity index 90% rename from packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceController.ts rename to packages/shared/widget-plugin-grid/src/query/DatasourceController.ts index 803618e756..fbee1be307 100644 --- a/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceController.ts +++ b/packages/shared/widget-plugin-grid/src/query/DatasourceController.ts @@ -1,10 +1,11 @@ import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; import { ListValue, ValueStatus } from "mendix"; -import { action, autorun, makeAutoObservable } from "mobx"; +import { action, autorun, computed, IComputedValue, makeAutoObservable } from "mobx"; import { QueryController } from "./query-controller"; type Gate = DerivedPropsGate<{ datasource: ListValue }>; + type DatasourceControllerSpec = { gate: Gate }; export class DatasourceController implements ReactiveController, QueryController { @@ -58,7 +59,7 @@ export class DatasourceController implements ReactiveController, QueryController return this.datasource.status === "loading"; } - private get datasource(): ListValue { + get datasource(): ListValue { return this.gate.props.datasource; } @@ -90,12 +91,13 @@ export class DatasourceController implements ReactiveController, QueryController } /** - * Returns a new copy of the controller. + * Returns computed value that holds controller copy. * Recomputes the copy every time the datasource changes. */ - get computedCopy(): DatasourceController { - const [copy] = [this.datasource].map(() => Object.create(this)); - return copy; + get derivedQuery(): IComputedValue { + const data = (): DatasourceController => [this.datasource].map(() => Object.create(this))[0]; + + return computed(data); } setup(): () => void { diff --git a/packages/shared/widget-plugin-grid/src/query/PaginationController.ts b/packages/shared/widget-plugin-grid/src/query/PaginationController.ts new file mode 100644 index 0000000000..ff68aae944 --- /dev/null +++ b/packages/shared/widget-plugin-grid/src/query/PaginationController.ts @@ -0,0 +1,88 @@ +import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; +import { QueryController } from "./query-controller"; + +type PaginationEnum = "buttons" | "virtualScrolling" | "loadMore"; + +type ShowPagingButtonsEnum = "always" | "auto"; + +type PaginationKind = `${PaginationEnum}.${ShowPagingButtonsEnum}`; + +interface StaticProps { + pageSize: number; + pagination: PaginationEnum; + showPagingButtons: ShowPagingButtonsEnum; + showTotalCount: boolean; +} + +/** NOTE: Use gate for dynamic props */ +type PaginationControllerSpec = StaticProps & { + query: QueryController; +}; + +export class PaginationController implements ReactiveController { + private readonly _pageSize: number; + private readonly _query: QueryController; + readonly pagination: PaginationEnum; + readonly paginationKind: PaginationKind; + readonly showPagingButtons: ShowPagingButtonsEnum; + readonly showTotalCount: boolean; + + constructor(host: ReactiveControllerHost, spec: PaginationControllerSpec) { + host.addController(this); + this._pageSize = spec.pageSize; + this._query = spec.query; + this.pagination = spec.pagination; + this.showPagingButtons = spec.showPagingButtons; + this.showTotalCount = spec.showTotalCount; + this.paginationKind = `${this.pagination}.${this.showPagingButtons}`; + this._setInitParams(); + } + + get isLimitBased(): boolean { + return this.pagination === "virtualScrolling" || this.pagination === "loadMore"; + } + + get pageSize(): number { + return this._pageSize; + } + + get currentPage(): number { + const { + _query: { limit, offset }, + pageSize + } = this; + return this.isLimitBased ? limit / pageSize : offset / pageSize; + } + + get showPagination(): boolean { + switch (this.paginationKind) { + case "buttons.always": + return true; + case "buttons.auto": { + const { totalCount = -1 } = this._query; + return totalCount > this._query.limit; + } + default: + return this.showTotalCount; + } + } + + private _setInitParams(): void { + if (this.pagination === "buttons" || this.showTotalCount) { + this._query.requestTotalCount(true); + } + + this._query.setPageSize(this.pageSize); + } + + setup(): void {} + + setPage = (computePage: (prevPage: number) => number): void => { + const newPage = computePage(this.currentPage); + if (this.isLimitBased) { + this._query.setLimit(newPage * this.pageSize); + } else { + this._query.setOffset(newPage * this.pageSize); + } + }; +} diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/RefreshController.ts b/packages/shared/widget-plugin-grid/src/query/RefreshController.ts similarity index 100% rename from packages/pluggableWidgets/datagrid-web/src/controllers/RefreshController.ts rename to packages/shared/widget-plugin-grid/src/query/RefreshController.ts diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/query-controller.ts b/packages/shared/widget-plugin-grid/src/query/query-controller.ts similarity index 100% rename from packages/pluggableWidgets/datagrid-web/src/controllers/query-controller.ts rename to packages/shared/widget-plugin-grid/src/query/query-controller.ts diff --git a/packages/shared/widget-plugin-hooks/package.json b/packages/shared/widget-plugin-hooks/package.json index b66000cd0c..e646777328 100644 --- a/packages/shared/widget-plugin-hooks/package.json +++ b/packages/shared/widget-plugin-hooks/package.json @@ -24,11 +24,10 @@ } }, "scripts": { - "compile": "tsc", + "build": "tsc", "dev": "tsc --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "tsc", "test": "jest" }, "devDependencies": { diff --git a/packages/shared/widget-plugin-mobx-kit/package.json b/packages/shared/widget-plugin-mobx-kit/package.json index 8285911ef1..e85497d339 100644 --- a/packages/shared/widget-plugin-mobx-kit/package.json +++ b/packages/shared/widget-plugin-mobx-kit/package.json @@ -28,7 +28,6 @@ "dev": "tsc -p tsconfig.build.json --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "pnpm build", "test": "jest" }, "dependencies": { diff --git a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts index b3d899c210..7b0caf4fe4 100644 --- a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts +++ b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts @@ -1,8 +1,12 @@ -export function disposeBatch(): [add: (fn: () => void) => void, disposeAll: () => void] { +type MaybeFn = (() => void) | void; + +export function disposeBatch(): [add: (fn: MaybeFn) => void, disposeAll: () => void] { const disposers = new Set<() => void>(); - const add = (fn: () => void): void => { - disposers.add(fn); + const add = (fn: MaybeFn): void => { + if (fn) { + disposers.add(fn); + } }; const disposeAll = (): void => { diff --git a/packages/shared/widget-plugin-mobx-kit/src/setupable.ts b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts new file mode 100644 index 0000000000..cb29e7ccb9 --- /dev/null +++ b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts @@ -0,0 +1,3 @@ +export interface ISetupable { + setup(): void | (() => void); +} diff --git a/packages/shared/widget-plugin-platform/package.json b/packages/shared/widget-plugin-platform/package.json index fb524af369..9cd03f5b6c 100644 --- a/packages/shared/widget-plugin-platform/package.json +++ b/packages/shared/widget-plugin-platform/package.json @@ -24,11 +24,10 @@ } }, "scripts": { - "compile": "tsc", + "build": "tsc", "dev": "tsc --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "tsc", "test": "jest" }, "devDependencies": { diff --git a/packages/shared/widget-plugin-sorting/jest.config.cjs b/packages/shared/widget-plugin-sorting/jest.config.cjs new file mode 100644 index 0000000000..1b85603e0f --- /dev/null +++ b/packages/shared/widget-plugin-sorting/jest.config.cjs @@ -0,0 +1,25 @@ +module.exports = { + modulePathIgnorePatterns: ["/dist/"], + transform: { + "^.+\\.(t|j)sx?$": [ + "@swc/jest", + { + jsc: { + transform: { + react: { + runtime: "automatic" + } + } + } + } + ] + }, + moduleDirectories: ["node_modules", "src"], + moduleNameMapper: { + "big.js": "big.js", + "(.+)\\.js": "$1" + }, + extensionsToTreatAsEsm: [".ts"], + collectCoverage: !process.env.CI, + coverageProvider: "v8" +}; diff --git a/packages/shared/widget-plugin-sorting/package.json b/packages/shared/widget-plugin-sorting/package.json index 403bef9f00..83e7f5f73b 100644 --- a/packages/shared/widget-plugin-sorting/package.json +++ b/packages/shared/widget-plugin-sorting/package.json @@ -1,6 +1,6 @@ { "name": "@mendix/widget-plugin-sorting", - "version": "1.0.0", + "version": "2.0.0", "description": "Sorting API plugin.", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -24,19 +24,23 @@ } }, "scripts": { - "compile": "tsc", + "build": "tsc", "dev": "tsc --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "tsc" + "test": "jest" }, "dependencies": { + "@mendix/widget-plugin-component-kit": "workspace:*", + "@mendix/widget-plugin-mobx-kit": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*", "mobx": "6.12.3", "mobx-react-lite": "4.0.7" }, "devDependencies": { "@mendix/eslint-config-web-widgets": "workspace:*", "@mendix/prettier-config-web-widgets": "workspace:*", - "@mendix/tsconfig-web-widgets": "workspace:*" + "@mendix/tsconfig-web-widgets": "workspace:*", + "@mendix/widget-plugin-test-utils": "workspace:*" } } diff --git a/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts b/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts new file mode 100644 index 0000000000..8afe628c5e --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts @@ -0,0 +1,223 @@ +import { attrId } from "@mendix/widget-plugin-test-utils"; +import { SortStoreHost } from "../stores/SortStoreHost"; +import { ObservableSortStore, SortInstruction } from "../types/store"; + +describe("SortStoreHost", () => { + let sortStoreHost: SortStoreHost; + let mockStore: ObservableSortStore; + + beforeEach(() => { + sortStoreHost = new SortStoreHost(); + mockStore = { + sortOrder: [ + [attrId("attr1"), "asc"], + [attrId("attr2"), "desc"] + ] as SortInstruction[] + }; + }); + + describe("constructor", () => { + it("should initialize with empty state", () => { + expect(sortStoreHost.sortOrder).toEqual([]); + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should make properties observable/computed correctly", () => { + // Test that the object is properly observable by checking MobX behavior + expect(sortStoreHost).toBeDefined(); + expect(typeof sortStoreHost.sortOrder).toBe("object"); + expect(typeof sortStoreHost.usedBy).toBe("object"); + }); + }); + + describe("sortOrder", () => { + it("should return empty array when no store is observed", () => { + expect(sortStoreHost.sortOrder).toEqual([]); + }); + + it("should return sort order from observed store", () => { + sortStoreHost.observe(mockStore); + expect(sortStoreHost.sortOrder).toEqual([ + [attrId("attr1"), "asc"], + [attrId("attr2"), "desc"] + ]); + }); + + it("should return empty array after unobserving", () => { + sortStoreHost.observe(mockStore); + sortStoreHost.unobserve(); + expect(sortStoreHost.sortOrder).toEqual([]); + }); + }); + + describe("observe", () => { + it("should set the internal store", () => { + sortStoreHost.observe(mockStore); + expect(sortStoreHost.sortOrder).toBe(mockStore.sortOrder); + }); + + it("should replace previously observed store", () => { + const anotherMockStore: ObservableSortStore = { + sortOrder: [[attrId("attr3"), "asc"]] as SortInstruction[] + }; + + sortStoreHost.observe(mockStore); + expect(sortStoreHost.sortOrder).toBe(mockStore.sortOrder); + + sortStoreHost.observe(anotherMockStore); + expect(sortStoreHost.sortOrder).toBe(anotherMockStore.sortOrder); + }); + }); + + describe("unobserve", () => { + it("should clear the internal store", () => { + sortStoreHost.observe(mockStore); + sortStoreHost.unobserve(); + expect(sortStoreHost.sortOrder).toEqual([]); + }); + + it("should be safe to call when no store is observed", () => { + expect(() => sortStoreHost.unobserve()).not.toThrow(); + expect(sortStoreHost.sortOrder).toEqual([]); + }); + }); + + describe("lock", () => { + it("should add id to usedBy list and return unlock function", () => { + const unlock = sortStoreHost.lock("widget1"); + + expect(sortStoreHost.usedBy).toBe("widget1"); + expect(typeof unlock).toBe("function"); + }); + + it("should return unlock function that removes the id", () => { + const unlock = sortStoreHost.lock("widget1"); + expect(sortStoreHost.usedBy).toBe("widget1"); + + unlock(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should handle multiple locks correctly", () => { + const unlock1 = sortStoreHost.lock("widget1"); + const unlock2 = sortStoreHost.lock("widget2"); + + // First widget should be the one returned by usedBy + expect(sortStoreHost.usedBy).toBe("widget1"); + + unlock1(); + expect(sortStoreHost.usedBy).toBe("widget2"); + + unlock2(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should not add duplicate ids", () => { + sortStoreHost.lock("widget1"); + sortStoreHost.lock("widget1"); + + expect(sortStoreHost.usedBy).toBe("widget1"); + // Internal _usedBy array should only have one entry + // We can test this indirectly by checking that unlocking once clears it + const unlock = sortStoreHost.lock("widget1"); + unlock(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + }); + + describe("usedBy", () => { + it("should return null when no widgets are using the store", () => { + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should return the first widget id when multiple widgets are using the store", () => { + sortStoreHost.lock("widget1"); + sortStoreHost.lock("widget2"); + sortStoreHost.lock("widget3"); + + expect(sortStoreHost.usedBy).toBe("widget1"); + }); + + it("should return the next widget id after the first is removed", () => { + const unlock1 = sortStoreHost.lock("widget1"); + const unlock2 = sortStoreHost.lock("widget2"); + + expect(sortStoreHost.usedBy).toBe("widget1"); + + unlock1(); + expect(sortStoreHost.usedBy).toBe("widget2"); + + unlock2(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + }); + + describe("private methods", () => { + it("should handle removing non-existent id gracefully", () => { + // This tests the _remove method indirectly + const unlock = sortStoreHost.lock("widget1"); + + // Call unlock twice - second call should be safe + unlock(); + expect(() => unlock()).not.toThrow(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should maintain correct order when removing from middle", () => { + const unlock1 = sortStoreHost.lock("widget1"); + const unlock2 = sortStoreHost.lock("widget2"); + const unlock3 = sortStoreHost.lock("widget3"); + + expect(sortStoreHost.usedBy).toBe("widget1"); + + // Remove middle item + unlock2(); + expect(sortStoreHost.usedBy).toBe("widget1"); + + unlock1(); + expect(sortStoreHost.usedBy).toBe("widget3"); + + unlock3(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + }); + + describe("integration scenarios", () => { + it("should work correctly when combining observe and lock", () => { + sortStoreHost.observe(mockStore); + const unlock = sortStoreHost.lock("widget1"); + + expect(sortStoreHost.sortOrder).toEqual([ + [attrId("attr1"), "asc"], + [attrId("attr2"), "desc"] + ]); + expect(sortStoreHost.usedBy).toBe("widget1"); + + sortStoreHost.unobserve(); + expect(sortStoreHost.sortOrder).toEqual([]); + expect(sortStoreHost.usedBy).toBe("widget1"); // Lock should remain + + unlock(); + expect(sortStoreHost.usedBy).toBeNull(); + }); + + it("should handle store changes after observation", () => { + const mutableStore = { + sortOrder: [[attrId("attr1"), "asc"]] as SortInstruction[] + }; + + sortStoreHost.observe(mutableStore); + expect(sortStoreHost.sortOrder).toEqual([[attrId("attr1"), "asc"]]); + + // Simulate store change + mutableStore.sortOrder = [ + [attrId("attr2"), "desc"], + [attrId("attr3"), "asc"] + ]; + expect(sortStoreHost.sortOrder).toEqual([ + [attrId("attr2"), "desc"], + [attrId("attr3"), "asc"] + ]); + }); + }); +}); diff --git a/packages/shared/widget-plugin-sorting/src/context.ts b/packages/shared/widget-plugin-sorting/src/context.ts deleted file mode 100644 index cf733dcdfc..0000000000 --- a/packages/shared/widget-plugin-sorting/src/context.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Context, createContext, useContext } from "react"; -import { SortingStoreInterface } from "./typings"; -import { Result, error, value } from "./result-meta"; -import { APIError } from "./errors"; - -export interface SortAPI { - version: 1; - store: Result; -} - -const SORT_PATH = "com.mendix.widgets.web.sortable.sortContext"; - -export function getGlobalSortContext(): Context { - return ((window as any)[SORT_PATH] ??= createContext(null)); -} - -export function useSortAPI(): Result { - const api = useContext(getGlobalSortContext()); - return api === null ? error({ code: "", message: "Out of context" }) : value(api); -} diff --git a/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts b/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts new file mode 100644 index 0000000000..2e4681253b --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts @@ -0,0 +1,69 @@ +import { action, computed, makeObservable, observable, reaction } from "mobx"; +import { BasicSortStore, ListAttributeId } from "../types/store"; + +export class SingleSortController { + private readonly _sortOrderStore: BasicSortStore; + private readonly emptyOptionCaption: string; + + direction: "asc" | "desc" = "asc"; + + constructor(spec: { store: BasicSortStore; emptyOptionCaption?: string }) { + const { store, emptyOptionCaption } = spec; + this.emptyOptionCaption = emptyOptionCaption ?? "Select an attribute"; + this._sortOrderStore = store; + + const [instruction] = store.sortOrder; + if (instruction) { + [, this.direction] = instruction; + } + + makeObservable(this, { + options: computed, + selected: computed, + direction: observable, + toggleDirection: action, + select: action, + _setDirection: action + }); + } + + get options(): Array<{ caption: string; value: string }> { + const empty = { caption: this.emptyOptionCaption, value: "none" }; + return [empty, ...this._sortOrderStore.options]; + } + + get selected(): ListAttributeId | null { + const [instruction] = this._sortOrderStore.sortOrder; + return instruction ? instruction[0] : null; + } + + private _setDirection = (direction: "asc" | "desc" | null): void => { + if (direction === null) { + return; + } + this.direction = direction; + }; + + toggleDirection = (): void => { + this.direction = this.direction === "asc" ? "desc" : "asc"; + if (this.selected) { + this._sortOrderStore.replace([this.selected, this.direction]); + } + }; + + select = (value: string): void => { + if (value === "none") { + this._sortOrderStore.replace(); + } else { + this._sortOrderStore.replace([value as ListAttributeId, this.direction]); + } + }; + + setup(): () => void { + // Sync direction with the store + return reaction(() => { + const [instruction] = this._sortOrderStore.sortOrder; + return instruction ? instruction[1] : null; + }, this._setDirection); + } +} diff --git a/packages/shared/widget-plugin-sorting/src/errors.ts b/packages/shared/widget-plugin-sorting/src/errors.ts deleted file mode 100644 index ab58ca1b05..0000000000 --- a/packages/shared/widget-plugin-sorting/src/errors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface APIError { - code: unknown; - message: string; -} diff --git a/packages/shared/widget-plugin-sorting/src/helpers/SortStoreProvider.ts b/packages/shared/widget-plugin-sorting/src/helpers/SortStoreProvider.ts new file mode 100644 index 0000000000..93ceb84b56 --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/helpers/SortStoreProvider.ts @@ -0,0 +1,32 @@ +import { AttributeMetaData, DynamicValue } from "mendix"; +import { SortOrderStore } from "../stores/SortOrderStore"; +import { SortStoreHost } from "../stores/SortStoreHost"; +import { SortInstruction } from "../types/store"; + +interface SortStoreProviderSpec { + host: SortStoreHost; + initSortOrder?: SortInstruction[]; + attributes: Array<{ + attribute: AttributeMetaData; + caption?: DynamicValue; + }>; +} + +export class SortStoreProvider { + private _host: SortStoreHost; + store: SortOrderStore; + + constructor(spec: SortStoreProviderSpec) { + this._host = spec.host; + const options = spec.attributes.map(item => ({ + value: item.attribute.id, + caption: item.caption?.value ?? "empty" + })); + this.store = new SortOrderStore({ options, initSortOrder: spec.initSortOrder }); + } + + setup(): () => void { + this._host.observe(this.store); + return () => this._host.unobserve(); + } +} diff --git a/packages/shared/widget-plugin-sorting/src/helpers/useSortControl.ts b/packages/shared/widget-plugin-sorting/src/helpers/useSortControl.ts deleted file mode 100644 index dcbefc27b6..0000000000 --- a/packages/shared/widget-plugin-sorting/src/helpers/useSortControl.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useMemo, useCallback } from "react"; -import { Option, SortDirection, SortingStoreInterface } from "../typings"; - -interface HookProps { - emptyOptionCaption?: string; -} - -interface ControlProps { - value: string | null; - options: Option[]; - direction: SortDirection; - onSelect: (option: Option) => void; - onDirectionClick: () => void; -} - -export function useSortControl(props: HookProps, store: SortingStoreInterface): ControlProps { - const options = useMemo(() => { - return [{ caption: props.emptyOptionCaption ?? "", value: null } as Option, ...store.options]; - }, [store.options, props.emptyOptionCaption]); - - return { - value: store.value, - options, - direction: store.direction, - onSelect: useCallback( - option => { - store.select(option.value); - }, - [store] - ), - onDirectionClick: store.toggleDirection - }; -} diff --git a/packages/shared/widget-plugin-sorting/src/helpers/useSortingStore.ts b/packages/shared/widget-plugin-sorting/src/helpers/useSortingStore.ts deleted file mode 100644 index 6cfd28bdcd..0000000000 --- a/packages/shared/widget-plugin-sorting/src/helpers/useSortingStore.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useSortAPI } from "../context"; -import { SortingStoreInterface } from "../typings"; -import { Result, value, error } from "../result-meta"; -import { APIError } from "../errors"; - -export function useSortingStore(): Result { - const api = useSortAPI(); - if (api.hasError) { - return error(api.error); - } - - const store = api.value.store; - if (store.hasError) { - return error(store.error); - } - - return value(store.value); -} diff --git a/packages/shared/widget-plugin-sorting/src/providers/SortAPIProvider.ts b/packages/shared/widget-plugin-sorting/src/providers/SortAPIProvider.ts deleted file mode 100644 index 67381d1d77..0000000000 --- a/packages/shared/widget-plugin-sorting/src/providers/SortAPIProvider.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { makeObservable, computed } from "mobx"; -import { DynamicValue, ListAttributeValue, ListValue } from "mendix"; -import { SortAPI } from "../context"; -import { value } from "../result-meta"; -import { SortingStore } from "../stores/SortingStore"; -import { SortInstruction } from "../typings"; - -export interface SortListType { - attribute: ListAttributeValue; - caption: DynamicValue; -} - -interface Props { - datasource: ListValue; - sortList: SortListType[]; -} - -export class SortAPIProvider { - private store: SortingStore; - context: SortAPI; - - constructor(props: Props) { - const options = props.sortList.map(item => ({ - value: item.attribute.id, - caption: `${item.caption?.value}` - })); - - this.store = new SortingStore(options, props.datasource.sortOrder); - - this.context = { - version: 1, - store: value(this.store) - }; - - makeObservable(this, { sortOrder: computed }); - } - - get sortOrder(): SortInstruction[] { - return this.store.sortOrder; - } -} diff --git a/packages/shared/widget-plugin-sorting/src/react/context.ts b/packages/shared/widget-plugin-sorting/src/react/context.ts new file mode 100644 index 0000000000..8fb9daa841 --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/react/context.ts @@ -0,0 +1,52 @@ +import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst"; +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { Context, createContext, useContext, useEffect } from "react"; +import { SortStoreHost } from "../stores/SortStoreHost"; +import { SortInstruction } from "../types/store"; +import { Result, error, value } from "./result-meta"; + +export interface SortAPI { + version: 1; + host: SortStoreHost; + initSortOrder?: SortInstruction[]; +} + +const SORT_PATH = "com.mendix.widgets.web.sortable.sortContext"; + +export function getGlobalSortContext(): Context { + const scope = window.top === window ? window : window.top; + return ((scope as any)[SORT_PATH] ??= createContext(null)); +} + +export function useSortAPI(): Result { + const api = useContext(getGlobalSortContext()); + if (api === null) { + return error(new Error("Error: widget is out of context. Please place the widget inside the Gallery header.")); + } + return value(api); +} + +export function useLockSortAPI(api: SortAPI): Result { + const id = useLock(api); + + if (api.host.usedBy !== id) { + return error( + new Error( + `Error: Sort API is already in use by another widget. Remove other sort widgets and refresh the page.` + ) + ); + } + + return value(api); +} + +function useLock({ host }: SortAPI): string { + const [unlock, id] = useConst(() => { + const id = `useLock@${generateUUID()}`; + return [host.lock(id), id] as const; + }); + + useEffect(() => unlock, [unlock]); + + return id; +} diff --git a/packages/shared/widget-plugin-sorting/src/react/hocs/withLinkedSortStore.tsx b/packages/shared/widget-plugin-sorting/src/react/hocs/withLinkedSortStore.tsx new file mode 100644 index 0000000000..70c63c493f --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/react/hocs/withLinkedSortStore.tsx @@ -0,0 +1,30 @@ +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { AttributeMetaData, DynamicValue } from "mendix"; +import { createElement, FC } from "react"; +import { SortStoreProvider } from "../../helpers/SortStoreProvider"; +import { BasicSortStore } from "../../types/store"; +import { SortAPI } from "../context"; + +interface RequiredProps { + attributes: Array<{ + attribute: AttributeMetaData; + caption?: DynamicValue; + }>; +} + +export function withLinkedSortStore

( + Component: FC

+): FC

{ + return function SortStoreProviderHost(props) { + const { store } = useSetup( + () => + new SortStoreProvider({ + host: props.sortAPI.host, + initSortOrder: props.sortAPI.initSortOrder, + attributes: props.attributes + }) + ); + + return ; + }; +} diff --git a/packages/shared/widget-plugin-sorting/src/react/hocs/withSortAPI.tsx b/packages/shared/widget-plugin-sorting/src/react/hocs/withSortAPI.tsx new file mode 100644 index 0000000000..d0119acac4 --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/react/hocs/withSortAPI.tsx @@ -0,0 +1,35 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { ErrorBoundary } from "@mendix/widget-plugin-component-kit/ErrorBoundary"; +import { observer } from "mobx-react-lite"; +import { createElement, FC } from "react"; +import { SortAPI, useLockSortAPI, useSortAPI } from "../context"; + +export function withSortAPI

(Component: FC

): FC

{ + const SortAPIGuard = observer(function SortAPIGuard(props: P & { sortAPI: SortAPI }): React.ReactElement { + const sortAPI = useLockSortAPI(props.sortAPI); + + if (sortAPI.hasError) { + return {sortAPI.error.message}; + } + + return ; + }); + + function SortAPIInjector(props: P): React.ReactElement { + const sortAPI = useSortAPI(); + + if (sortAPI.hasError) { + return {sortAPI.error.message}; + } + + return ; + } + + return function (props): React.ReactElement { + return ( + + + + ); + }; +} diff --git a/packages/shared/widget-plugin-sorting/src/result-meta.ts b/packages/shared/widget-plugin-sorting/src/react/result-meta.ts similarity index 100% rename from packages/shared/widget-plugin-sorting/src/result-meta.ts rename to packages/shared/widget-plugin-sorting/src/react/result-meta.ts diff --git a/packages/shared/widget-plugin-sorting/src/react/useSortSelect.ts b/packages/shared/widget-plugin-sorting/src/react/useSortSelect.ts new file mode 100644 index 0000000000..1ee75efbe9 --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/react/useSortSelect.ts @@ -0,0 +1,33 @@ +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { SingleSortController } from "../controllers/SingleSortController"; +import { BasicSortStore, SortDirection } from "../types/store"; + +interface HookProps { + emptyOptionCaption?: string; + sortStore: BasicSortStore; +} + +interface ControlProps { + value: string | null; + options: Array<{ + caption: string; + value: string; + }>; + direction: SortDirection; + onSelect: (value: string) => void; + onDirectionClick: () => void; +} + +export function useSortSelect(props: HookProps): ControlProps { + const ctrl = useSetup( + () => new SingleSortController({ store: props.sortStore, emptyOptionCaption: props.emptyOptionCaption }) + ); + + return { + value: ctrl.selected, + options: ctrl.options, + direction: ctrl.direction, + onSelect: ctrl.select, + onDirectionClick: ctrl.toggleDirection + }; +} diff --git a/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts b/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts new file mode 100644 index 0000000000..1cbad5fc6d --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts @@ -0,0 +1,43 @@ +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { action, computed, makeObservable, observable } from "mobx"; +import { BasicSortStore, Option, SortInstruction } from "../types/store"; + +export class SortOrderStore implements BasicSortStore { + private readonly _sortOrder: SortInstruction[] = []; + + readonly id = `SortOrderStore@${generateUUID()}`; + readonly options: Option[]; + + constructor(spec: { options?: Option[]; initSortOrder?: SortInstruction[] } = {}) { + const { options = [], initSortOrder = [] } = spec; + + this.options = [...options]; + this._sortOrder = [...initSortOrder]; + + makeObservable(this, { + _sortOrder: observable, + sortOrder: computed, + replace: action, + push: action, + remove: action + }); + } + + get sortOrder(): SortInstruction[] { + return [...this._sortOrder]; + } + + replace(...order: SortInstruction[]): void { + this._sortOrder.splice(0, this._sortOrder.length, ...order); + } + + push(...item: SortInstruction[]): void { + this._sortOrder.push(...item); + } + + remove(index: number): void { + if (index >= 0 && index < this._sortOrder.length) { + this._sortOrder.splice(index, 1); + } + } +} diff --git a/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts b/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts new file mode 100644 index 0000000000..16fe5d53e4 --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts @@ -0,0 +1,58 @@ +import { action, computed, makeObservable, observable } from "mobx"; +import { ObservableSortStore, SortInstruction } from "../types/store"; + +export class SortStoreHost { + private _store: ObservableSortStore | null = null; + private _usedBy: string[] = []; + + get sortOrder(): SortInstruction[] { + return this._store?.sortOrder ?? []; + } + + constructor() { + makeObservable(this, { + sortOrder: computed, + lock: action, + _add: action, + _remove: action, + _usedBy: observable, + usedBy: computed, + observe: action, + unobserve: action + }); + } + + // TODO: toJSON + + // TODO: fromJSON + + observe(store: ObservableSortStore): void { + this._store = store; + } + + unobserve(): void { + this._store = null; + } + + lock(id: string): () => void { + this._add(id); + return () => this._remove(id); + } + + private _add(id: string): void { + if (this._usedBy.indexOf(id) === -1) { + this._usedBy.push(id); + } + } + + private _remove(id: string): void { + const index = this._usedBy.indexOf(id); + if (index !== -1) { + this._usedBy.splice(index, 1); + } + } + + get usedBy(): string | null { + return this._usedBy.at(0) ?? null; + } +} diff --git a/packages/shared/widget-plugin-sorting/src/stores/SortingStore.ts b/packages/shared/widget-plugin-sorting/src/stores/SortingStore.ts deleted file mode 100644 index ee0650010b..0000000000 --- a/packages/shared/widget-plugin-sorting/src/stores/SortingStore.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { makeObservable, computed, observable, action } from "mobx"; -import { ListAttributeId, SortDirection, SortingStoreInterface, SortInstruction, Option } from "../typings"; - -export class SortingStore implements SortingStoreInterface { - private _direction: SortDirection; - private _selected: ListAttributeId | null; - options: Option[]; - - constructor(options: Option[], initSort: SortInstruction[]) { - this.options = options; - [[this._selected, this._direction] = [null, "asc"]] = initSort; - - makeObservable(this, { - options: observable.ref, - _selected: observable, - _direction: observable, - value: computed, - sortOrder: computed, - direction: computed, - select: action, - toggleDirection: action - }); - } - - get value(): ListAttributeId | null { - return this._selected; - } - - get direction(): SortDirection { - return this._direction; - } - - get sortOrder(): SortInstruction[] { - return this._selected ? [[this._selected, this._direction]] : []; - } - - select = (value: ListAttributeId | null): void => { - this._selected = value; - }; - - toggleDirection = (): void => { - this._direction = this._direction === "asc" ? "desc" : "asc"; - }; -} diff --git a/packages/shared/widget-plugin-sorting/src/types/store.ts b/packages/shared/widget-plugin-sorting/src/types/store.ts new file mode 100644 index 0000000000..642cc8bdeb --- /dev/null +++ b/packages/shared/widget-plugin-sorting/src/types/store.ts @@ -0,0 +1,29 @@ +import type { ListAttributeValue } from "mendix"; + +export type SortDirection = "asc" | "desc"; + +export type ListAttributeId = ListAttributeValue["id"]; + +export type SortInstruction = [id: ListAttributeId, dir: SortDirection]; + +export type Option = { + caption: string; + value: ListAttributeId; +}; + +export interface ObservableSortStore { + sortOrder: SortInstruction[]; +} + +export interface BasicSortStore extends ObservableSortStore { + options: Option[]; + sortOrder: SortInstruction[]; + push(...item: SortInstruction[]): void; + remove(index: number): void; + replace(...item: SortInstruction[]): void; +} + +export interface Serializable { + toJSON(): unknown; + fromJSON(json: unknown): void; +} diff --git a/packages/shared/widget-plugin-sorting/src/typings.ts b/packages/shared/widget-plugin-sorting/src/typings.ts deleted file mode 100644 index 0791257c2e..0000000000 --- a/packages/shared/widget-plugin-sorting/src/typings.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ListAttributeValue } from "mendix"; - -export type SortDirection = "asc" | "desc"; -export type ListAttributeId = ListAttributeValue["id"]; -export type SortInstruction = [id: ListAttributeId, dir: SortDirection]; - -export type Option = { - caption: string; - value: ListAttributeId | null; -}; - -export interface SortingStoreInterface { - options: Option[]; - value: ListAttributeId | null; - direction: SortDirection; - select(value: ListAttributeId | null): void; - toggleDirection(): void; -} diff --git a/packages/shared/widget-plugin-test-utils/package.json b/packages/shared/widget-plugin-test-utils/package.json index efe6d8c0a3..f1ad17bb28 100644 --- a/packages/shared/widget-plugin-test-utils/package.json +++ b/packages/shared/widget-plugin-test-utils/package.json @@ -26,7 +26,6 @@ "dev": "tsc -p tsconfig.build.json --watch", "format": "prettier --write .", "lint": "eslint src/ package.json", - "prepare": "pnpm build", "test": "jest" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af5895134d..ec63a1a27b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -233,7 +233,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.1.6))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))(react@18.2.0)(tslib@2.8.1) '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets @@ -380,6 +380,10 @@ importers: version: 16.0.0 packages/pluggableWidgets/accessibility-helper-web: + dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -389,7 +393,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -414,7 +418,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -433,6 +437,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -451,7 +458,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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.0)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -464,6 +471,9 @@ importers: packages/pluggableWidgets/badge-button-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -476,7 +486,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -492,6 +502,9 @@ importers: packages/pluggableWidgets/badge-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -504,7 +517,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -523,6 +536,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -541,7 +557,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -569,7 +585,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -594,6 +610,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -612,7 +631,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -643,7 +662,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -671,6 +690,9 @@ importers: packages/pluggableWidgets/carousel-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit '@types/react-test-renderer': specifier: ^18.0.7 version: 18.0.7 @@ -692,7 +714,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -753,7 +775,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -790,6 +812,12 @@ importers: '@mendix/time-series-chart-web': specifier: workspace:* version: link:../time-series-chart-web + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -821,7 +849,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -846,6 +874,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -864,7 +895,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -898,7 +929,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -929,9 +960,15 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit '@mendix/widget-plugin-mobx-kit': specifier: workspace:* version: link:../../shared/widget-plugin-mobx-kit + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform classnames: specifier: ^2.3.2 version: 2.3.2 @@ -944,7 +981,7 @@ importers: version: 18.0.1 '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -981,7 +1018,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1006,6 +1043,9 @@ importers: packages/pluggableWidgets/datagrid-dropdown-filter-web: dependencies: + '@mendix/widget-plugin-dropdown-filter': + specifier: workspace:^ + version: link:../../shared/widget-plugin-dropdown-filter '@mendix/widget-plugin-external-events': specifier: workspace:* version: link:../../shared/widget-plugin-external-events @@ -1024,7 +1064,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1064,7 +1104,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1098,6 +1138,9 @@ importers: '@mendix/widget-plugin-hooks': specifier: workspace:* version: link:../../shared/widget-plugin-hooks + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:^ + version: link:../../shared/widget-plugin-mobx-kit '@mendix/widget-plugin-platform': specifier: workspace:* version: link:../../shared/widget-plugin-platform @@ -1109,7 +1152,7 @@ importers: version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) mobx-react-lite: specifier: 4.0.7 - version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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)(typescript@5.8.2))(react@18.2.0) + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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) devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -1119,7 +1162,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1135,6 +1178,9 @@ importers: '@floating-ui/react': specifier: ^0.26.27 version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit '@mendix/widget-plugin-external-events': specifier: workspace:* version: link:../../shared/widget-plugin-external-events @@ -1164,7 +1210,7 @@ importers: version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) mobx-react-lite: specifier: 4.0.7 - version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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)(typescript@5.8.2))(react@18.2.0) + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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) nanoevents: specifier: ^9.0.0 version: 9.0.0 @@ -1177,7 +1223,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1190,6 +1236,12 @@ importers: packages/pluggableWidgets/document-viewer-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1217,7 +1269,7 @@ importers: version: 7.25.9(@babel/core@7.27.4) '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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) '@rollup/plugin-replace': specifier: ^6.0.2 version: 6.0.2(rollup@3.29.5) @@ -1264,6 +1316,9 @@ importers: classnames: specifier: ^2.3.2 version: 2.3.2 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) packages/pluggableWidgets/events-web: dependencies: @@ -1279,7 +1334,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1303,6 +1358,10 @@ importers: version: 7.0.3 packages/pluggableWidgets/fieldset-web: + dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -1312,7 +1371,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1328,6 +1387,9 @@ importers: packages/pluggableWidgets/file-uploader-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.2.6 version: 2.3.2 @@ -1339,7 +1401,7 @@ importers: version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) mobx-react-lite: specifier: 4.0.7 - version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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)(typescript@5.8.2))(react@18.2.0) + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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) react-dropzone: specifier: ^14.2.3 version: 14.2.9(react@18.2.0) @@ -1352,7 +1414,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1377,12 +1439,18 @@ importers: packages/pluggableWidgets/gallery-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit '@mendix/widget-plugin-external-events': specifier: workspace:* version: link:../../shared/widget-plugin-external-events '@mendix/widget-plugin-filtering': specifier: workspace:* version: link:../../shared/widget-plugin-filtering + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:^ + version: link:../../shared/widget-plugin-mobx-kit '@mendix/widget-plugin-sorting': specifier: workspace:* version: link:../../shared/widget-plugin-sorting @@ -1394,7 +1462,7 @@ importers: version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) mobx-react-lite: specifier: 4.0.7 - version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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)(typescript@5.8.2))(react@18.2.0) + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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) devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -1404,7 +1472,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1425,6 +1493,13 @@ importers: version: 7.0.3 packages/pluggableWidgets/google-tag-web: + dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -1434,7 +1509,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1444,6 +1519,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1465,7 +1543,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1481,6 +1559,9 @@ importers: packages/pluggableWidgets/html-element-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit dompurify: specifier: ^3.2.4 version: 3.2.4 @@ -1493,7 +1574,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1503,6 +1584,9 @@ importers: packages/pluggableWidgets/image-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1518,7 +1602,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1534,6 +1618,9 @@ importers: '@floating-ui/react': specifier: ^0.26.27 version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1546,7 +1633,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1562,6 +1649,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1580,7 +1670,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1623,7 +1713,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1651,6 +1741,9 @@ importers: packages/pluggableWidgets/markdown-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1666,7 +1759,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1691,6 +1784,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1712,7 +1808,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1731,6 +1827,9 @@ importers: '@floating-ui/react': specifier: ^0.26.27 version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit devDependencies: '@mendix/automation-utils': specifier: workspace:* @@ -1740,7 +1839,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1771,7 +1870,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1805,7 +1904,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1824,6 +1923,9 @@ importers: packages/pluggableWidgets/range-slider-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -1842,7 +1944,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -1879,7 +1981,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2018,7 +2120,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2037,6 +2139,9 @@ importers: packages/pluggableWidgets/slider-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2055,7 +2160,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2092,7 +2197,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2117,6 +2222,9 @@ importers: '@mendix/shared-charts': specifier: workspace:* version: link:../../shared/charts + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2135,7 +2243,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2151,6 +2259,9 @@ importers: packages/pluggableWidgets/timeline-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2163,7 +2274,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2182,6 +2293,9 @@ importers: '@floating-ui/react': specifier: ^0.26.27 version: 0.26.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2194,7 +2308,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2210,6 +2324,9 @@ importers: packages/pluggableWidgets/tree-node-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2222,7 +2339,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2235,6 +2352,9 @@ importers: packages/pluggableWidgets/video-player-web: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit classnames: specifier: ^2.3.2 version: 2.3.2 @@ -2247,7 +2367,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 10.21.0 - version: 10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1) + version: 10.21.0(@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 @@ -2341,7 +2461,7 @@ importers: version: 4.2.0(eslint@9.23.0(jiti@2.4.2)) eslint-plugin-jest: specifier: ^28.11.0 - version: 28.11.0(@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.14.1))(typescript@5.8.2) + version: 28.11.0(@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)))(typescript@5.8.2) eslint-plugin-package-json: specifier: ^0.29.0 version: 0.29.0(@types/estree@1.0.8)(eslint@9.23.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) @@ -2364,6 +2484,34 @@ importers: specifier: ^8.29.0 version: 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) + packages/shared/filter-commons: + dependencies: + mendix: + specifier: ^10.23.70273 + version: 10.23.70273 + mobx: + specifier: 6.12.3 + version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) + mobx-react-lite: + specifier: 4.0.7 + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(react@18.2.0) + devDependencies: + '@mendix/eslint-config-web-widgets': + specifier: workspace:* + version: link:../eslint-config-web-widgets + '@mendix/prettier-config-web-widgets': + specifier: workspace:* + version: link:../prettier-config-web-widgets + '@mendix/tsconfig-web-widgets': + specifier: workspace:* + version: link:../tsconfig-web-widgets + '@mendix/widget-plugin-test-utils': + specifier: workspace:* + version: link:../widget-plugin-test-utils + '@swc/core': + specifier: ^1.7.26 + version: 1.7.26(@swc/helpers@0.5.15) + packages/shared/prettier-config-web-widgets: dependencies: '@eslint/js': @@ -2408,6 +2556,46 @@ importers: specifier: ^29.7.0 version: 29.7.0 + packages/shared/widget-plugin-dropdown-filter: + dependencies: + '@mendix/filter-commons': + specifier: workspace:* + version: link:../filter-commons + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../widget-plugin-component-kit + '@mendix/widget-plugin-external-events': + specifier: workspace:* + version: link:../widget-plugin-external-events + '@mendix/widget-plugin-hooks': + specifier: workspace:* + version: link:../widget-plugin-hooks + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:^ + version: link:../widget-plugin-mobx-kit + downshift: + specifier: ^9.0.9 + version: 9.0.9(react@18.2.0) + mendix: + specifier: ^10.23.70273 + version: 10.23.70273 + mobx: + specifier: 6.12.3 + version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) + mobx-react-lite: + specifier: 4.0.7 + version: 4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(react@18.2.0) + devDependencies: + '@mendix/eslint-config-web-widgets': + specifier: workspace:* + version: link:../eslint-config-web-widgets + '@mendix/prettier-config-web-widgets': + specifier: workspace:* + version: link:../prettier-config-web-widgets + '@mendix/tsconfig-web-widgets': + specifier: workspace:* + version: link:../tsconfig-web-widgets + packages/shared/widget-plugin-external-events: dependencies: nanoevents: @@ -2441,12 +2629,21 @@ importers: '@floating-ui/react-dom': specifier: ^2.1.2 version: 2.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@mendix/filter-commons': + specifier: workspace:* + version: link:../filter-commons + '@mendix/widget-plugin-dropdown-filter': + specifier: workspace:* + version: link:../widget-plugin-dropdown-filter '@mendix/widget-plugin-external-events': specifier: workspace:* version: link:../widget-plugin-external-events '@mendix/widget-plugin-hooks': specifier: workspace:* version: link:../widget-plugin-hooks + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:^ + version: link:../widget-plugin-mobx-kit '@mendix/widget-plugin-platform': specifier: workspace:* version: link:../widget-plugin-platform @@ -2489,6 +2686,10 @@ importers: version: 29.7.0 packages/shared/widget-plugin-grid: + dependencies: + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:^ + version: link:../widget-plugin-mobx-kit devDependencies: '@mendix/eslint-config-web-widgets': specifier: workspace:* @@ -2603,6 +2804,15 @@ importers: packages/shared/widget-plugin-sorting: dependencies: + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../widget-plugin-component-kit + '@mendix/widget-plugin-mobx-kit': + specifier: workspace:* + version: link:../widget-plugin-mobx-kit + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../widget-plugin-platform mobx: specifier: 6.12.3 version: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) @@ -2619,6 +2829,9 @@ importers: '@mendix/tsconfig-web-widgets': specifier: workspace:* version: link:../tsconfig-web-widgets + '@mendix/widget-plugin-test-utils': + specifier: workspace:* + version: link:../widget-plugin-test-utils packages/shared/widget-plugin-test-utils: dependencies: @@ -3901,16 +4114,16 @@ packages: resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-array@0.20.0': - resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + '@eslint/config-array@0.20.1': + resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/config-helpers@0.2.1': resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.2': - resolution: {integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==} + '@eslint/config-helpers@0.2.3': + resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.12.0': @@ -6728,6 +6941,11 @@ packages: peerDependencies: react: ^18.0.0 + downshift@9.0.9: + resolution: {integrity: sha512-ygOT8blgiz5liDuEFAIaPeU4dDEa+w9p6PHVUisPIjrkF5wfR59a52HpGWAVVMoWnoFO8po2mZSScKZueihS7g==} + peerDependencies: + react: ^18.0.0 + draw-svg-path@1.0.0: resolution: {integrity: sha512-P8j3IHxcgRMcY6sDzr0QvJDLzBnJJqpTG33UZ2Pvp8rw0apCHhJCWqYprqrXjrgHnJ6tuhP1iTJSAodPDHxwkg==} @@ -7380,8 +7598,8 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} - flow-parser@0.273.0: - resolution: {integrity: sha512-KHe9AJfT0Zn0TpQ2daDFBpwaE0zqjWWiWLOANxzo/U6Xar5fRpU3Lucnk8iMVNitxo9inz2OmSnger70qHmsLw==} + flow-parser@0.273.1: + resolution: {integrity: sha512-UTTfeYIhxYJ7xuW+HL9oyx6lnUGx1+W5Cyo8hOPgMrDU49GANfONtkb9dguDvIyQ20fz8CHZwB25ZP2206bBWQ==} engines: {node: '>=0.4.0'} font-atlas@2.1.0: @@ -7846,8 +8064,8 @@ packages: immutable@4.3.0: resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==} - immutable@5.1.2: - resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==} + immutable@5.1.3: + resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} import-cwd@3.0.0: resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} @@ -9789,8 +10007,8 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} + postcss@8.5.5: + resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} engines: {node: ^10 || ^12 || >=14} potpack@1.0.2: @@ -12312,16 +12530,31 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -12387,11 +12620,21 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -12412,6 +12655,11 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -12427,16 +12675,31 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -12452,6 +12715,11 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-typescript@7.25.7(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -14185,7 +14453,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-array@0.20.0': + '@eslint/config-array@0.20.1': dependencies: '@eslint/object-schema': 2.1.6 debug: 4.4.1 @@ -14195,7 +14463,7 @@ snapshots: '@eslint/config-helpers@0.2.1': {} - '@eslint/config-helpers@0.2.2': {} + '@eslint/config-helpers@0.2.3': {} '@eslint/core@0.12.0': dependencies: @@ -14349,82 +14617,11 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - '@jest/core@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.14.0 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.14.0) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - optional: true - - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.14.0 - ansi-escapes: 4.3.2 + '@types/node': 22.14.1 chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - jest-haste-map: 29.7.0 jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6))': dependencies: @@ -14433,14 +14630,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) + jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -14468,14 +14665,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -14544,7 +14741,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.14.0 + '@types/node': 22.14.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -14622,7 +14819,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/yargs': 17.0.29 chalk: 4.1.2 @@ -14742,7 +14939,7 @@ snapshots: '@melloware/coloris@0.24.2': {} - '@mendix/pluggable-widgets-tools@10.21.0(@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.0)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1)': + '@mendix/pluggable-widgets-tools@10.21.0(@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.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))(react@18.2.0)(tslib@2.8.1)': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) @@ -14768,9 +14965,9 @@ snapshots: '@testing-library/user-event': 14.5.1(@testing-library/dom@8.20.1) '@types/react': 18.2.36 '@types/react-dom': 18.2.14 - '@types/react-native': 0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2)) + '@types/react-native': 0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6)) '@types/testing-library__jest-dom': 5.14.9 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.1.6))(eslint@7.32.0)(typescript@5.8.2) '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.8.2) ansi-colors: 4.1.1 babel-eslint: 10.1.0(eslint@7.32.0) @@ -14783,7 +14980,7 @@ snapshots: enzyme-to-json: 3.6.2(enzyme@3.11.0) eslint: 7.32.0 eslint-config-prettier: 8.10.0(eslint@7.32.0) - eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) + eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.1.6))(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@3.5.3) eslint-plugin-promise: 4.3.1 eslint-plugin-react: 7.28.0(eslint@7.32.0) @@ -14794,7 +14991,7 @@ snapshots: identity-obj-proxy: 3.0.0 jasmine: 3.99.0 jasmine-core: 3.99.1 - jest: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) + jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) jest-environment-jsdom: 29.7.0 jest-jasmine2: 29.7.0 jest-junit: 13.2.0 @@ -14816,14 +15013,14 @@ snapshots: rollup-plugin-command: 1.1.3 rollup-plugin-license: 3.6.0(picomatch@4.0.2)(rollup@3.29.5) rollup-plugin-livereload: 2.0.5 - rollup-plugin-postcss: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) + rollup-plugin-postcss: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) rollup-plugin-re: 1.0.7 sass: 1.58.3 semver: 7.7.1 shelljs: 0.8.5 shx: 0.3.4 - ts-jest: 29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)))(typescript@5.8.2) - ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2) + ts-jest: 29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)))(typescript@5.8.2) + ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2) typescript: 5.8.2 xml2js: 0.6.2 zip-a-folder: 0.0.12 @@ -14848,7 +15045,7 @@ snapshots: - tslib - utf-8-validate - '@mendix/pluggable-widgets-tools@10.21.0(@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)(typescript@5.1.6))(react@18.2.0)(tslib@2.8.1)': + '@mendix/pluggable-widgets-tools@10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1)': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) @@ -14874,7 +15071,7 @@ snapshots: '@testing-library/user-event': 14.5.1(@testing-library/dom@8.20.1) '@types/react': 18.2.36 '@types/react-dom': 18.2.14 - '@types/react-native': 0.72.8(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)(typescript@5.1.6)) + '@types/react-native': 0.72.8(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)(typescript@5.8.2)) '@types/testing-library__jest-dom': 5.14.9 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.8.2) @@ -14900,7 +15097,7 @@ snapshots: identity-obj-proxy: 3.0.0 jasmine: 3.99.0 jasmine-core: 3.99.1 - jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) + jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) jest-environment-jsdom: 29.7.0 jest-jasmine2: 29.7.0 jest-junit: 13.2.0 @@ -14922,13 +15119,13 @@ snapshots: rollup-plugin-command: 1.1.3 rollup-plugin-license: 3.6.0(picomatch@4.0.2)(rollup@3.29.5) rollup-plugin-livereload: 2.0.5 - rollup-plugin-postcss: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) + rollup-plugin-postcss: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) rollup-plugin-re: 1.0.7 sass: 1.58.3 semver: 7.7.1 shelljs: 0.8.5 shx: 0.3.4 - ts-jest: 29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)))(typescript@5.8.2) + ts-jest: 29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)))(typescript@5.8.2) ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2) typescript: 5.8.2 xml2js: 0.6.2 @@ -14954,7 +15151,7 @@ snapshots: - tslib - utf-8-validate - '@mendix/pluggable-widgets-tools@10.21.0(@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)(typescript@5.8.2))(react@18.2.0)(tslib@2.8.1)': + '@mendix/pluggable-widgets-tools@10.21.0(@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)': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) @@ -14980,7 +15177,7 @@ snapshots: '@testing-library/user-event': 14.5.1(@testing-library/dom@8.20.1) '@types/react': 18.2.36 '@types/react-dom': 18.2.14 - '@types/react-native': 0.72.8(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)(typescript@5.8.2)) + '@types/react-native': 0.72.8(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)) '@types/testing-library__jest-dom': 5.14.9 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2) '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.8.2) @@ -15315,6 +15512,17 @@ snapshots: execa: 5.1.1 fast-glob: 3.3.3 + '@react-native-community/cli-config@14.1.0': + dependencies: + '@react-native-community/cli-tools': 14.1.0 + chalk: 4.1.2 + cosmiconfig: 9.0.0 + deepmerge: 4.3.1 + fast-glob: 3.3.3 + joi: 17.13.3 + transitivePeerDependencies: + - typescript + '@react-native-community/cli-config@14.1.0(typescript@5.1.6)': dependencies: '@react-native-community/cli-tools': 14.1.0 @@ -15343,6 +15551,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@react-native-community/cli-doctor@14.1.0': + dependencies: + '@react-native-community/cli-config': 14.1.0 + '@react-native-community/cli-platform-android': 14.1.0 + '@react-native-community/cli-platform-apple': 14.1.0 + '@react-native-community/cli-platform-ios': 14.1.0 + '@react-native-community/cli-tools': 14.1.0 + chalk: 4.1.2 + command-exists: 1.2.9 + deepmerge: 4.3.1 + envinfo: 7.14.0 + execa: 5.1.1 + node-stream-zip: 1.15.0 + ora: 5.4.1 + semver: 7.7.2 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + yaml: 2.8.0 + transitivePeerDependencies: + - typescript + '@react-native-community/cli-doctor@14.1.0(typescript@5.1.6)': dependencies: '@react-native-community/cli-config': 14.1.0(typescript@5.1.6) @@ -15440,6 +15669,30 @@ snapshots: dependencies: joi: 17.13.3 + '@react-native-community/cli@14.1.0': + dependencies: + '@react-native-community/cli-clean': 14.1.0 + '@react-native-community/cli-config': 14.1.0 + '@react-native-community/cli-debugger-ui': 14.1.0 + '@react-native-community/cli-doctor': 14.1.0 + '@react-native-community/cli-server-api': 14.1.0 + '@react-native-community/cli-tools': 14.1.0 + '@react-native-community/cli-types': 14.1.0 + chalk: 4.1.2 + commander: 9.5.0 + deepmerge: 4.3.1 + execa: 5.1.1 + find-up: 5.0.0 + fs-extra: 8.1.0 + graceful-fs: 4.2.11 + prompts: 2.4.2 + semver: 7.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + '@react-native-community/cli@14.1.0(typescript@5.1.6)': dependencies: '@react-native-community/cli-clean': 14.1.0 @@ -15724,48 +15977,48 @@ snapshots: '@react-native/normalize-colors@0.75.3': {} - '@react-native/virtualized-lists@0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))': + '@react-native/virtualized-lists@0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react-native: 0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2) + react-native: 0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6) - '@react-native/virtualized-lists@0.72.8(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)(typescript@5.1.6))': + '@react-native/virtualized-lists@0.72.8(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)(typescript@5.8.2))': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - 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)(typescript@5.1.6) + 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)(typescript@5.8.2) - '@react-native/virtualized-lists@0.72.8(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)(typescript@5.8.2))': + '@react-native/virtualized-lists@0.72.8(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))': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - 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)(typescript@5.8.2) + 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-native/virtualized-lists@0.75.3(@types/react@18.2.36)(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0)': + '@react-native/virtualized-lists@0.75.3(@types/react@18.2.36)(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))(react@18.2.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.2.0 - react-native: 0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2) + react-native: 0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6) optionalDependencies: '@types/react': 18.2.36 - '@react-native/virtualized-lists@0.75.3(@types/react@18.2.36)(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)(typescript@5.1.6))(react@18.2.0)': + '@react-native/virtualized-lists@0.75.3(@types/react@18.2.36)(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)(typescript@5.8.2))(react@18.2.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 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)(typescript@5.1.6) + 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)(typescript@5.8.2) optionalDependencies: '@types/react': 18.2.36 - '@react-native/virtualized-lists@0.75.3(@types/react@18.2.36)(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)(typescript@5.8.2))(react@18.2.0)': + '@react-native/virtualized-lists@0.75.3(@types/react@18.2.36)(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)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 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)(typescript@5.8.2) + 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) optionalDependencies: '@types/react': 18.2.36 @@ -16119,13 +16372,13 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 3.0.5 - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/google.maps@3.55.2': {} '@types/graceful-fs@4.1.8': dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -16253,23 +16506,23 @@ snapshots: '@types/leaflet': 1.9.3 '@types/react': 18.2.36 - '@types/react-native@0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))': + '@types/react-native@0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))': dependencies: - '@react-native/virtualized-lists': 0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2)) + '@react-native/virtualized-lists': 0.72.8(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6)) '@types/react': 18.2.36 transitivePeerDependencies: - react-native - '@types/react-native@0.72.8(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)(typescript@5.1.6))': + '@types/react-native@0.72.8(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)(typescript@5.8.2))': dependencies: - '@react-native/virtualized-lists': 0.72.8(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)(typescript@5.1.6)) + '@react-native/virtualized-lists': 0.72.8(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)(typescript@5.8.2)) '@types/react': 18.2.36 transitivePeerDependencies: - react-native - '@types/react-native@0.72.8(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)(typescript@5.8.2))': + '@types/react-native@0.72.8(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))': dependencies: - '@react-native/virtualized-lists': 0.72.8(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)(typescript@5.8.2)) + '@react-native/virtualized-lists': 0.72.8(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)) '@types/react': 18.2.36 transitivePeerDependencies: - react-native @@ -16340,6 +16593,25 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.1.6))(eslint@7.32.0)(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@7.32.0)(typescript@5.8.2) + '@typescript-eslint/utils': 5.62.0(eslint@7.32.0)(typescript@5.8.2) + debug: 4.3.7 + eslint: 7.32.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare-lite: 1.4.0 + semver: 7.7.1 + tsutils: 3.21.0(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -17111,6 +17383,19 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.3 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.27.4) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.26.5 @@ -17244,12 +17529,34 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) + babel-preset-current-node-syntax@1.0.1(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.27.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.27.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.27.4) + babel-preset-jest@29.6.3(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) + babel-preset-jest@29.6.3(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.27.4) + babel-runtime@6.26.0: dependencies: core-js: 2.6.12 @@ -17785,6 +18092,13 @@ snapshots: js-yaml: 3.14.1 parse-json: 4.0.0 + cosmiconfig@9.0.0: + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + cosmiconfig@9.0.0(typescript@5.1.6): dependencies: env-paths: 2.2.1 @@ -17814,13 +18128,13 @@ snapshots: dependencies: buffer: 5.7.1 - create-jest@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): + create-jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17829,13 +18143,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.14.1): + create-jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.14.1) + jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17843,43 +18157,12 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - create-jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-require@1.1.1: {} - - crelt@1.0.6: {} - - cross-env@7.0.3: + create-require@1.1.1: {} + + crelt@1.0.6: {} + + cross-env@7.0.3: dependencies: cross-spawn: 7.0.6 @@ -17946,12 +18229,12 @@ snapshots: css-loader@7.1.2(webpack@5.94.0): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.4) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.4) - postcss-modules-scope: 3.2.1(postcss@8.5.4) - postcss-modules-values: 4.0.0(postcss@8.5.4) + icss-utils: 5.1.0(postcss@8.5.5) + postcss: 8.5.5 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.5) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.5) + postcss-modules-scope: 3.2.1(postcss@8.5.5) + postcss-modules-values: 4.0.0(postcss@8.5.5) postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: @@ -18413,6 +18696,15 @@ snapshots: react-is: 18.2.0 tslib: 2.7.0 + downshift@9.0.9(react@18.2.0): + dependencies: + '@babel/runtime': 7.25.7 + compute-scroll-into-view: 3.1.0 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + tslib: 2.8.1 + draw-svg-path@1.0.0: dependencies: abs-svg-path: 0.1.1 @@ -18772,8 +19064,8 @@ snapshots: '@types/sass': 1.45.0 '@types/stylus': 0.48.43 glob: 10.4.5 - postcss: 8.5.4 - postcss-modules: 6.0.1(postcss@8.5.4) + postcss: 8.5.5 + postcss-modules: 6.0.1(postcss@8.5.5) escalade@3.2.0: {} @@ -18822,6 +19114,16 @@ snapshots: eslint: 9.23.0(jiti@2.4.2) globals: 15.15.0 + eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.1.6))(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2): + dependencies: + '@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@5.8.2) + eslint: 7.32.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.1.6))(eslint@7.32.0)(typescript@5.8.2) + transitivePeerDependencies: + - supports-color + - typescript + eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2))(eslint@7.32.0)(typescript@5.8.2): dependencies: '@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@5.8.2) @@ -18832,13 +19134,13 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@28.11.0(@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.14.1))(typescript@5.8.2): + eslint-plugin-jest@28.11.0(@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)))(typescript@5.8.2): dependencies: '@typescript-eslint/utils': 6.13.2(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) eslint: 9.23.0(jiti@2.4.2) optionalDependencies: '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) - jest: 29.7.0(@types/node@22.14.1) + jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)) transitivePeerDependencies: - supports-color - typescript @@ -19062,8 +19364,8 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.24.0(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.0 - '@eslint/config-helpers': 0.2.2 + '@eslint/config-array': 0.20.1 + '@eslint/config-helpers': 0.2.3 '@eslint/core': 0.12.0 '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.24.0 @@ -19339,7 +19641,7 @@ snapshots: flow-enums-runtime@0.0.6: {} - flow-parser@0.273.0: {} + flow-parser@0.273.1: {} font-atlas@2.1.0: dependencies: @@ -19878,9 +20180,9 @@ snapshots: dependencies: postcss: 8.4.47 - icss-utils@5.1.0(postcss@8.5.4): + icss-utils@5.1.0(postcss@8.5.5): dependencies: - postcss: 8.5.4 + postcss: 8.5.5 identity-obj-proxy@3.0.0: dependencies: @@ -19902,7 +20204,7 @@ snapshots: immutable@4.3.0: {} - immutable@5.1.2: {} + immutable@5.1.3: {} import-cwd@3.0.0: dependencies: @@ -20251,11 +20553,11 @@ snapshots: istanbul-lib-instrument@6.0.1: dependencies: - '@babel/core': 7.26.10 - '@babel/parser': 7.26.10 + '@babel/core': 7.27.4 + '@babel/parser': 7.27.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.1 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -20267,7 +20569,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.7 + debug: 4.4.1 istanbul-lib-coverage: 3.2.1 source-map: 0.6.1 transitivePeerDependencies: @@ -20324,7 +20626,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -20344,45 +20646,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-cli@29.7.0(@types/node@22.14.1): - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.14.1) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.14.1) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - optional: true - jest-cli@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) @@ -20421,167 +20684,12 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.14.0): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.0 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - optional: true - - jest-config@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.0 - ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.0 - ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.0 - ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.14.1): - dependencies: - '@babel/core': 7.26.10 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.14.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - optional: true - jest-config@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.27.4 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) + babel-jest: 29.7.0(@babel/core@7.27.4) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -20609,10 +20717,10 @@ snapshots: jest-config@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2)): dependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.27.4 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) + babel-jest: 29.7.0(@babel/core@7.27.4) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -20677,7 +20785,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -20687,7 +20795,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.8 - '@types/node': 22.14.0 + '@types/node': 22.14.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -20783,7 +20891,7 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-util: 29.7.0 jest-validate: 29.7.0 - resolve: 1.22.8 + resolve: 1.22.10 resolve.exports: 2.0.2 slash: 3.0.0 @@ -20794,7 +20902,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -20887,7 +20995,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -20896,42 +21004,17 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 22.14.0 + '@types/node': 22.14.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@22.14.1): - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.14.1) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - optional: true - jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)) @@ -21005,7 +21088,7 @@ snapshots: '@babel/register': 7.27.1(@babel/core@7.27.4) babel-core: 7.0.0-bridge.0(@babel/core@7.27.4) chalk: 4.1.2 - flow-parser: 0.273.0 + flow-parser: 0.273.1 graceful-fs: 4.2.11 micromatch: 4.0.8 neo-async: 2.6.2 @@ -21030,7 +21113,7 @@ snapshots: '@babel/register': 7.27.1(@babel/core@7.27.4) babel-core: 7.0.0-bridge.0(@babel/core@7.27.4) chalk: 4.1.2 - flow-parser: 0.273.0 + flow-parser: 0.273.1 graceful-fs: 4.2.11 micromatch: 4.0.8 neo-async: 2.6.2 @@ -21319,7 +21402,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 make-error@1.3.6: {} @@ -21744,14 +21827,14 @@ snapshots: mkdirp@1.0.4: {} - mobx-react-lite@4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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)(typescript@5.8.2))(react@18.2.0): + mobx-react-lite@4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(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): dependencies: mobx: 6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9) react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) optionalDependencies: 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)(typescript@5.8.2) + 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) mobx-react-lite@4.0.7(patch_hash=47fd2d1b5c35554ddd4fa32fcaa928a16fda9f82dca0ff68bcdc1f7c3e5f9d1a)(mobx@6.12.3(patch_hash=39c55279e8f75c9a322eba64dd22e1a398f621c64bbfc3632e55a97f46edfeb9))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: @@ -22390,14 +22473,6 @@ snapshots: postcss: 8.4.31 ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.8.2) - postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.4.47 - ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2) - postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: lilconfig: 2.1.0 @@ -22498,9 +22573,9 @@ snapshots: dependencies: postcss: 8.4.47 - postcss-modules-extract-imports@3.1.0(postcss@8.5.4): + postcss-modules-extract-imports@3.1.0(postcss@8.5.5): dependencies: - postcss: 8.5.4 + postcss: 8.5.5 postcss-modules-local-by-default@4.0.5(postcss@8.4.31): dependencies: @@ -22516,10 +22591,10 @@ snapshots: postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 - postcss-modules-local-by-default@4.2.0(postcss@8.5.4): + postcss-modules-local-by-default@4.2.0(postcss@8.5.5): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.5) + postcss: 8.5.5 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 @@ -22533,9 +22608,9 @@ snapshots: postcss: 8.4.47 postcss-selector-parser: 6.0.13 - postcss-modules-scope@3.2.1(postcss@8.5.4): + postcss-modules-scope@3.2.1(postcss@8.5.5): dependencies: - postcss: 8.5.4 + postcss: 8.5.5 postcss-selector-parser: 7.1.0 postcss-modules-values@4.0.0(postcss@8.4.31): @@ -22548,10 +22623,10 @@ snapshots: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 - postcss-modules-values@4.0.0(postcss@8.5.4): + postcss-modules-values@4.0.0(postcss@8.5.5): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.5) + postcss: 8.5.5 postcss-modules@4.3.1(postcss@8.4.31): dependencies: @@ -22577,16 +22652,16 @@ snapshots: postcss-modules-values: 4.0.0(postcss@8.4.47) string-hash: 1.1.3 - postcss-modules@6.0.1(postcss@8.5.4): + postcss-modules@6.0.1(postcss@8.5.5): dependencies: generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.5.4) + icss-utils: 5.1.0(postcss@8.5.5) lodash.camelcase: 4.3.0 - postcss: 8.5.4 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.4) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.4) - postcss-modules-scope: 3.2.1(postcss@8.5.4) - postcss-modules-values: 4.0.0(postcss@8.5.4) + postcss: 8.5.5 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.5) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.5) + postcss-modules-scope: 3.2.1(postcss@8.5.5) + postcss-modules-values: 4.0.0(postcss@8.5.5) string-hash: 1.1.3 postcss-normalize-charset@5.1.0(postcss@8.4.31): @@ -22777,7 +22852,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.4: + postcss@8.5.5: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -23139,10 +23214,10 @@ snapshots: react-lifecycles-compat@3.0.4: {} - react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2): + react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 14.1.0(typescript@5.8.2) + '@react-native-community/cli': 14.1.0(typescript@5.1.6) '@react-native-community/cli-platform-android': 14.1.0 '@react-native-community/cli-platform-ios': 14.1.0 '@react-native/assets-registry': 0.75.3 @@ -23151,7 +23226,7 @@ snapshots: '@react-native/gradle-plugin': 0.75.3 '@react-native/js-polyfills': 0.75.3 '@react-native/normalize-colors': 0.75.3 - '@react-native/virtualized-lists': 0.75.3(@types/react@18.2.36)(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.8.2))(react@18.2.0) + '@react-native/virtualized-lists': 0.75.3(@types/react@18.2.36)(react-native@0.75.3(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.2.36)(react@18.2.0)(typescript@5.1.6))(react@18.2.0) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -23192,10 +23267,10 @@ snapshots: - typescript - utf-8-validate - 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)(typescript@5.1.6): + 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): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 14.1.0(typescript@5.1.6) + '@react-native-community/cli': 14.1.0 '@react-native-community/cli-platform-android': 14.1.0 '@react-native-community/cli-platform-ios': 14.1.0 '@react-native/assets-registry': 0.75.3 @@ -23204,7 +23279,7 @@ snapshots: '@react-native/gradle-plugin': 0.75.3 '@react-native/js-polyfills': 0.75.3 '@react-native/normalize-colors': 0.75.3 - '@react-native/virtualized-lists': 0.75.3(@types/react@18.2.36)(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)(typescript@5.1.6))(react@18.2.0) + '@react-native/virtualized-lists': 0.75.3(@types/react@18.2.36)(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) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -23712,25 +23787,6 @@ snapshots: transitivePeerDependencies: - ts-node - rollup-plugin-postcss@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)): - dependencies: - chalk: 4.1.2 - concat-with-sourcemaps: 1.1.0 - cssnano: 5.1.15(postcss@8.4.47) - import-cwd: 3.0.0 - p-queue: 6.6.2 - pify: 5.0.0 - postcss: 8.4.47 - postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - postcss-modules: 4.3.1(postcss@8.4.47) - promise.series: 0.2.0 - resolve: 1.22.8 - rollup-pluginutils: 2.8.2 - safe-identifier: 0.4.2 - style-inject: 0.3.0 - transitivePeerDependencies: - - ts-node - rollup-plugin-postcss@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)): dependencies: chalk: 4.1.2 @@ -23859,7 +23915,7 @@ snapshots: sass@1.89.2: dependencies: chokidar: 4.0.3 - immutable: 5.1.2 + immutable: 5.1.3 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -24568,25 +24624,6 @@ snapshots: ts-custom-error@3.3.1: {} - ts-jest@29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)))(typescript@5.8.2): - dependencies: - bs-logger: 0.2.6 - ejs: 3.1.10 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2)) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.1 - typescript: 5.8.2 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.26.10 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.10) - ts-jest@29.2.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6)))(typescript@5.8.2): dependencies: bs-logger: 0.2.6 @@ -24634,26 +24671,6 @@ snapshots: typescript: 5.1.6 webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.15))(webpack-cli@5.0.1) - ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.2): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.14.0 - acorn: 8.14.0 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.8.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.7.26(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@22.14.1)(typescript@5.1.6): dependencies: '@cspotcode/source-map-support': 0.8.1 diff --git a/turbo.json b/turbo.json index 103f087b02..5b52561437 100644 --- a/turbo.json +++ b/turbo.json @@ -12,7 +12,7 @@ "outputs": ["dist/**", "!dist/tmp/**"] }, "release": { - "dependsOn": ["^release", "verify"], + "dependsOn": ["^build", "verify"], "outputs": ["dist/**", "!dist/tmp/**"] }, "build:module": { @@ -29,9 +29,11 @@ }, "test": { "outputs": [], - "inputs": ["src/**", "typings/**", "package.json", "tsconfig.json"] + "inputs": ["src/**", "typings/**", "package.json", "tsconfig.json"], + "dependsOn": ["^build"] }, "lint": { + "dependsOn": ["^build"], "outputs": [] }, "create-gh-release": { @@ -60,5 +62,11 @@ "outputs": [] } }, - "globalDependencies": ["automation/**", ".npmrc", ".nvmrc", ".github/**", "package.json"] + "globalDependencies": [ + "automation/**", + ".npmrc", + ".nvmrc", + ".github/**", + "package.json" + ] }