diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss index b77663d6cc..6be2a5dda0 100644 --- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss +++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss @@ -138,7 +138,6 @@ $root: ".widget-dropdown-filter"; &-clear { @include btn-with-cross; align-items: center; - align-self: center; display: flex; flex-shrink: 0; justify-self: end; @@ -150,6 +149,11 @@ $root: ".widget-dropdown-filter"; &:has(+ #{$root}-toggle) { border-inline-end: 1px solid var(--gray, #787d87); } + + &:focus { + border-radius: 2px; + outline: 2px solid var(--brand-primary, $brand-primary); + } } &-state-icon { @@ -262,9 +266,13 @@ $root: ".widget-dropdown-filter"; justify-content: center; line-height: 1.334; padding: var(--wdf-tag-padding); + margin: var(--spacing-smallest, 2px); &:focus-visible { outline: var(--brand-primary, #264ae5) auto 1px; } + &:focus { + background-color: var(--color-primary-light, $color-primary-light); + } } #{$root}-input { @@ -273,6 +281,14 @@ $root: ".widget-dropdown-filter"; width: initial; } + &:not(:focus-within):not([data-empty]) { + #{$root}-input { + opacity: 0; + flex-shrink: 1; + min-width: 1px; + } + } + #{$root}-clear { border-color: transparent; } 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..5194e4211a 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts @@ -20,6 +20,8 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul if (values.filterable) { hidePropertyIn(defaultProperties, values, "clearable"); hidePropertyIn(defaultProperties, values, "emptyOptionCaption"); + } else { + hidePropertyIn(defaultProperties, values, "filterInputPlaceholderCaption"); } if (!showSelectedItemsStyle) { @@ -54,7 +56,7 @@ export const getPreview = (values: DatagridDropdownFilterPreviewProps, isDarkMod text({ fontColor: palette.text.secondary, italic: true - })(values.emptyOptionCaption || " ") + })(values.emptySelectionCaption || " ") ], grow: 1 } as ContainerProps, 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..9fede9d5c2 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx @@ -14,6 +14,7 @@ function Preview(props: DatagridDropdownFilterPreviewProps): ReactElement { options={[]} empty={!props.clearable} clearable={props.clearable} + showCheckboxes={false} value={getPreviewValue(props)} onClear={noop} useSelectProps={() => ({ items: [] })} @@ -25,11 +26,7 @@ const noop = (): void => {}; function getPreviewValue(props: DatagridDropdownFilterPreviewProps): string { let value = props.defaultValue; - if (!props.filterable) { - value ||= props.emptyOptionCaption || "Select"; - } else { - value ||= "Search"; - } + value ||= props.emptySelectionCaption || (props.filterable ? "Search" : "Select"); return value; } diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx index 5435f96422..fb7ff098a6 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx @@ -16,7 +16,9 @@ function Container(props: DatagridDropdownFilterContainerProps & Select_FilterAP parentChannelName: props.parentChannelName, name: props.name, multiselect: props.multiSelect, - emptyCaption: props.emptyOptionCaption?.value, + emptySelectionCaption: props.emptySelectionCaption?.value ?? "", + emptyOptionCaption: props.emptyOptionCaption?.value ?? "", + placeholder: props.filterInputPlaceholderCaption?.value ?? "", defaultValue: props.defaultValue?.value, filterable: props.filterable, selectionMethod: props.selectionMethod, diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml index 5e32a4a329..b28a99f14d 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml @@ -83,13 +83,23 @@ - + Input caption Assistive technology will read this upon reaching the input element. + + + Empty selection caption + This text is shown if no options are selected. For example 'No options are selected' or 'Select color'. + + + Filter input placeholder + This text is shown as placeholder for filterable filters. For example 'type to search'. + + diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx index 5b332e833e..50dd78fec4 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx @@ -18,7 +18,9 @@ export interface RefFilterContainerProps { ariaLabel?: string; className?: string; defaultValue?: string; - emptyCaption?: string; + emptyOptionCaption: string; + emptySelectionCaption: string; + placeholder: string; filterStore: RefFilterStore; multiselect: boolean; name: string; @@ -80,6 +82,7 @@ const ComboboxWidget = observer(function ComboboxWidget(props: RefFilterContaine ; onChange?: ActionValue; ariaLabel?: DynamicValue; + emptySelectionCaption?: DynamicValue; + filterInputPlaceholderCaption?: DynamicValue; } export interface DatagridDropdownFilterPreviewProps { @@ -62,4 +64,6 @@ export interface DatagridDropdownFilterPreviewProps { valueAttribute: string; onChange: {} | null; ariaLabel: string; + emptySelectionCaption: string; + filterInputPlaceholderCaption: string; } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/PickerBaseController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/PickerBaseController.ts index a762277879..d88aaffd5f 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/PickerBaseController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/PickerBaseController.ts @@ -58,5 +58,6 @@ export interface PickerBaseControllerProps { multiselect: boolean; onChange?: ActionValue; valueAttribute?: EditableValue; - emptyCaption?: string; + emptyOptionCaption?: string; + emptySelectionCaption?: string; } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts index c355a2e530..43a282cd76 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefBaseController.ts @@ -35,6 +35,7 @@ export interface RefBaseControllerProps { multiselect: boolean; onChange?: ActionValue; valueAttribute?: EditableValue; - emptyCaption?: string; - placeholder?: string; + emptyOptionCaption: string; + emptySelectionCaption: string; + placeholder: string; } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefComboboxController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefComboboxController.ts index d211132988..bbc714374b 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefComboboxController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefComboboxController.ts @@ -4,7 +4,8 @@ import { RefBaseController, RefBaseControllerProps } from "./RefBaseController"; export class RefComboboxController extends ComboboxControllerMixin(RefBaseController) { constructor(props: RefBaseControllerProps) { super({ ...props, multiselect: false }); - this.inputPlaceholder = props.placeholder ?? "Search"; + this.inputPlaceholder = props.placeholder; + this.emptyCaption = props.emptySelectionCaption; } handleFocus = (event: React.FocusEvent): void => { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefSelectController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefSelectController.ts index 6d5c46280c..d79b1844a0 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefSelectController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefSelectController.ts @@ -4,8 +4,8 @@ import { SelectControllerMixin } from "./mixins/SelectControllerMixin"; export class RefSelectController extends SelectControllerMixin(RefBaseController) { constructor(props: RefBaseControllerProps) { super(props); - this.emptyOption.caption = props.emptyCaption || "None"; - this.placeholder = props.placeholder || "Search"; + this.emptyOption.caption = props.emptyOptionCaption; + this.emptyCaption = props.emptySelectionCaption; } handleFocus = (): void => { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefTagPickerController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefTagPickerController.ts index 0595766f8d..43ba9178a7 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/RefTagPickerController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/RefTagPickerController.ts @@ -15,7 +15,8 @@ export class RefTagPickerController extends TagPickerControllerMixin(RefBaseCont constructor(props: Props) { super(props); - this.inputPlaceholder = props.placeholder ?? "Search"; + this.inputPlaceholder = props.placeholder; + this.emptyCaption = props.emptySelectionCaption; 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/controllers/picker/StaticBaseController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts index b9506dcd99..62d75fa38a 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticBaseController.ts @@ -55,8 +55,9 @@ export interface StaticBaseControllerProps { multiselect: boolean; onChange?: ActionValue; valueAttribute?: EditableValue; - emptyCaption?: string; - placeholder?: string; + emptyOptionCaption: string; + emptySelectionCaption: string; + placeholder: string; } export interface CustomOption { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts index c6410a631c..a22e8ccf1b 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticComboboxController.ts @@ -5,5 +5,6 @@ export class StaticComboboxController extends ComboboxControllerMixin(StaticBase constructor(props: StaticBaseControllerProps) { super({ ...props, multiselect: false }); this.inputPlaceholder = props.placeholder ?? "Search"; + this.emptyCaption = props.emptySelectionCaption; } } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts index 8174fbb975..5159b517c6 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticSelectController.ts @@ -4,7 +4,7 @@ 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"; + this.emptyOption.caption = props.emptyOptionCaption; + this.emptyCaption = props.emptySelectionCaption; } } diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts index 11bc90fd30..9ca14499a7 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/StaticTagPickerController.ts @@ -15,7 +15,8 @@ export class StaticTagPickerController extends TagPickerControllerMixin(StaticBa constructor(props: Props) { super(props); - this.inputPlaceholder = props.placeholder ?? "Search"; + this.inputPlaceholder = props.placeholder; + this.emptyCaption = props.emptySelectionCaption; 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/controllers/picker/mixins/ComboboxControllerMixin.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/ComboboxControllerMixin.ts index 0364d9331f..cf24e575e0 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/ComboboxControllerMixin.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/ComboboxControllerMixin.ts @@ -26,6 +26,7 @@ export function ComboboxControllerMixin(Base: TBas touched = false; inputValue = ""; inputPlaceholder = ""; + emptyCaption = ""; constructor(...args: any[]) { super(...args); diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/SelectControllerMixin.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/SelectControllerMixin.ts index c765116ed8..fe2ab40320 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/SelectControllerMixin.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/SelectControllerMixin.ts @@ -22,11 +22,11 @@ const none = "[[__none__]]" as const; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function SelectControllerMixin(Base: TBase) { return class SelectControllerMixin extends Base { - placeholder = "Select"; + emptyCaption = ""; readonly emptyOption = { value: none, - caption: "None", + caption: "", selected: false }; @@ -41,6 +41,9 @@ export function SelectControllerMixin(Base: TBase) } get options(): OptionWithState[] { + if (this.multiselect) { + return this.filterStore.options; + } return [this.emptyOption, ...this.filterStore.options]; } @@ -52,7 +55,7 @@ export function SelectControllerMixin(Base: TBase) const selected = this.filterStore.selectedOptions; if (selected.length < 1) { - return this.placeholder; + return this.emptyCaption; } return selected.map(option => option.caption).join(", "); @@ -84,6 +87,18 @@ export function SelectControllerMixin(Base: TBase) if (this.multiselect) { props.stateReducer = (state, { changes, type }) => { switch (type) { + case useSelect.stateChangeTypes.ToggleButtonClick: + if (state.isOpen) { + return { + ...changes, + isOpen: true, + highlightedIndex: state.highlightedIndex + }; + } + + return { + ...changes + }; case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter: case useSelect.stateChangeTypes.ItemClick: return { diff --git a/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/TagPickerControllerMixin.ts b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/TagPickerControllerMixin.ts index 42ed406a37..5a2079b33c 100644 --- a/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/TagPickerControllerMixin.ts +++ b/packages/shared/widget-plugin-filtering/src/controllers/picker/mixins/TagPickerControllerMixin.ts @@ -27,6 +27,7 @@ export function TagPickerControllerMixin(Base: TBa touched = false; inputValue = ""; inputPlaceholder = ""; + emptyCaption = ""; filterSelectedOptions = false; constructor(...args: any[]) { @@ -142,6 +143,15 @@ export function TagPickerControllerMixin(Base: TBa highlightedIndex: state.highlightedIndex, inputValue: state.inputValue }; + case useCombobox.stateChangeTypes.InputClick: + if (state.isOpen) { + return { + ...changes, + isOpen: true, + highlightedIndex: state.highlightedIndex + }; + } + return changes; default: return { ...changes, diff --git a/packages/shared/widget-plugin-filtering/src/controls/base/ClearButton.tsx b/packages/shared/widget-plugin-filtering/src/controls/base/ClearButton.tsx index 6dba754f23..2a70606529 100644 --- a/packages/shared/widget-plugin-filtering/src/controls/base/ClearButton.tsx +++ b/packages/shared/widget-plugin-filtering/src/controls/base/ClearButton.tsx @@ -1,4 +1,4 @@ -import { createElement, Fragment, ReactElement } from "react"; +import { createElement, ReactElement, useCallback } from "react"; import { Cross } from "../picker-primitives"; type ClearButtonClassNamesProps = { @@ -10,18 +10,26 @@ type ClearButtonClassNamesProps = { type ClearButtonProps = { cls: ClearButtonClassNamesProps; onClick?: () => void; - showSeparator?: boolean; visible: boolean; }; export function ClearButton(props: ClearButtonProps): ReactElement | null { - const { cls, onClick, showSeparator, visible } = props; + const { cls, onClick, visible } = props; + + const onClickHandler = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (onClick) { + onClick(); + } + }, + [onClick] + ); + return visible ? ( - - - {showSeparator &&
} - + ) : null; } diff --git a/packages/shared/widget-plugin-filtering/src/controls/base/OptionsWrapper.tsx b/packages/shared/widget-plugin-filtering/src/controls/base/OptionsWrapper.tsx index b0b0170054..00962c98eb 100644 --- a/packages/shared/widget-plugin-filtering/src/controls/base/OptionsWrapper.tsx +++ b/packages/shared/widget-plugin-filtering/src/controls/base/OptionsWrapper.tsx @@ -18,8 +18,7 @@ type OptionsWrapperProps = { isOpen: boolean; options: OptionWithState[]; highlightedIndex: number; - showCheckboxes?: boolean; - haveEmptyFirstOption?: boolean; + showCheckboxes: boolean; } & ( | Pick, "getMenuProps" | "getItemProps"> | Pick, "getMenuProps" | "getItemProps"> @@ -28,17 +27,7 @@ type OptionsWrapperProps = { const noop = (): void => {}; export const OptionsWrapper = forwardRef((props: OptionsWrapperProps, ref: RefObject): ReactElement => { - const { - cls, - style, - onMenuScroll, - isOpen, - highlightedIndex, - showCheckboxes, - haveEmptyFirstOption, - getMenuProps, - getItemProps - } = props; + const { cls, style, onMenuScroll, isOpen, highlightedIndex, showCheckboxes, getMenuProps, getItemProps } = props; return (