From 8ffc9fe471326679f44d14a8f83634c92bda1545 Mon Sep 17 00:00:00 2001 From: melloware Date: Mon, 14 Aug 2023 10:31:37 -0400 Subject: [PATCH] Fix #4716: TreeSelect showClear and clearIcon --- components/doc/treeselect/clearicondoc.js | 107 ++++++++++++++++++++ components/lib/dropdown/DropdownBase.js | 8 +- components/lib/treeselect/TreeSelect.js | 48 ++++++++- components/lib/treeselect/TreeSelectBase.js | 4 + components/lib/treeselect/treeselect.d.ts | 13 +++ pages/treeselect/index.js | 12 ++- 6 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 components/doc/treeselect/clearicondoc.js diff --git a/components/doc/treeselect/clearicondoc.js b/components/doc/treeselect/clearicondoc.js new file mode 100644 index 0000000000..0d9b51f97d --- /dev/null +++ b/components/doc/treeselect/clearicondoc.js @@ -0,0 +1,107 @@ +import React, { useEffect, useState } from 'react'; +import { NodeService } from '../../../service/NodeService'; +import { TreeSelect } from '../../lib/treeselect/TreeSelect'; +import { DocSectionCode } from '../common/docsectioncode'; +import { DocSectionText } from '../common/docsectiontext'; + +export function ClearIconDoc(props) { + const [nodes, setNodes] = useState(null); + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + + useEffect(() => { + NodeService.getTreeNodes().then((data) => setNodes(data)); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const code = { + basic: ` + setSelectedNodeKey(e.value)} options={nodes} + className="md:w-20rem w-full" placeholder="Select Item"> + `, + javascript: ` +import React, { useState, useEffect } from "react"; +import { TreeSelect } from 'primereact/treeselect'; +import { NodeService } from './service/NodeService'; + +export default function BasicDemo() { + const [nodes, setNodes] = useState(null); + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + + useEffect(() => { + NodeService.getTreeNodes().then((data) => setNodes(data)); + }, []); + + return ( +
+ setSelectedNodeKey(e.value)} options={nodes} + className="md:w-20rem w-full" placeholder="Select Item" showClear> +
+ ); +} + `, + typescript: ` +import React, { useState, useEffect } from "react"; +import { TreeSelect, TreeSelectChangeEvent } from 'primereact/treeselect'; +import { TreeNode } from 'primereact/treenode'; +import { NodeService } from './service/NodeService'; + +export default function BasicDemo() { + const [nodes, setNodes] = useState(null); + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + + useEffect(() => { + NodeService.getTreeNodes().then((data) => setNodes(data)); + }, []); + + return ( +
+ setSelectedNodeKey(e.value)} + className="md:w-20rem w-full" placeholder="Select Item" showClear> +
+ ); +} + `, + data: ` +/* NodeService */ +{ + key: '0', + label: 'Documents', + data: 'Documents Folder', + icon: 'pi pi-fw pi-inbox', + children: [ + { + key: '0-0', + label: 'Work', + data: 'Work Folder', + icon: 'pi pi-fw pi-cog', + children: [ + { key: '0-0-0', label: 'Expenses.doc', icon: 'pi pi-fw pi-file', data: 'Expenses Document' }, + { key: '0-0-1', label: 'Resume.doc', icon: 'pi pi-fw pi-file', data: 'Resume Document' } + ] + }, + { + key: '0-1', + label: 'Home', + data: 'Home Folder', + icon: 'pi pi-fw pi-home', + children: [{ key: '0-1-0', label: 'Invoices.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month' }] + } + ] +}, +... +` + }; + + return ( + <> + +

+ When showClear is enabled, a clear icon is added to reset the TreeSelect. +

+
+
+ setSelectedNodeKey(e.value)} options={nodes} className="md:w-20rem w-full" placeholder="Select Item" showClear> +
+ + + ); +} diff --git a/components/lib/dropdown/DropdownBase.js b/components/lib/dropdown/DropdownBase.js index ae3a84a28f..2bb561227a 100644 --- a/components/lib/dropdown/DropdownBase.js +++ b/components/lib/dropdown/DropdownBase.js @@ -23,7 +23,6 @@ const classes = { 'p-dropdown-label-empty': label === null && !props.placeholder }), trigger: 'p-dropdown-trigger', - clearIcon: 'p-dropdown-clear-icon p-clickable', emptyMessage: 'p-dropdown-empty-message', itemGroup: 'p-dropdown-item-group', dropdownIcon: 'p-dropdown-trigger-icon p-clickable', @@ -60,12 +59,6 @@ const styles = ` user-select: none; } -.p-dropdown-clear-icon { - position: absolute; - top: 50%; - margin-top: -.5rem; -} - .p-dropdown-trigger { display: flex; align-items: center; @@ -128,6 +121,7 @@ input.p-dropdown-label { position: relative; } +.p-dropdown-clear-icon, .p-dropdown-filter-icon, .p-dropdown-filter-clear-icon { position: absolute; diff --git a/components/lib/treeselect/TreeSelect.js b/components/lib/treeselect/TreeSelect.js index ff6ab9854f..a8393ef867 100644 --- a/components/lib/treeselect/TreeSelect.js +++ b/components/lib/treeselect/TreeSelect.js @@ -1,6 +1,6 @@ import * as React from 'react'; -import PrimeReact, { localeOption } from '../api/Api'; -import { PrimeReactContext } from '../api/Api'; +import PrimeReact, { PrimeReactContext, localeOption } from '../api/Api'; +import { useHandleStyle } from '../componentbase/ComponentBase'; import { useMountEffect, useOverlayListener, useUnmountEffect, useUpdateEffect } from '../hooks/Hooks'; import { ChevronDownIcon } from '../icons/chevrondown'; import { SearchIcon } from '../icons/search'; @@ -8,10 +8,9 @@ import { TimesIcon } from '../icons/times'; import { OverlayService } from '../overlayservice/OverlayService'; import { Ripple } from '../ripple/Ripple'; import { Tree } from '../tree/Tree'; -import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils, classNames, mergeProps } from '../utils/Utils'; +import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils, mergeProps } from '../utils/Utils'; import { TreeSelectBase } from './TreeSelectBase'; import { TreeSelectPanel } from './TreeSelectPanel'; -import { useHandleStyle } from '../componentbase/ComponentBase'; export const TreeSelect = React.memo( React.forwardRef((inProps, ref) => { @@ -110,6 +109,28 @@ export const TreeSelect = React.memo( } }; + const clear = (event) => { + if (props.onChange) { + selfChange.current = true; + + props.onChange({ + originalEvent: event, + value: undefined, + stopPropagation: () => { + event.stopPropagation(); + }, + preventDefault: () => { + event.preventDefault(); + }, + target: { + name: props.name, + id: props.id, + value: undefined + } + }); + } + }; + const onNodeSelect = (node) => { props.onNodeSelect && props.onNodeSelect(node); isSingleSelectionMode && hide(); @@ -485,6 +506,23 @@ export const TreeSelect = React.memo( return
{dropdownIcon}
; }; + const createClearIcon = () => { + if (props.value != null && props.showClear && !props.disabled) { + const clearIconProps = mergeProps( + { + className: cx('clearIcon'), + onPointerUp: clear + }, + ptm('clearIcon') + ); + const icon = props.clearIcon || ; + + return IconUtils.getJSXIcon(icon, { ...clearIconProps }, { props }); + } + + return null; + }; + const createContent = () => { const emptyMessageProps = mergeProps( { @@ -665,6 +703,7 @@ export const TreeSelect = React.memo( const keyboardHelper = createKeyboardHelper(); const labelElement = createLabel(); const dropdownIcon = createDropdownIcon(); + const clearIcon = createClearIcon(); const content = createContent(); const header = createHeader(); const footer = createFooter(); @@ -673,6 +712,7 @@ export const TreeSelect = React.memo(
{keyboardHelper} {labelElement} + {clearIcon} {dropdownIcon} | React.HTMLAttributes>; + /** + * uses to pass attributes to the clear icon's DOM element. + */ + clearIcon?: TreeSelectPassThroughType | React.HTMLAttributes>; } /** @@ -303,6 +307,10 @@ export interface TreeSelectProps extends Omit | undefined; /** * Icon of the close button. */ @@ -439,6 +447,11 @@ export interface TreeSelectProps extends Omit { @@ -43,6 +44,11 @@ const TreeSelectDemo = () => { label: 'Filter', component: FilterDoc }, + { + id: 'clearicon', + label: 'Clear Icon', + component: ClearIconDoc + }, { id: 'controlled', label: 'Controlled',