chore: add wds datepicker widget (#37711)

![CleanShot 2024-11-26 at 15 56
15](https://github.com/user-attachments/assets/d812f475-11e1-4750-9018-bdd39d5a5de3)

/ok-to-test tags="@tag.Anvil"

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Introduced the WDS Date Picker Widget, enhancing date selection
capabilities within the UI.
- Added configuration options for widget size, visibility, and
autocomplete behavior.
- Implemented comprehensive validation for date input, ensuring accurate
user selections.
- Expanded widget collection to include the new WDS Date Picker Widget.
- Introduced new constants for date format options, facilitating diverse
formatting choices.
- Added support for a new "Date" input type, enhancing input widget
configurability.

- **Documentation**
- Updated property pane configurations to include detailed settings for
date format, validation, and event handling.

- **Bug Fixes**
- Improved handling of derived properties to ensure proper context
during widget interactions.

These updates collectively improve user experience and flexibility in
date selection within the application.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12083004714>
> Commit: b17348e03db911501970d2c8a59c4fea30a175e1
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12083004714&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Anvil`
> Spec:
> <hr>Fri, 29 Nov 2024 10:44:39 UTC
<!-- end of auto-generated comment: Cypress test results  -->

---------

Co-authored-by: Vadim Vaitenko <vadim@appsmith.com>
This commit is contained in:
Pawan Kumar 2024-11-29 16:20:58 +05:30 committed by GitHub
parent 8a369e1096
commit 63eec76635
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 716 additions and 4 deletions

View File

@ -61,6 +61,10 @@
}
.calendar tbody [role="button"][data-focus-visible] {
outline: var(--border-width-2) solid var(--color-bd-accent);
outline-offset: var(--border-width-2);
--box-shadow-offset: 2px;
box-shadow:
0 0 0 var(--box-shadow-offset) var(--color-bg),
0 0 0 calc(var(--box-shadow-offset) + var(--border-width-2))
var(--color-bd-focus);
}

View File

@ -6,6 +6,7 @@ export const INPUT_TYPES = {
PASSWORD: "PASSWORD",
PHONE_NUMBER: "PHONE_NUMBER",
MULTI_LINE_TEXT: "MULTI_LINE_TEXT",
DATE: "DATE",
} as const;
export const INPUT_TYPE_TO_WIDGET_TYPE_MAP = {
@ -16,4 +17,5 @@ export const INPUT_TYPE_TO_WIDGET_TYPE_MAP = {
[INPUT_TYPES.MULTI_LINE_TEXT]: "WDS_MULTILINE_INPUT_WIDGET",
[INPUT_TYPES.CURRENCY]: "WDS_CURRENCY_INPUT_WIDGET",
[INPUT_TYPES.PHONE_NUMBER]: "WDS_PHONE_INPUT_WIDGET",
[INPUT_TYPES.DATE]: "WDS_DATEPICKER_WIDGET",
};

View File

@ -1,4 +1,7 @@
import { WDSBaseInputWidget } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
import {
INPUT_TYPES,
WDSBaseInputWidget,
} from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import type { WidgetDefaultProps } from "WidgetProvider/constants";
@ -14,4 +17,5 @@ export const defaultsConfig = {
showStepArrows: false,
label: "Current Price",
responsiveBehavior: ResponsiveBehavior.Fill,
inputType: INPUT_TYPES.CURRENCY,
} as WidgetDefaultProps;

View File

@ -0,0 +1,11 @@
import type { AnvilConfig } from "WidgetProvider/constants";
export const anvilConfig: AnvilConfig = {
isLargeWidget: false,
widgetSize: {
minWidth: {
base: "100%",
"180px": "sizing-30",
},
},
};

View File

@ -0,0 +1,11 @@
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
export const autocompleteConfig = {
"!doc":
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
"!url": "https://docs.appsmith.com/widget-reference/datepicker",
isVisible: DefaultAutocompleteDefinitions.isVisible,
selectedDate: "string",
formattedDate: "string",
isDisabled: "bool",
};

View File

@ -0,0 +1,20 @@
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import type { WidgetDefaultProps } from "WidgetProvider/constants";
import { INPUT_TYPES } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
export const defaultsConfig = {
animateLoading: true,
label: "Label",
dateFormat: "YYYY-MM-DD HH:mm",
defaultOptionValue: "",
isRequired: false,
isDisabled: false,
isVisible: true,
isInline: false,
widgetName: "DatePicker",
widgetType: "WDS_DATE_PICKER",
version: 1,
timePrecision: "day",
responsiveBehavior: ResponsiveBehavior.Fill,
inputType: INPUT_TYPES.DATE,
} as unknown as WidgetDefaultProps;

View File

@ -0,0 +1,7 @@
export * from "./propertyPaneConfig";
export { metaConfig } from "./metaConfig";
export { anvilConfig } from "./anvilConfig";
export { defaultsConfig } from "./defaultsConfig";
export { settersConfig } from "./settersConfig";
export { methodsConfig } from "./methodsConfig";
export { autocompleteConfig } from "./autocompleteConfig";

View File

@ -0,0 +1,21 @@
import { WIDGET_TAGS } from "constants/WidgetConstants";
export const metaConfig = {
name: "DatePicker",
tags: [WIDGET_TAGS.INPUTS],
needsMeta: true,
searchTags: [
"datepicker",
"appointment",
"calendar",
"date",
"day",
"hour",
"meeting",
"moment",
"schedule",
"time",
"week",
"year",
],
};

View File

@ -0,0 +1,6 @@
import { DatePickerIcon, DatePickerThumbnail } from "appsmith-icons";
export const methodsConfig = {
IconCmp: DatePickerIcon,
ThumbnailCmp: DatePickerThumbnail,
};

View File

@ -0,0 +1,191 @@
import { ValidationTypes } from "constants/WidgetValidation";
import { DATE_FORMAT_OPTIONS } from "../../constants";
import { propertyPaneContentConfig as WdsInputWidgetPropertyPaneContentConfig } from "modules/ui-builder/ui/wds/WDSInputWidget/config/propertyPaneConfig/contentConfig";
import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
const inputTypeSectionConfig = WdsInputWidgetPropertyPaneContentConfig.find(
(config) => config.sectionName === "Type",
);
export const propertyPaneContentConfig = [
inputTypeSectionConfig,
{
sectionName: "Data",
children: [
{
helpText: "Sets the format of the selected date",
propertyName: "dateFormat",
label: "Date format",
controlType: "DROP_DOWN",
isJSConvertible: true,
optionWidth: "340px",
options: DATE_FORMAT_OPTIONS,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
hideSubText: true,
},
{
propertyName: "defaultDate",
label: "Default Date",
helpText:
"Sets the default date of the widget. The date is updated if the default date changes",
controlType: "DATE_PICKER",
placeholderText: "Enter Default Date",
useValidationMessage: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.DATE_ISO_STRING },
},
{
propertyName: "timePrecision",
label: "Time Precision",
controlType: "DROP_DOWN",
helpText: "Sets the time precision or hides the time picker.",
defaultValue: "day",
options: [
{
label: "Day",
value: "day",
},
{
label: "Hour",
value: "hour",
},
{
label: "Minute",
value: "minute",
},
{
label: "Second",
value: "second",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: ["day", "hour", "minute", "second"],
default: "day",
},
},
},
],
},
{
sectionName: "Label",
children: [
{
helpText: "Sets the label text of the date picker widget",
propertyName: "label",
label: "Text",
controlType: "INPUT_TEXT",
placeholderText: "Label",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
{
sectionName: "Validations",
children: [
{
propertyName: "isRequired",
label: "Required",
helpText: "Makes input to the widget mandatory",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "minDate",
label: "Minimum Date",
helpText: "Sets the minimum date that can be selected",
controlType: "DATE_PICKER",
placeholderText: "Enter Minimum Date",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.DATE_ISO_STRING },
},
{
propertyName: "maxDate",
label: "Maximum Date",
helpText: "Sets the maximum date that can be selected",
controlType: "DATE_PICKER",
placeholderText: "Enter Maximum Date",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.DATE_ISO_STRING },
},
],
},
{
sectionName: "General",
children: [
{
helpText: "Shows help text or details about the current input",
propertyName: "labelTooltip",
label: "Tooltip",
controlType: "INPUT_TEXT",
placeholderText: "",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "isDisabled",
label: "Disabled",
helpText: "Disables input to this widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "animateLoading",
label: "Animate loading",
controlType: "SWITCH",
helpText: "Controls the loading of the widget",
defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "Events",
children: [
{
propertyName: "onDateSelected",
label: "onDateSelected",
helpText: "when a date is selected in the calendar",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
] as PropertyPaneConfig[];

View File

@ -0,0 +1 @@
export { propertyPaneContentConfig } from "./contentConfig";

View File

@ -0,0 +1,12 @@
export const settersConfig = {
__setters: {
setVisibility: {
path: "isVisible",
type: "boolean",
},
setDisabled: {
path: "isDisabled",
type: "boolean",
},
},
};

View File

@ -0,0 +1,88 @@
import moment from "moment";
import { SubTextPosition } from "components/constants";
export const DATE_FORMAT_OPTIONS = [
{
label: moment().format("YYYY-MM-DDTHH:mm:ss.sssZ"),
subText: "ISO 8601",
value: "YYYY-MM-DDTHH:mm:ss.sssZ",
},
{
label: moment().format("LLL"),
subText: "LLL",
value: "LLL",
},
{
label: moment().format("LL"),
subText: "LL",
value: "LL",
},
{
label: moment().format("YYYY-MM-DD HH:mm"),
subText: "YYYY-MM-DD HH:mm",
value: "YYYY-MM-DD HH:mm",
},
{
label: moment().format("YYYY-MM-DDTHH:mm:ss"),
subText: "YYYY-MM-DDTHH:mm:ss",
value: "YYYY-MM-DDTHH:mm:ss",
},
{
label: moment().format("YYYY-MM-DD hh:mm:ss A"),
subText: "YYYY-MM-DD hh:mm:ss A",
value: "YYYY-MM-DD hh:mm:ss A",
},
{
label: moment().format("DD/MM/YYYY HH:mm"),
subText: "DD/MM/YYYY HH:mm",
value: "DD/MM/YYYY HH:mm",
},
{
label: moment().format("D MMMM, YYYY"),
subText: "D MMMM, YYYY",
value: "D MMMM, YYYY",
},
{
label: moment().format("H:mm A D MMMM, YYYY"),
subText: "H:mm A D MMMM, YYYY",
value: "H:mm A D MMMM, YYYY",
},
{
label: moment().format("YYYY-MM-DD"),
subText: "YYYY-MM-DD",
value: "YYYY-MM-DD",
},
{
label: moment().format("MM-DD-YYYY"),
subText: "MM-DD-YYYY",
value: "MM-DD-YYYY",
},
{
label: moment().format("DD-MM-YYYY"),
subText: "DD-MM-YYYY",
value: "DD-MM-YYYY",
},
{
label: moment().format("MM/DD/YYYY"),
subText: "MM/DD/YYYY",
value: "MM/DD/YYYY",
},
{
label: moment().format("DD/MM/YYYY"),
subText: "DD/MM/YYYY",
value: "DD/MM/YYYY",
},
{
label: moment().format("DD/MM/YY"),
subText: "DD/MM/YY",
value: "DD/MM/YY",
},
{
label: moment().format("MM/DD/YY"),
subText: "MM/DD/YY",
value: "MM/DD/YY",
},
].map((x) => ({
...x,
subTextPosition: SubTextPosition.BOTTOM,
}));

View File

@ -0,0 +1,3 @@
import { WDSDatePickerWidget } from "./widget";
export { WDSDatePickerWidget };

View File

@ -0,0 +1,36 @@
export default {
isValid: (props, moment) => {
const parsedMinDate = new Date(props.minDate);
const parsedMaxDate = new Date(props.maxDate);
const parsedSelectedDate = props.selectedDate
? moment(new Date(props.selectedDate))
: null;
// only do validation when the date is dirty
if (!props.isDirty) {
return true;
}
if (!parsedSelectedDate && !props.isRequired) {
return true;
}
if (!parsedSelectedDate && props.isRequired) {
return false;
}
if (props.minDate && props.maxDate) {
return parsedSelectedDate.isBetween(parsedMinDate, parsedMaxDate);
}
if (props.minDate) {
return parsedSelectedDate.isAfter(parsedMinDate);
}
if (props.maxDate) {
return parsedSelectedDate.isBefore(parsedMaxDate);
}
return true;
},
};

View File

@ -0,0 +1,64 @@
import moment from "moment";
import derived from "./derived";
describe("isValid function", () => {
const mockMoment = (date: string) => moment(date);
it("should return true when isDirty is false", () => {
const props = { isDirty: false };
expect(derived.isValid(props, mockMoment)).toBe(true);
});
it("should return true when selectedDate is null and not required", () => {
const props = { isDirty: true, isRequired: false, selectedDate: null };
expect(derived.isValid(props, mockMoment)).toBe(true);
});
it("should return false when selectedDate is null and required", () => {
const props = { isDirty: true, isRequired: true, selectedDate: null };
expect(derived.isValid(props, mockMoment)).toBe(false);
});
it("should return true when selectedDate is between minDate and maxDate", () => {
const props = {
isDirty: true,
minDate: "2023-01-01",
maxDate: "2023-12-31",
selectedDate: "2023-06-15",
};
expect(derived.isValid(props, mockMoment)).toBe(true);
});
it("should return false when selectedDate is before minDate", () => {
const props = {
isDirty: true,
minDate: "2023-01-01",
selectedDate: "2022-12-31",
};
expect(derived.isValid(props, mockMoment)).toBe(false);
});
it("should return false when selectedDate is after maxDate", () => {
const props = {
isDirty: true,
maxDate: "2023-12-31",
selectedDate: "2024-01-01",
};
expect(derived.isValid(props, mockMoment)).toBe(false);
});
it("should return true when selectedDate is valid and no min/max dates are set", () => {
const props = {
isDirty: true,
selectedDate: "2023-06-15",
};
expect(derived.isValid(props, mockMoment)).toBe(true);
});
});

View File

@ -0,0 +1,15 @@
import type { WDSDatePickerWidgetProps } from "./types";
export function validateInput(props: WDSDatePickerWidgetProps) {
if (props.isValid === false) {
return {
validationStatus: "invalid",
errorMessage: "Please select a valid date",
};
}
return {
validationStatus: "valid",
errorMessage: "",
};
}

View File

@ -0,0 +1,149 @@
import React from "react";
import moment from "moment";
import BaseWidget from "widgets/BaseWidget";
import type { WidgetState } from "widgets/BaseWidget";
import type {
AnvilConfig,
AutocompletionDefinitions,
} from "WidgetProvider/constants";
import { parseDateTime } from "@internationalized/date";
import { DatePicker, type DateValue } from "@appsmith/wds";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import * as config from "../config";
import { validateInput } from "./helpers";
import derivedPropertyFns from "./derived";
import type { WDSDatePickerWidgetProps } from "./types";
import { parseDerivedProperties } from "widgets/WidgetUtils";
class WDSDatePickerWidget extends BaseWidget<
WDSDatePickerWidgetProps,
WidgetState
> {
static type = "WDS_DATEPICKER_WIDGET";
static getConfig() {
return config.metaConfig;
}
static getDefaults() {
return config.defaultsConfig;
}
static getMethods() {
return config.methodsConfig;
}
static getAnvilConfig(): AnvilConfig | null {
return config.anvilConfig;
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return config.autocompleteConfig;
}
static getPropertyPaneContentConfig() {
return config.propertyPaneContentConfig;
}
static getPropertyPaneStyleConfig() {
return [];
}
static getDerivedPropertiesMap() {
const parsedDerivedProperties = parseDerivedProperties(derivedPropertyFns);
return {
isValid: `{{(() => {${parsedDerivedProperties.isValid}})()}}`,
selectedDate: `{{ this.value ? moment(this.value).toISOString() : "" }}`,
formattedDate: `{{ this.value ? moment(this.value).format(this.dateFormat) : "" }}`,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
value: "defaultDate",
};
}
static getMetaPropertiesMap() {
return {
value: undefined,
isDirty: false,
};
}
static getStylesheetConfig() {
return {};
}
static getSetterConfig() {
return config.settersConfig;
}
static getDependencyMap() {
return {};
}
componentDidUpdate(prevProps: WDSDatePickerWidgetProps): void {
if (!this.shouldResetDirtyState(prevProps)) {
return;
}
this.resetDirtyState();
}
handleDateChange = (date: DateValue) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("value", date.toString(), {
triggerPropertyName: "onDateSelected",
dynamicString: this.props.onDateSelected,
event: {
type: EventType.ON_DATE_SELECTED,
},
});
};
private shouldResetDirtyState(prevProps: WDSDatePickerWidgetProps): boolean {
const { defaultDate, isDirty } = this.props;
const hasDefaultDateChanged = defaultDate !== prevProps.defaultDate;
return hasDefaultDateChanged && isDirty;
}
private resetDirtyState() {
this.props.updateWidgetMetaProperty("isDirty", false);
}
private parseDate(date: string | undefined) {
return date
? parseDateTime(moment(date).format("YYYY-MM-DDTHH:mm:ss"))
: undefined;
}
getWidgetView() {
const { label, labelTooltip, maxDate, minDate, value, ...rest } =
this.props;
const { errorMessage, validationStatus } = validateInput(this.props);
return (
<DatePicker
contextualHelp={labelTooltip}
errorMessage={errorMessage}
granularity={this.props.timePrecision}
isInvalid={validationStatus === "invalid"}
label={label}
maxValue={this.parseDate(maxDate)}
minValue={this.parseDate(minDate)}
onChange={this.handleDateChange}
value={this.parseDate(value)}
{...rest}
/>
);
}
}
export { WDSDatePickerWidget };

View File

@ -0,0 +1,11 @@
import type { WidgetProps } from "widgets/BaseWidget";
export interface WDSDatePickerWidgetProps extends WidgetProps {
selectedDate: string;
defaultDate: string;
onDateSelected: string;
isRequired?: boolean;
isDisabled?: boolean;
label: string;
labelTooltip?: string;
}

View File

@ -48,6 +48,10 @@ export const propertyPaneContentConfig = [
label: "Currency",
value: "CURRENCY",
},
{
label: "Date",
value: "DATE",
},
],
isBindProperty: false,
isTriggerProperty: false,

View File

@ -1,4 +1,7 @@
import { WDSBaseInputWidget } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
import {
INPUT_TYPES,
WDSBaseInputWidget,
} from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import type { WidgetDefaultProps } from "WidgetProvider/constants";
@ -13,4 +16,5 @@ export const defaultsConfig = {
allowFormatting: true,
responsiveBehavior: ResponsiveBehavior.Fill,
label: "Phone number",
inputType: INPUT_TYPES.PHONE_NUMBER,
} as WidgetDefaultProps;

View File

@ -61,6 +61,7 @@ export const WDS_V2_WIDGET_MAP = {
MULTILINE_INPUT_WIDGET: "WDS_MULTILINE_INPUT_WIDGET",
WDS_SELECT_WIDGET: "WDS_SELECT_WIDGET",
WDS_COMBOBOX_WIDGET: "WDS_COMBOBOX_WIDGET",
WDS_DATEPICKER_WIDGET: "WDS_DATEPICKER_WIDGET",
// Anvil layout widgets
ZONE_WIDGET: anvilWidgets.ZONE_WIDGET,

View File

@ -989,3 +989,48 @@ export const checkForOnClick = (e: React.MouseEvent<HTMLElement>) => {
return false;
};
/**
* Parses the derived properties from the given property functions. Used in getDerivedPropertiesMap
*
* @example
* ```js
* {
* isValidDate: (props, moment, _) => {
* return props.value === 1;
* }
* ```
*
* It will return
* ```js
* {
* isValidDate: "{{ this.value === 1 }}"
* }
* ```
*
* Main rule to remember is don't deconstruct the props like `const { value } = props;` in the derived property function.
* Directly access props like `props.value`
*/
export function parseDerivedProperties(propertyFns: Record<string, unknown>) {
const derivedProperties: Record<string, string> = {};
for (const [key, value] of Object.entries(propertyFns)) {
if (typeof value === "function") {
const functionString = value.toString();
const functionBody = functionString.match(/(?<=\{)(.|\n)*(?=\})/)?.[0];
if (functionBody) {
const paramMatch = functionString.match(/\((.*?),/);
const propsParam = paramMatch ? paramMatch[1].trim() : "props";
const modifiedBody = functionBody
.trim()
.replace(new RegExp(`${propsParam}\\.`, "g"), "this.");
derivedProperties[key] = modifiedBody;
}
}
}
return derivedProperties;
}

View File

@ -88,6 +88,7 @@ import { WDSNumberInputWidget } from "modules/ui-builder/ui/wds/WDSNumberInputWi
import { WDSMultilineInputWidget } from "modules/ui-builder/ui/wds/WDSMultilineInputWidget";
import { WDSSelectWidget } from "modules/ui-builder/ui/wds/WDSSelectWidget";
import { EEWDSWidgets } from "ee/modules/ui-builder/ui/wds";
import { WDSDatePickerWidget } from "modules/ui-builder/ui/wds/WDSDatePickerWidget";
const LegacyWidgets = [
CanvasWidget,
@ -185,6 +186,7 @@ const WDSWidgets = [
WDSNumberInputWidget,
WDSMultilineInputWidget,
WDSSelectWidget,
WDSDatePickerWidget,
];
const Widgets = [