diff --git a/components/lib/accordion/Accordion.js b/components/lib/accordion/Accordion.js index 55d57311e2..6572df2596 100644 --- a/components/lib/accordion/Accordion.js +++ b/components/lib/accordion/Accordion.js @@ -176,7 +176,7 @@ export const Accordion = React.forwardRef((inProps, ref) => { }; const isSelected = (index) => { - return props.multiple ? activeIndex && activeIndex.some((i) => i === index) : activeIndex === index; + return props.multiple && Array.isArray(activeIndex) ? activeIndex && activeIndex.some((i) => i === index) : activeIndex === index; }; React.useImperativeHandle(ref, () => ({ diff --git a/components/lib/autocomplete/AutoCompletePanel.js b/components/lib/autocomplete/AutoCompletePanel.js index 4bdf8653c9..d7b9170895 100644 --- a/components/lib/autocomplete/AutoCompletePanel.js +++ b/components/lib/autocomplete/AutoCompletePanel.js @@ -21,7 +21,8 @@ export const AutoCompletePanel = React.memo( const getPTOptions = (item, key) => { return _ptm(key, { context: { - selected: props.selectedItem.current === item + selected: props.selectedItem.current === item, + disabled: item.disabled } }); }; @@ -107,7 +108,7 @@ export const AutoCompletePanel = React.memo( className: cx('item', { suggestion }), style, onClick: (e) => props.onItemClick(e, suggestion), - 'aria-selected': props.selectedItem === suggestion, + 'aria-selected': props.selectedItem.current === suggestion, 'data-p-disabled': suggestion.disabled }, getPTOptions(suggestion, 'item') diff --git a/components/lib/autocomplete/autocomplete.d.ts b/components/lib/autocomplete/autocomplete.d.ts index 14c87ff681..1e6502869d 100755 --- a/components/lib/autocomplete/autocomplete.d.ts +++ b/components/lib/autocomplete/autocomplete.d.ts @@ -216,6 +216,11 @@ export interface AutoCompleteContext { * @defaultValue false */ selected: boolean; + /** + * Current disabled state of the item as a boolean. + * @defaultValue false + */ + disabled: boolean; } /** diff --git a/components/lib/blockui/BlockUI.js b/components/lib/blockui/BlockUI.js index 31ad966689..589157e4f8 100644 --- a/components/lib/blockui/BlockUI.js +++ b/components/lib/blockui/BlockUI.js @@ -13,6 +13,7 @@ export const BlockUI = React.forwardRef((inProps, ref) => { const [visibleState, setVisibleState] = React.useState(props.blocked); const elementRef = React.useRef(null); const maskRef = React.useRef(null); + const activeElementRef = React.useRef(null); const { ptm, cx, isUnstyled } = BlockUIBase.setMetaData({ props @@ -22,13 +23,18 @@ export const BlockUI = React.forwardRef((inProps, ref) => { const block = () => { setVisibleState(true); + activeElementRef.current = document.activeElement; }; const unblock = () => { const callback = () => { setVisibleState(false); - props.fullScreen && DomHandler.unblockBodyScroll(); + if (props.fullScreen) { + DomHandler.unblockBodyScroll(); + activeElementRef.current && activeElementRef.current.focus(); + } + props.onUnblocked && props.onUnblocked(); }; @@ -46,7 +52,7 @@ export const BlockUI = React.forwardRef((inProps, ref) => { const onPortalMounted = () => { if (props.fullScreen) { DomHandler.blockBodyScroll(); - document.activeElement.blur(); + activeElementRef.current && activeElementRef.current.blur(); } if (props.autoZIndex) { @@ -67,9 +73,7 @@ export const BlockUI = React.forwardRef((inProps, ref) => { }, [props.blocked]); useUnmountEffect(() => { - if (props.fullScreen) { - DomHandler.unblockBodyScroll(); - } + props.fullScreen && DomHandler.unblockBodyScroll(); ZIndexUtils.clear(maskRef.current); }); diff --git a/components/lib/calendar/Calendar.js b/components/lib/calendar/Calendar.js index 46257d143b..74d95d2ba0 100644 --- a/components/lib/calendar/Calendar.js +++ b/components/lib/calendar/Calendar.js @@ -1839,11 +1839,7 @@ export const Calendar = React.memo( } } - if (props.disabledDates || props.enabledDates) { - validDate = !isDateDisabled(day, month, year); - } - - if (props.disabledDays && currentView === 'date') { + if (props.disabledDates || props.enabledDates || props.disabledDays) { validDay = !isDayDisabled(day, month, year); } @@ -1993,27 +1989,46 @@ export const Calendar = React.memo( return today.getDate() === day && today.getMonth() === month && today.getFullYear() === year; }; - const isDateDisabled = (day, month, year) => { + const isDayDisabled = (day, month, year) => { if (props.disabledDates) { - return props.disabledDates.some((d) => d.getFullYear() === year && d.getMonth() === month && d.getDate() === day); + if (props.disabledDates.some((d) => d.getFullYear() === year && d.getMonth() === month && d.getDate() === day)) { + return true; + } + } else if (props.enabledDates) { + if (!props.enabledDates.some((d) => d.getFullYear() === year && d.getMonth() === month && d.getDate() === day)) { + return true; + } } - if (props.enabledDates) { - return !props.enabledDates.some((d) => d.getFullYear() === year && d.getMonth() === month && d.getDate() === day); + if (props.disabledDays && currentView === 'date') { + let weekday = new Date(year, month, day); + let weekdayNumber = weekday.getDay(); + + if (props.disabledDays.indexOf(weekdayNumber) !== -1) { + return true; + } } return false; }; - const isDayDisabled = (day, month, year) => { - if (props.disabledDays) { - let weekday = new Date(year, month, day); - let weekdayNumber = weekday.getDay(); + const isMonthYearDisabled = (month, year) => { + const daysCountInAllMonth = month === -1 ? new Array(12).fill(0).map((_, i) => getDaysCountInMonth(i, year)) : [getDaysCountInMonth(month, year)]; + + for (let i = 0; i < daysCountInAllMonth.length; i++) { + const monthDays = daysCountInAllMonth[i]; + const _month = month === -1 ? i : month; + + for (let day = 1; day <= monthDays; day++) { + let isDateSelectable = isSelectable(day, _month, year); - return props.disabledDays.indexOf(weekdayNumber) !== -1; + if (isDateSelectable) { + return false; + } + } } - return false; + return true; }; const updateInputfield = (value) => { @@ -2582,7 +2597,10 @@ export const Calendar = React.memo( useUpdateEffect(() => { if (overlayVisibleState || props.visible) { - alignOverlay(); + // Github #5529 + setTimeout(() => { + alignOverlay(); + }); } }, [currentView, overlayVisibleState, props.visible]); @@ -3654,10 +3672,10 @@ export const Calendar = React.memo( {monthPickerValues().map((m, i) => { const monthProps = mergeProps( { - className: cx('month', { isMonthSelected, isSelectable, i, currentYear }), + className: cx('month', { isMonthSelected, isMonthYearDisabled, i, currentYear }), onClick: (event) => onMonthSelect(event, i), onKeyDown: (event) => onMonthCellKeydown(event, i), - 'data-p-disabled': !m.selectable, + 'data-p-disabled': isMonthYearDisabled(i, currentYear), 'data-p-highlight': isMonthSelected(i) }, ptm('month', { @@ -3665,7 +3683,7 @@ export const Calendar = React.memo( month: m, monthIndex: i, selected: isMonthSelected(i), - disabled: !m.selectable + disabled: isMonthYearDisabled(i, currentYear) } }) ); @@ -3697,17 +3715,17 @@ export const Calendar = React.memo( {yearPickerValues().map((y, i) => { const yearProps = mergeProps( { - className: cx('year', { isYearSelected, isSelectable, y }), + className: cx('year', { isYearSelected, isMonthYearDisabled, y }), onClick: (event) => onYearSelect(event, y), 'data-p-highlight': isYearSelected(y), - 'data-p-disabled': !isSelectable(0, -1, y) + 'data-p-disabled': isMonthYearDisabled(-1, y) }, ptm('year', { context: { year: y, yearIndex: i, selected: isYearSelected(i), - disabled: !y.selectable + disabled: isMonthYearDisabled(-1, y) } }) ); diff --git a/components/lib/calendar/Calendar.spec.js b/components/lib/calendar/Calendar.spec.js new file mode 100644 index 0000000000..e38ff1eef1 --- /dev/null +++ b/components/lib/calendar/Calendar.spec.js @@ -0,0 +1,93 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import { PrimeReactProvider } from '../api/Api'; +import { Calendar } from './Calendar'; + +describe('Calendar', () => { + function getAllDatesOfYear(year) { + let startDate = new Date(year, 0, 1); + let endDate = new Date(year, 11, 31); + + let dates = []; + let currentDate = startDate; + + while (currentDate <= endDate) { + dates.push(new Date(currentDate)); + currentDate.setDate(currentDate.getDate() + 1); + } + + return dates; + } + + test('When the days of the year are disabled, then the years and month should be disabled', async () => { + const { container } = render( + + + + ); + + const years = container.querySelectorAll('.p-yearpicker-year'); + + for (const year of years) { + if (year.innerHTML === '2023') { + expect(year).toHaveAttribute('data-p-disabled', 'true'); + expect(year).toHaveClass('p-disabled'); + } else { + expect(year).toHaveAttribute('data-p-disabled', 'false'); + expect(year).not.toHaveClass('p-disabled'); + } + } + + const { container: monthContainer } = render( + + + + ); + + const months = monthContainer.querySelectorAll('.p-monthpicker-month'); + + for (const month of months) { + expect(month).toHaveAttribute('data-p-disabled', 'true'); + expect(month).toHaveClass('p-disabled'); + } + }); + + test('If any day of the month is not disabled, then both the year and month can be selected', async () => { + const disabledDates = getAllDatesOfYear(2023); + + // January and December are not disabled. + disabledDates.shift(); + disabledDates.pop(); + + const { container: yearContainer } = render( + + + + ); + const years = yearContainer.querySelectorAll('.p-yearpicker-year'); + + for (const year of years) { + expect(year).toHaveAttribute('data-p-disabled', 'false'); + expect(year).not.toHaveClass('p-disabled'); + } + + // month + const { container: monthContainer } = render( + + + + ); + + const months = monthContainer.querySelectorAll('.p-monthpicker-month'); + + Array.from(months).forEach((month, index) => { + if (index === 0 || index === 11) { + expect(month).toHaveAttribute('data-p-disabled', 'false'); + expect(month).not.toHaveClass('p-disabled'); + } else { + expect(month).toHaveAttribute('data-p-disabled', 'true'); + expect(month).toHaveClass('p-disabled'); + } + }); + }); +}); diff --git a/components/lib/calendar/CalendarBase.js b/components/lib/calendar/CalendarBase.js index 4dbcc117c5..f1ec418550 100644 --- a/components/lib/calendar/CalendarBase.js +++ b/components/lib/calendar/CalendarBase.js @@ -190,9 +190,9 @@ const classes = { clearButton: 'p-button-text', footer: 'p-datepicker-footer', yearPicker: 'p-yearpicker', - year: ({ isYearSelected, isSelectable, y }) => classNames('p-yearpicker-year', { 'p-highlight': isYearSelected(y), 'p-disabled': !isSelectable(0, -1, y) }), + year: ({ isYearSelected, y, isMonthYearDisabled }) => classNames('p-yearpicker-year', { 'p-highlight': isYearSelected(y), 'p-disabled': isMonthYearDisabled(-1, y) }), monthPicker: 'p-monthpicker', - month: ({ isMonthSelected, isSelectable, i, currentYear }) => classNames('p-monthpicker-month', { 'p-highlight': isMonthSelected(i), 'p-disabled': !isSelectable(0, i, currentYear) }), + month: ({ isMonthSelected, isMonthYearDisabled, i, currentYear }) => classNames('p-monthpicker-month', { 'p-highlight': isMonthSelected(i), 'p-disabled': isMonthYearDisabled(i, currentYear) }), hourPicker: 'p-hour-picker', secondPicker: 'p-second-picker', minutePicker: 'p-minute-picker', diff --git a/components/lib/cascadeselect/CascadeSelect.js b/components/lib/cascadeselect/CascadeSelect.js index 37663fd207..2137ca206b 100644 --- a/components/lib/cascadeselect/CascadeSelect.js +++ b/components/lib/cascadeselect/CascadeSelect.js @@ -24,11 +24,14 @@ export const CascadeSelect = React.memo( focused: focusedState, overlayVisible: overlayVisibleState, attributeSelector: attributeSelectorState + }, + context: { + ...context } }); useHandleStyle(CascadeSelectBase.css.styles, isUnstyled, { name: 'cascadeselect' }); - useOnEscapeKey(overlayRef, overlayVisibleState, () => hide()); + const elementRef = React.useRef(null); const overlayRef = React.useRef(null); const inputRef = React.useRef(null); @@ -46,6 +49,8 @@ export const CascadeSelect = React.memo( when: overlayVisibleState }); + useOnEscapeKey(overlayRef, overlayVisibleState, () => hide()); + const onOptionSelect = (event) => { if (props.onChange) { props.onChange({ @@ -330,7 +335,7 @@ export const CascadeSelect = React.memo( ref: labelRef, className: cx('label', { label }) }, - ptm('label') + ptm('label', { context: { label, ...context } }) ); return {label}; diff --git a/components/lib/cascadeselect/CascadeSelectBase.js b/components/lib/cascadeselect/CascadeSelectBase.js index afe53059bd..c23d22c390 100644 --- a/components/lib/cascadeselect/CascadeSelectBase.js +++ b/components/lib/cascadeselect/CascadeSelectBase.js @@ -25,10 +25,10 @@ const classes = { 'p-ripple-disabled': (context && context.ripple === false) || PrimeReact.ripple === false }), sublist: 'p-cascadeselect-panel p-cascadeselect-items p-cascadeselect-sublist', - item: ({ option, isOptionGroup, activeOptionState }) => + item: ({ option, isGroup, isSelected }) => classNames('p-cascadeselect-item', { - 'p-cascadeselect-item-group': isOptionGroup(option), - 'p-cascadeselect-item-active p-highlight': activeOptionState === option + 'p-cascadeselect-item-group': isGroup, + 'p-cascadeselect-item-active p-highlight': isSelected }), dropdownIcon: 'p-cascadeselect-trigger-icon', loadingIcon: 'p-cascadeselect-trigger-icon', diff --git a/components/lib/cascadeselect/CascadeSelectSub.js b/components/lib/cascadeselect/CascadeSelectSub.js index 1dd9bf87fa..7dc45c32b6 100644 --- a/components/lib/cascadeselect/CascadeSelectSub.js +++ b/components/lib/cascadeselect/CascadeSelectSub.js @@ -10,9 +10,10 @@ export const CascadeSelectSub = React.memo((props) => { const context = React.useContext(PrimeReactContext); const { ptm, cx } = props; - const getPTOptions = (key) => { + const getPTOptions = (key, options) => { return ptm(key, { - hostName: props.hostName + hostName: props.hostName, + state: { ...options } }); }; @@ -238,15 +239,17 @@ export const CascadeSelectSub = React.memo((props) => { getPTOptions('content') ); + const isSelected = activeOptionState === option; + const isGroup = isOptionGroup(option); const itemProps = mergeProps( { - className: classNames(option.className, cx('item', { option, isOptionGroup, activeOptionState })), + className: classNames(option.className, cx('item', { option, isGroup, isSelected })), style: option.style, role: 'none', - 'data-p-item-group': isOptionGroup(option), - 'data-p-highlight': activeOptionState === option + 'data-p-item-group': isGroup, + 'data-p-highlight': isSelected }, - getPTOptions('item') + getPTOptions('item', { selected: isSelected, group: isGroup }) ); return ( diff --git a/components/lib/cascadeselect/cascadeselect.d.ts b/components/lib/cascadeselect/cascadeselect.d.ts index a935b82688..5024cb8002 100644 --- a/components/lib/cascadeselect/cascadeselect.d.ts +++ b/components/lib/cascadeselect/cascadeselect.d.ts @@ -24,6 +24,7 @@ export declare type CascadeSelectPassThroughTransitionType = ReactCSSTransitionP export interface CascadeSelectPassThroughMethodOptions { props: CascadeSelectProps; state: CascadeSelectState; + context: CascadeSelectContext; } /** @@ -34,18 +35,35 @@ export interface CascadeSelectState { * Current focused state as a boolean. * @defaultValue false */ - focused: boolean; + focused?: boolean; /** * Current overlay visible state as a boolean. * @defaultValue false */ - overlayVisible: boolean; + overlayVisible?: boolean; /** * Current overlay attributeSelector state as a string. */ - attributeSelector: string; + attributeSelector?: string; + /** + * For items, this is the state of the item. + */ + selected?: boolean; + /** + * For items, this is whether it is a group item or not. + */ + grouped?: boolean; } +/** + * Defines current inline context in CascadeSelect component. + */ +export interface CascadeSelectContext extends APIOptions { + /** + * Label of the currently selected item + */ + label?: string; +} /** * Custom passthrough(pt) options. * @see {@link CascadeSelectProps.pt} diff --git a/components/lib/checkbox/Checkbox.js b/components/lib/checkbox/Checkbox.js index e15b0f122a..dfee79bf72 100644 --- a/components/lib/checkbox/Checkbox.js +++ b/components/lib/checkbox/Checkbox.js @@ -1,11 +1,11 @@ import * as React from 'react'; import { PrimeReactContext } from '../api/Api'; +import { useHandleStyle } from '../componentbase/ComponentBase'; import { useMountEffect, useUpdateEffect } from '../hooks/Hooks'; import { CheckIcon } from '../icons/check'; import { Tooltip } from '../tooltip/Tooltip'; import { DomHandler, IconUtils, ObjectUtils, classNames, mergeProps } from '../utils/Utils'; import { CheckboxBase } from './CheckboxBase'; -import { useHandleStyle } from '../componentbase/ComponentBase'; export const Checkbox = React.memo( React.forwardRef((inProps, ref) => { @@ -37,35 +37,34 @@ export const Checkbox = React.memo( const checkboxClicked = event.target instanceof HTMLDivElement || event.target instanceof HTMLSpanElement || event.target instanceof Object; const isInputToggled = event.target === inputRef.current; const isCheckboxToggled = checkboxClicked && event.target.checked !== checked; - - if (isInputToggled || isCheckboxToggled) { - const value = checked ? props.falseValue : props.trueValue; - const eventData = { - originalEvent: event, + const value = checked ? props.falseValue : props.trueValue; + const eventData = { + originalEvent: event, + value: props.value, + checked: value, + stopPropagation: () => { + event.stopPropagation(); + }, + preventDefault: () => { + event.preventDefault(); + }, + target: { + type: 'checkbox', + name: props.name, + id: props.id, value: props.value, - checked: value, - stopPropagation: () => { - event.stopPropagation(); - }, - preventDefault: () => { - event.preventDefault(); - }, - target: { - type: 'checkbox', - name: props.name, - id: props.id, - value: props.value, - checked: value - } - }; - - props.onClick && props.onClick(eventData); - - // do not continue if the user defined click wants to prevent - if (event.defaultPrevented) { - return; + checked: value } + }; + props.onClick && props.onClick(eventData); + + // do not continue if the user defined click wants to prevent + if (event.defaultPrevented) { + return; + } + + if (isInputToggled || isCheckboxToggled) { props.onChange && props.onChange(eventData); } diff --git a/components/lib/chip/Chip.js b/components/lib/chip/Chip.js index d7c0e935e6..29fb9af7a1 100644 --- a/components/lib/chip/Chip.js +++ b/components/lib/chip/Chip.js @@ -27,7 +27,10 @@ export const Chip = React.memo( setVisibleState(false); if (props.onRemove) { - props.onRemove(event); + props.onRemove({ + originalEvent: event, + value: props.label || props.image || props.icon + }); } }; diff --git a/components/lib/chip/chip.d.ts b/components/lib/chip/chip.d.ts index f541f42b31..4aa2816bec 100644 --- a/components/lib/chip/chip.d.ts +++ b/components/lib/chip/chip.d.ts @@ -65,6 +65,21 @@ export interface ChipState { visible: boolean; } +/** + * Custom remove event + * @event + */ +interface ChipRemoveEvent { + /** + * Browser event + */ + originalEvent: React.SyntheticEvent; + /** + * Removed item value + */ + value: string; +} + /** * Defines valid properties in Chip component. In addition to these, all properties of HTMLDivElement can be used in this component. * @group Properties @@ -106,9 +121,9 @@ export interface ChipProps extends Omit): void; + onRemove?(event: ChipRemoveEvent): void; /** * Used to get the child elements of the component. * @readonly diff --git a/components/lib/componentbase/ComponentBase.js b/components/lib/componentbase/ComponentBase.js index 6c1d309154..aeb24e2ed6 100644 --- a/components/lib/componentbase/ComponentBase.js +++ b/components/lib/componentbase/ComponentBase.js @@ -1,7 +1,7 @@ import PrimeReact from '../api/Api'; import { useMountEffect, useStyle, useUnmountEffect, useUpdateEffect } from '../hooks/Hooks'; import { mergeProps } from '../utils/MergeProps'; -import { ObjectUtils } from '../utils/Utils'; +import { ObjectUtils, classNames } from '../utils/Utils'; const baseStyle = ` .p-hidden-accessible { @@ -520,7 +520,14 @@ export const ComponentBase = { const getPTClassValue = (...args) => { const value = getOptionValue(...args); - return ObjectUtils.isString(value) ? { className: value } : value; + if (Array.isArray(value)) return { className: classNames(...value) }; + if (ObjectUtils.isString(value)) return { className: value }; + + if (value?.hasOwnProperty('className') && Array.isArray(value.className)) { + return { className: classNames(...value.className) }; + } + + return value; }; const globalPT = searchInDefaultPT ? (isNestedParam ? _useGlobalPT(getPTClassValue, originalkey, params) : _useDefaultPT(getPTClassValue, originalkey, params)) : undefined; diff --git a/components/lib/confirmdialog/ConfirmDialog.js b/components/lib/confirmdialog/ConfirmDialog.js index 918624bfc2..101a64ae0e 100644 --- a/components/lib/confirmdialog/ConfirmDialog.js +++ b/components/lib/confirmdialog/ConfirmDialog.js @@ -160,6 +160,7 @@ export const ConfirmDialog = React.memo( icon: getPropValue('acceptIcon'), className: classNames(getPropValue('acceptClassName'), cx('acceptButton')), onClick: accept, + pt: ptm('acceptButton'), unstyled: props.unstyled, __parentMetadata: { parent: metaData diff --git a/components/lib/datatable/BodyCell.js b/components/lib/datatable/BodyCell.js index 0b3f3ca351..47be72ff03 100644 --- a/components/lib/datatable/BodyCell.js +++ b/components/lib/datatable/BodyCell.js @@ -27,11 +27,10 @@ export const BodyCell = React.memo((props) => { const { ptm, ptmo, cx } = props.ptCallbacks; const getColumnProp = (name) => ColumnBase.getCProp(props.column, name); - const getColumnProps = (column) => ColumnBase.getCProps(column); + const getColumnProps = () => ColumnBase.getCProps(props.column); const getColumnPTOptions = (key) => { - const cProps = getColumnProps(props.column); - + const cProps = getColumnProps(); const columnMetaData = { props: cProps, parent: props.metaData, @@ -228,7 +227,7 @@ export const BodyCell = React.memo((props) => { clearTimeout(tabindexTimeout.current); tabindexTimeout.current = setTimeout(() => { if (editingState) { - const focusableEl = props.editMode === 'cell' ? DomHandler.getFirstFocusableElement(elementRef.current, ':not(.p-cell-editor-key-helper)') : DomHandler.findSingle(elementRef.current, '[data-p-row-editor-save="true"]'); + const focusableEl = props.editMode === 'cell' ? DomHandler.getFirstFocusableElement(elementRef.current, ':not([data-pc-section="editorkeyhelperlabel"])') : DomHandler.findSingle(elementRef.current, '[data-p-row-editor-save="true"]'); focusableEl && focusableEl.focus(); } diff --git a/components/lib/datatable/DataTable.js b/components/lib/datatable/DataTable.js index d760c99fd0..97b40c4b25 100644 --- a/components/lib/datatable/DataTable.js +++ b/components/lib/datatable/DataTable.js @@ -371,7 +371,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { const saveColumnWidths = (state) => { let widths = []; - let headers = DomHandler.find(elementRef.current, '.p-datatable-thead > tr > th'); + let headers = DomHandler.find(elementRef.current, '[data-pc-section="thead"] > tr > th'); headers.forEach((header) => widths.push(DomHandler.getOuterWidth(header))); state.columnWidths = widths.join(','); @@ -384,15 +384,15 @@ export const DataTable = React.forwardRef((inProps, ref) => { const addColumnWidthStyles = (widths) => { createStyleElement(); let innerHTML = ''; - let selector = `.p-datatable[${attributeSelector.current}] > .p-datatable-wrapper ${isVirtualScrollerDisabled() ? '' : '> .p-virtualscroller'} > .p-datatable-table`; + let selector = `[data-pc-name="datatable"][${attributeSelector.current}] > [data-pc-section="wrapper"] ${isVirtualScrollerDisabled() ? '' : '> [data-pc-name="virtualscroller"]'} > [data-pc-section="table"]`; widths.forEach((width, index) => { let style = `width: ${width}px !important; max-width: ${width}px !important`; innerHTML += ` - ${selector} > .p-datatable-thead > tr > th:nth-child(${index + 1}), - ${selector} > .p-datatable-tbody > tr > td:nth-child(${index + 1}), - ${selector} > .p-datatable-tfoot > tr > td:nth-child(${index + 1}) { + ${selector} > [data-pc-section="thead"] > tr > th:nth-child(${index + 1}), + ${selector} > [data-pc-section="tbody"] > tr > td:nth-child(${index + 1}), + ${selector} > [data-pc-section="tfoot"] > tr > td:nth-child(${index + 1}) { ${style} } `; @@ -557,7 +557,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { updateTableWidth(frozenBodyRef.current); if (wrapperRef.current) { - updateTableWidth(DomHandler.findSingle(wrapperRef.current, '.p-virtualscroller-content')); + updateTableWidth(DomHandler.findSingle(wrapperRef.current, '[data-pc-name="virtualscroller"] > table > tbody')); } } } @@ -587,7 +587,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { const resizeTableCells = (newColumnWidth, nextColumnWidth) => { let widths = []; let colIndex = DomHandler.index(resizeColumnElement.current); - let headers = DomHandler.find(tableRef.current, '.p-datatable-thead > tr > th'); + let headers = DomHandler.find(tableRef.current, '[data-pc-section="thead"] > tr > th'); headers.forEach((header) => widths.push(DomHandler.getOuterWidth(header))); @@ -595,16 +595,16 @@ export const DataTable = React.forwardRef((inProps, ref) => { createStyleElement(); let innerHTML = ''; - let selector = `.p-datatable[${attributeSelector.current}] > .p-datatable-wrapper ${isVirtualScrollerDisabled() ? '' : '> .p-virtualscroller'} > .p-datatable-table`; + let selector = `[data-pc-name="datatable"][${attributeSelector.current}] > [data-pc-section="wrapper"] ${isVirtualScrollerDisabled() ? '' : '> [data-pc-name="virtualscroller"]'} > [data-pc-section="table"]`; widths.forEach((width, index) => { let colWidth = index === colIndex ? newColumnWidth : nextColumnWidth && index === colIndex + 1 ? nextColumnWidth : width; let style = `width: ${colWidth}px !important; max-width: ${colWidth}px !important`; innerHTML += ` - ${selector} > .p-datatable-thead > tr > th:nth-child(${index + 1}), - ${selector} > .p-datatable-tbody > tr > td:nth-child(${index + 1}), - ${selector} > .p-datatable-tfoot > tr > td:nth-child(${index + 1}) { + ${selector} > [data-pc-section="thead"] > tr > th:nth-child(${index + 1}), + ${selector} > [data-pc-section="tbody"] > tr > td:nth-child(${index + 1}), + ${selector} > [data-pc-section="tfoot"] > tr > td:nth-child(${index + 1}) { ${style} } `; @@ -744,7 +744,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { let dragColIndex = columns.findIndex((child) => isSameColumn(child, draggedColumn.current)); let dropColIndex = columns.findIndex((child) => isSameColumn(child, column)); let widths = []; - let headers = DomHandler.find(tableRef.current, '.p-datatable-thead > tr > th'); + let headers = DomHandler.find(tableRef.current, '[data-pc-section="thead"] > tr > th'); headers.forEach((header) => widths.push(DomHandler.getOuterWidth(header))); const movedItem = widths.find((items, index) => index === dragColIndex); @@ -1252,7 +1252,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { const resetScroll = () => { if (wrapperRef.current) { - const scrollableContainer = !isVirtualScrollerDisabled() ? DomHandler.findSingle(wrapperRef.current, '.p-virtualscroller') : wrapperRef.current; + const scrollableContainer = !isVirtualScrollerDisabled() ? DomHandler.findSingle(wrapperRef.current, '[data-pc-name="virtualscroller"]') : wrapperRef.current; scrollableContainer.scrollTo(0, 0); } @@ -1342,7 +1342,7 @@ export const DataTable = React.forwardRef((inProps, ref) => { }; const closeEditingRows = () => { - DomHandler.find(document.body, '.p-row-editor-cancel').forEach((button, index) => { + DomHandler.find(document.body, '[data-pc-section="roweditorcancelbuttonprops"]').forEach((button, index) => { setTimeout(() => { button.click(); }, index * 5); @@ -1966,7 +1966,8 @@ export const DataTable = React.forwardRef((inProps, ref) => { id: props.id, className: classNames(props.className, ptCallbacks.cx('root', { selectable })), style: props.style, - 'data-scrollselectors': '.p-datatable-wrapper' + 'data-scrollselectors': '.p-datatable-wrapper', + 'data-showgridlines': props.showGridlines }, DataTableBase.getOtherProps(props), ptCallbacks.ptm('root') diff --git a/components/lib/datatable/TableBody.js b/components/lib/datatable/TableBody.js index 079013ea10..0a43916d7a 100644 --- a/components/lib/datatable/TableBody.js +++ b/components/lib/datatable/TableBody.js @@ -442,11 +442,11 @@ export const TableBody = React.memo( if (!allowCellSelection() && props.selectionAutoFocus) { if (isCheckboxSelectionModeInColumn) { - const checkbox = DomHandler.findSingle(target, 'td.p-selection-column .p-checkbox-box'); + const checkbox = DomHandler.findSingle(target, 'td[data-p-selection-column="true"] [data-pc-section="checkbox"]'); checkbox && checkbox.focus(); } else if (isRadioSelectionModeInColumn) { - const radio = DomHandler.findSingle(target, 'td.p-selection-column input[type="radio"]'); + const radio = DomHandler.findSingle(target, 'td[data-p-selection-column="true"] input[type="radio"]'); radio && radio.focus(); } diff --git a/components/lib/dialog/Dialog.js b/components/lib/dialog/Dialog.js index 593748f237..cfb02ed60f 100644 --- a/components/lib/dialog/Dialog.js +++ b/components/lib/dialog/Dialog.js @@ -396,8 +396,8 @@ export const Dialog = React.forwardRef((inProps, ref) => { for (let breakpoint in props.breakpoints) { innerHTML += ` @media screen and (max-width: ${breakpoint}) { - .p-dialog[${attributeSelector.current}] { - width: ${props.breakpoints[breakpoint]}; + [data-pc-name="dialog"][${attributeSelector.current}] { + width: ${props.breakpoints[breakpoint]} !important; } } `; @@ -406,15 +406,26 @@ export const Dialog = React.forwardRef((inProps, ref) => { styleElement.current.innerHTML = innerHTML; }; + const destroyStyle = () => { + styleElement.current = DomHandler.removeInlineStyle(styleElement.current); + }; + useMountEffect(() => { if (props.visible) { setMaskVisibleState(true); } + }); + React.useEffect(() => { if (props.breakpoints) { createStyle(); } - }); + + return () => { + destroyStyle(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.breakpoints]); useUpdateEffect(() => { if (props.visible && !maskVisibleState) { diff --git a/components/lib/dropdown/Dropdown.js b/components/lib/dropdown/Dropdown.js index 5865b438ac..9be0ed8be8 100644 --- a/components/lib/dropdown/Dropdown.js +++ b/components/lib/dropdown/Dropdown.js @@ -3,13 +3,13 @@ import PrimeReact, { FilterService, PrimeReactContext } from '../api/Api'; import { useHandleStyle } from '../componentbase/ComponentBase'; import { useMountEffect, useOverlayListener, useUnmountEffect, useUpdateEffect } from '../hooks/Hooks'; import { ChevronDownIcon } from '../icons/chevrondown'; +import { SpinnerIcon } from '../icons/spinner'; import { TimesIcon } from '../icons/times'; import { OverlayService } from '../overlayservice/OverlayService'; import { Tooltip } from '../tooltip/Tooltip'; import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils, classNames, mergeProps } from '../utils/Utils'; import { DropdownBase } from './DropdownBase'; import { DropdownPanel } from './DropdownPanel'; -import { SpinnerIcon } from '../icons/spinner'; export const Dropdown = React.memo( React.forwardRef((inProps, ref) => { @@ -741,7 +741,7 @@ export const Dropdown = React.memo( onBlur: onInputBlur, onKeyDown: onInputKeyDown, disabled: props.disabled, - tabIndex: props.tabIndex, + tabIndex: props.tabIndex || 0, ...ariaProps }, ptm('input') @@ -771,6 +771,7 @@ export const Dropdown = React.memo( onInput: onEditableInputChange, onFocus: onEditableInputFocus, onBlur: onInputBlur, + tabIndex: props.tabIndex || 0, 'aria-haspopup': 'listbox', ...ariaProps }, @@ -783,7 +784,8 @@ export const Dropdown = React.memo( const inputProps = mergeProps( { ref: inputRef, - className: cx('input', { label }) + className: cx('input', { label }), + tabIndex: '-1' }, ptm('input') ); diff --git a/components/lib/hooks/useDebounce.js b/components/lib/hooks/useDebounce.js index 29ba443edc..1233441ec9 100644 --- a/components/lib/hooks/useDebounce.js +++ b/components/lib/hooks/useDebounce.js @@ -1,16 +1,31 @@ import * as React from 'react'; -import { useTimeout } from './useTimeout'; +import { useMountEffect, useUnmountEffect } from './Hooks'; export const useDebounce = (initialValue, delay) => { const [inputValue, setInputValue] = React.useState(initialValue); const [debouncedValue, setDebouncedValue] = React.useState(initialValue); - const timeout = useTimeout( - () => { + const mountedRef = React.useRef(false); + const timeoutRef = React.useRef(null); + const cancelTimer = () => window.clearTimeout(timeoutRef.current); + + useMountEffect(() => { + mountedRef.current = true; + }); + + useUnmountEffect(() => { + cancelTimer(); + }); + + React.useEffect(() => { + if (!mountedRef.current) { + return; + } + + cancelTimer(); + timeoutRef.current = window.setTimeout(() => { setDebouncedValue(inputValue); - }, - delay, - inputValue !== debouncedValue - ); + }, delay); + }, [inputValue, delay]); return [inputValue, debouncedValue, setInputValue]; }; diff --git a/components/lib/image/Image.js b/components/lib/image/Image.js index 811a44f314..61cc1365c8 100644 --- a/components/lib/image/Image.js +++ b/components/lib/image/Image.js @@ -30,6 +30,8 @@ export const Image = React.memo( const previewRef = React.useRef(null); const previewClick = React.useRef(false); const previewButton = React.useRef(null); + const zoomOutDisabled = scaleState <= 0.5; + const zoomInDisabled = scaleState >= 1.5; useOnEscapeKey(maskRef, props.closeOnEscape, () => { hide(); @@ -122,12 +124,20 @@ export const Image = React.memo( }; const zoomIn = () => { - setScaleState((prevScale) => prevScale + 0.1); + setScaleState((prevScale) => { + if (zoomInDisabled) return prevScale; + + return prevScale + 0.1; + }); previewClick.current = true; }; const zoomOut = () => { - setScaleState((prevScale) => prevScale - 0.1); + setScaleState((prevScale) => { + if (zoomOutDisabled) return prevScale; + + return prevScale - 0.1; + }); previewClick.current = true; }; @@ -179,8 +189,6 @@ export const Image = React.memo( const createElement = () => { const { downloadable, alt, crossOrigin, referrerPolicy, useMap, loading } = props; - const zoomOutDisabled = scaleState <= 0.5; - const zoomInDisabled = scaleState >= 1.5; const downloadIconProps = mergeProps(ptm('downloadIcon')); const rotateRightIconProps = mergeProps(ptm('rotateRightIcon')); const rotateLeftIconProps = mergeProps(ptm('rotateLeftIcon')); diff --git a/components/lib/inplace/Inplace.js b/components/lib/inplace/Inplace.js index a14b1b517d..27739e3b2e 100644 --- a/components/lib/inplace/Inplace.js +++ b/components/lib/inplace/Inplace.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { localeOption, PrimeReactContext } from '../api/Api'; import { Button } from '../button/Button'; import { useHandleStyle } from '../componentbase/ComponentBase'; +import { useUpdateEffect } from '../hooks/Hooks'; import { TimesIcon } from '../icons/times'; import { classNames, IconUtils, mergeProps, ObjectUtils } from '../utils/Utils'; import { InplaceBase } from './InplaceBase'; @@ -44,6 +45,10 @@ export const Inplace = React.forwardRef((inProps, ref) => { }; const close = (event) => { + if (props.disabled) { + return; + } + props.onClose && props.onClose(event); if (props.onToggle) { @@ -138,6 +143,10 @@ export const Inplace = React.forwardRef((inProps, ref) => { getElement: () => elementRef.current })); + useUpdateEffect(() => { + props.active ? open(null) : close(null); + }, [props.active]); + const children = createChildren(); const rootProps = mergeProps( diff --git a/components/lib/inputnumber/InputNumber.js b/components/lib/inputnumber/InputNumber.js index e73cc328a7..0c60ae2aa4 100644 --- a/components/lib/inputnumber/InputNumber.js +++ b/components/lib/inputnumber/InputNumber.js @@ -58,7 +58,8 @@ export const InputNumber = React.memo( currencyDisplay: props.currencyDisplay, useGrouping: props.useGrouping, minimumFractionDigits: props.minFractionDigits, - maximumFractionDigits: props.maxFractionDigits + maximumFractionDigits: props.maxFractionDigits, + roundingMode: props.roundingMode }; }; @@ -108,7 +109,8 @@ export const InputNumber = React.memo( currency: props.currency, currencyDisplay: props.currencyDisplay, minimumFractionDigits: 0, - maximumFractionDigits: 0 + maximumFractionDigits: 0, + roundingMode: props.roundingMode }); return new RegExp(`[${formatter.format(1).replace(/\s/g, '').replace(_numeral.current, '').replace(_group.current, '')}]`, 'g'); @@ -138,7 +140,8 @@ export const InputNumber = React.memo( currency: props.currency, currencyDisplay: props.currencyDisplay, minimumFractionDigits: 0, - maximumFractionDigits: 0 + maximumFractionDigits: 0, + roundingMode: props.roundingMode }); suffixChar.current = formatter.format(1).split('1')[1]; diff --git a/components/lib/inputnumber/InputNumberBase.js b/components/lib/inputnumber/InputNumberBase.js index c4e17d0dbc..b2d50d3b28 100644 --- a/components/lib/inputnumber/InputNumberBase.js +++ b/components/lib/inputnumber/InputNumberBase.js @@ -171,6 +171,7 @@ export const InputNumberBase = ComponentBase.extend({ prefix: null, readOnly: false, required: false, + roundingMode: undefined, showButtons: false, size: null, step: 1, diff --git a/components/lib/inputnumber/inputnumber.d.ts b/components/lib/inputnumber/inputnumber.d.ts index 4497b2c80f..ef19e8b7a8 100644 --- a/components/lib/inputnumber/inputnumber.d.ts +++ b/components/lib/inputnumber/inputnumber.d.ts @@ -16,6 +16,8 @@ import { TooltipOptions } from '../tooltip/tooltipoptions'; import { FormEvent } from '../ts-helpers'; import { IconType, PassThroughType } from '../utils/utils'; +export declare type RoundingMode = 'ceil' | 'floor' | 'expand' | 'trunc' | 'halfCeil' | 'halfFloor' | 'halfExpand' | 'halfTrunc' | 'halfEven'; + export declare type InputNumberPassThroughType = PassThroughType; /** @@ -190,6 +192,11 @@ export interface InputNumberProps extends Omit { @@ -95,6 +95,7 @@ export const InputSwitch = React.memo( role: 'checkbox', 'aria-checked': checked }, + otherProps, ptm('root') ); const hiddenInputWrapperProps = mergeProps( diff --git a/components/lib/listbox/ListBoxItem.js b/components/lib/listbox/ListBoxItem.js index da2f667340..e5a5b34eff 100644 --- a/components/lib/listbox/ListBoxItem.js +++ b/components/lib/listbox/ListBoxItem.js @@ -3,6 +3,7 @@ import { Ripple } from '../ripple/Ripple'; import { DomHandler, mergeProps, ObjectUtils } from '../utils/Utils'; export const ListBoxItem = React.memo((props) => { + const [focusedState, setFocusedState] = React.useState(false); const { ptCallbacks: { ptm, cx } } = props; @@ -12,11 +13,20 @@ export const ListBoxItem = React.memo((props) => { hostName: props.hostName, context: { selected: props.selected, - disabled: props.disabled + disabled: props.disabled, + focused: focusedState } }); }; + const onFocus = (event) => { + setFocusedState(true); + }; + + const onBlur = (event) => { + setFocusedState(false); + }; + const onClick = (event) => { if (props.onClick) { props.onClick({ @@ -91,6 +101,8 @@ export const ListBoxItem = React.memo((props) => { onClick: onClick, onTouchEnd: onTouchEnd, onKeyDown: onKeyDown, + onFocus: onFocus, + onBlur: onBlur, tabIndex: '-1', 'aria-label': props.label, key: props.label, diff --git a/components/lib/listbox/listbox.d.ts b/components/lib/listbox/listbox.d.ts index ed1d16e4dd..bdc0e0010c 100755 --- a/components/lib/listbox/listbox.d.ts +++ b/components/lib/listbox/listbox.d.ts @@ -8,14 +8,14 @@ * */ import * as React from 'react'; +import { ComponentHooks } from '../componentbase/componentbase'; +import { InputTextPassThroughOptions } from '../inputtext/inputtext'; +import { PassThroughOptions } from '../passthrough'; import { SelectItemOptionsType } from '../selectitem/selectitem'; import { TooltipPassThroughOptions } from '../tooltip/tooltip'; import { TooltipOptions } from '../tooltip/tooltipoptions'; import { IconType, PassThroughType } from '../utils/utils'; import { VirtualScroller, VirtualScrollerPassThroughOptions, VirtualScrollerProps } from '../virtualscroller'; -import { InputTextPassThroughOptions } from '../inputtext/inputtext'; -import { PassThroughOptions } from '../passthrough'; -import { ComponentHooks } from '../componentbase/componentbase'; export declare type ListBoxPassThroughType = PassThroughType; @@ -109,6 +109,11 @@ export interface ListBoxContext { * @defaultValue false */ selected: boolean; + /** + * Current focused state of the item as a boolean. + * @defaultValue false + */ + focused: boolean; /** * Current disabled state of the item as a boolean. * @defaultValue false diff --git a/components/lib/messages/Messages.js b/components/lib/messages/Messages.js index 41e4874357..7cc046c078 100644 --- a/components/lib/messages/Messages.js +++ b/components/lib/messages/Messages.js @@ -103,7 +103,7 @@ export const Messages = React.memo( const transitionProps = mergeProps( { - classNames: ptCallbacks.cx('transition'), + classNames: ptCallbacks.cx('uimessage.transition'), unmountOnExit: true, timeout: { enter: 300, exit: 300 }, options: props.transitionOptions diff --git a/components/lib/multiselect/MultiSelectBase.js b/components/lib/multiselect/MultiSelectBase.js index 2e5397fded..2746e9eff3 100644 --- a/components/lib/multiselect/MultiSelectBase.js +++ b/components/lib/multiselect/MultiSelectBase.js @@ -260,6 +260,7 @@ export const MultiSelectBase = ComponentBase.extend({ resetFilterOnHide: false, scrollHeight: '200px', selectAll: false, + selectAllLabel: null, selectedItemTemplate: null, selectedItemsLabel: '{0} items selected', selectionLimit: null, diff --git a/components/lib/multiselect/MultiSelectHeader.js b/components/lib/multiselect/MultiSelectHeader.js index 3921b27679..9145c7738f 100644 --- a/components/lib/multiselect/MultiSelectHeader.js +++ b/components/lib/multiselect/MultiSelectHeader.js @@ -116,12 +116,19 @@ export const MultiSelectHeader = React.memo((props) => { getPTOptions('headerCheckboxIcon') ); + const headerCheckboxContainerProps = mergeProps( + { + className: cx('headerCheckboxContainer') + }, + getPTOptions('headerCheckboxContainer') + ); + const checkedIcon = props.itemCheckboxIcon || ; const itemCheckboxIcon = IconUtils.getJSXIcon(checkedIcon, { ...headerCheckboxIconProps }, { selected: props.selected }); const checkboxElement = props.showSelectAll && ( -
- +
+ {!props.filter && }
); diff --git a/components/lib/multiselect/MultiSelectItem.js b/components/lib/multiselect/MultiSelectItem.js index 9339c8694b..1ee1abe6f2 100644 --- a/components/lib/multiselect/MultiSelectItem.js +++ b/components/lib/multiselect/MultiSelectItem.js @@ -4,17 +4,28 @@ import { Ripple } from '../ripple/Ripple'; import { IconUtils, ObjectUtils, classNames, mergeProps } from '../utils/Utils'; export const MultiSelectItem = React.memo((props) => { + const [focusedState, setFocusedState] = React.useState(false); const { ptm, cx } = props; const getPTOptions = (key) => { return ptm(key, { hostName: props.hostName, context: { - selected: props.selected + selected: props.selected, + disabled: props.disabled, + focused: focusedState } }); }; + const onFocus = (event) => { + setFocusedState(true); + }; + + const onBlur = (event) => { + setFocusedState(false); + }; + const onClick = (event) => { if (props.onClick) { props.onClick({ @@ -69,8 +80,10 @@ export const MultiSelectItem = React.memo((props) => { className: classNames(props.className, props.option.className, cx('item', { itemProps: props })), style: props.style, onClick: onClick, - tabIndex: tabIndex, onKeyDown: onKeyDown, + onFocus: onFocus, + onBlur: onBlur, + tabIndex: tabIndex, role: 'option', 'aria-selected': props.selected, 'data-p-highlight': props.selected, diff --git a/components/lib/multiselect/multiselect.d.ts b/components/lib/multiselect/multiselect.d.ts index 273b31037e..6280c61553 100644 --- a/components/lib/multiselect/multiselect.d.ts +++ b/components/lib/multiselect/multiselect.d.ts @@ -92,6 +92,10 @@ export interface MultiSelectPassThroughOptions { * Uses to pass attributes to the header checkbox's DOM element. */ headerCheckbox?: CheckboxPassThroughOptions; + /** + * Uses to pass attributes to the header checkbox's container DOM element. + */ + headerCheckboxContainer?: MultiSelectPassThroughType>; /** * Uses to pass attributes to the header checkbox icon's DOM element. */ diff --git a/components/lib/passthrough/tailwind/index.js b/components/lib/passthrough/tailwind/index.js index f8c4179374..8676c40446 100644 --- a/components/lib/passthrough/tailwind/index.js +++ b/components/lib/passthrough/tailwind/index.js @@ -396,6 +396,7 @@ const Tailwind = { 'dark:border dark:border-blue-900/40 dark:bg-gray-900 dark:text-white/80' ) }, + closeButton: 'flex items-center justify-center overflow-hidden absolute top-0 right-0 w-6 h-6', content: 'p-5 items-center flex', transition: TRANSITIONS.overlay }, @@ -492,10 +493,10 @@ const Tailwind = { }, arrow: ({ context }) => ({ className: classNames('absolute w-0 h-0 border-transparent border-solid', { - '-m-t-1 border-y-[0.25rem] border-r-[0.25rem] border-l-0 border-r-gray-600': context.right, - '-m-t-1 border-y-[0.25rem] border-l-[0.25rem] border-r-0 border-l-gray-600': context.left, - '-m-l-1 border-x-[0.25rem] border-t-[0.25rem] border-b-0 border-t-gray-600': context.top, - '-m-l-1 border-x-[0.25rem] border-b-[0.25rem] border-t-0 border-b-gray-600': context.bottom + '-mt-1 border-y-[0.25rem] border-r-[0.25rem] border-l-0 border-r-gray-600': context.right, + '-mt-1 border-y-[0.25rem] border-l-[0.25rem] border-r-0 border-l-gray-600': context.left, + '-ml-1 border-x-[0.25rem] border-t-[0.25rem] border-b-0 border-t-gray-600': context.top, + '-ml-1 border-x-[0.25rem] border-b-[0.25rem] border-t-0 border-b-gray-600': context.bottom }) }), text: { @@ -697,6 +698,14 @@ const Tailwind = { 'mt-2 order-2': props.iconPos == 'bottom' && props.label != null }) }), + loadingIcon: ({ props }) => ({ + className: classNames('mx-0', { + 'mr-2': props.loading && props.iconPos == 'left' && props.label != null, + 'ml-2 order-1': props.loading && props.iconPos == 'right' && props.label != null, + 'mb-2': props.loading && props.iconPos == 'top' && props.label != null, + 'mt-2 order-2': props.loading && props.iconPos == 'bottom' && props.label != null + }) + }), badge: ({ props }) => ({ className: classNames({ 'ml-2 w-4 h-4 leading-none flex items-center justify-center': props.badge }) }) @@ -845,13 +854,12 @@ const Tailwind = { sublist: { className: classNames('block absolute left-full top-0', 'min-w-full z-10', 'py-3 bg-white dark:bg-gray-900 border-0 shadow-md') }, - item: { - className: classNames( - 'cursor-pointer font-normal whitespace-nowrap', - 'm-0 border-0 bg-transparent transition-shadow rounded-none', - 'text-gray-700 dark:text-white/80 hover:text-gray-700 dark:hover:text-white/80 hover:bg-gray-200 dark:hover:bg-gray-800/80' - ) - }, + item: ({ state }) => ({ + className: classNames('cursor-pointer font-normal whitespace-nowrap', 'm-0 border-0 bg-transparent transition-shadow rounded-none', { + 'text-gray-700 hover:text-gray-700 hover:bg-gray-200 dark:text-white/80 dark:hover:text-white/80 dark:hover:bg-gray-800/80': !state.selected, + 'bg-blue-50 text-blue-700 dark:bg-blue-300 dark:text-white/80': state.selected + }) + }), content: { className: classNames('flex items-center overflow-hidden relative', 'py-3 px-5') }, @@ -974,14 +982,14 @@ const Tailwind = { transition: TRANSITIONS.overlay }, togglebutton: { - root: ({ props, context }) => ({ + root: ({ props, state }) => ({ className: classNames( 'inline-flex cursor-pointer select-none items-center align-bottom text-center overflow-hidden relative', 'px-4 py-3 rounded-md text-base w-36', 'border transition duration-200 ease-in-out', - // { - // 'outline-none outline-offset-0 shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]': context.focused - // }, + { + 'outline-none outline-offset-0 shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]': state.focused + }, { 'bg-white dark:bg-gray-900 border-gray-300 dark:border-blue-900/40 text-gray-700 dark:text-white/80 hover:bg-gray-100 dark:hover:bg-gray-800/80 hover:border-gray-300 dark:hover:bg-gray-800/70 hover:text-gray-700 dark:hover:text-white/80': !props.checked, @@ -1254,14 +1262,14 @@ const Tailwind = { 'bg-blue-50 text-blue-700 dark:bg-blue-300 dark:text-white/80': !context.focused && context.selected }) }), - itemgroup: { + itemGroup: { className: classNames('m-0 p-3 text-gray-800 bg-white font-bold', 'dark:bg-gray-900 dark:text-white/80', 'cursor-auto') }, header: { className: classNames('p-3 border-b border-gray-300 text-gray-700 bg-gray-100 mt-0 rounded-tl-lg rounded-tr-lg', 'dark:bg-gray-800 dark:text-white/80 dark:border-blue-900/40') }, - filtercontainer: 'relative', - filterinput: { + filterContainer: 'relative', + filterInput: { root: { className: classNames( 'pr-7 -mr-7', @@ -1272,7 +1280,7 @@ const Tailwind = { ) } }, - filtericon: '-mt-2 absolute top-1/2' + filterIcon: '-mt-2 absolute top-1/2' }, mention: { root: 'relative', @@ -1310,18 +1318,20 @@ const Tailwind = { headerCheckboxContainer: { className: classNames('inline-flex cursor-pointer select-none align-bottom relative', 'mr-2', 'w-6 h-6') }, - headerCheckbox: ({ context }) => ({ - className: classNames( - 'flex items-center justify-center', - 'border-2 w-6 h-6 text-gray-600 dark:text-white/70 rounded-lg transition-colors duration-200', - 'hover:border-blue-500 focus:outline-none focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]', - { - 'border-gray-300 dark:border-blue-900/40 bg-white dark:bg-gray-900': !context?.selected, - 'border-blue-500 bg-blue-500': context?.selected - } - ) - }), - headercheckboxicon: 'w-4 h-4 transition-all duration-200 text-white text-base', + headerCheckbox: { + root: ({ props }) => ({ + className: classNames( + 'flex items-center justify-center', + 'border-2 w-6 h-6 text-gray-600 dark:text-white/70 rounded-lg transition-colors duration-200', + 'hover:border-blue-500 focus:outline-none focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]', + { + 'border-gray-300 dark:border-blue-900/40 bg-white dark:bg-gray-900': !props?.checked, + 'border-blue-500 bg-blue-500': props?.checked + } + ) + }) + }, + headerCheckboxIcon: 'w-4 h-4 transition-all duration-200 text-white text-base', closeButton: { className: classNames( 'flex items-center justify-center overflow-hidden relative', @@ -1357,22 +1367,24 @@ const Tailwind = { } ) }), - checkboxicon: 'w-4 h-4 transition-all duration-200 text-white text-base', - itemgroup: { + checkboxIcon: 'w-4 h-4 transition-all duration-200 text-white text-base', + itemGroup: { className: classNames('m-0 p-3 text-gray-800 bg-white font-bold', 'dark:bg-gray-900 dark:text-white/80', 'cursor-auto') }, - filtercontainer: 'relative', - filterinput: { - className: classNames( - 'pr-7 -mr-7', - 'w-full', - 'font-sans text-base text-gray-700 bg-white py-3 px-3 border border-gray-300 transition duration-200 rounded-lg appearance-none', - 'dark:bg-gray-900 dark:border-blue-900/40 dark:hover:border-blue-300 dark:text-white/80', - 'hover:border-blue-500 focus:outline-none focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]' - ) + filterContainer: 'relative', + filterInput: { + root: { + className: classNames( + 'pr-7 -mr-7', + 'w-full', + 'font-sans text-base text-gray-700 bg-white py-3 px-3 border border-gray-300 transition duration-200 rounded-lg appearance-none', + 'dark:bg-gray-900 dark:border-blue-900/40 dark:hover:border-blue-300 dark:text-white/80', + 'hover:border-blue-500 focus:outline-none focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]' + ) + } }, - filtericon: '-mt-2 absolute top-1/2', - clearicon: 'text-gray-500 right-12 -mt-2 absolute top-1/2', + filterIcon: '-mt-2 absolute top-1/2', + clearIcon: 'text-gray-500 right-12 -mt-2 absolute top-1/2', transition: TRANSITIONS.overlay }, multistatecheckbox: { @@ -1451,7 +1463,7 @@ const Tailwind = { 'focus:outline-offset-0 focus:shadow-[0_0_0_0.2rem_rgba(191,219,254,1)] hover:border-blue-500 focus:outline-none dark:focus:shadow-[0_0_0_0.2rem_rgba(147,197,253,0.5)]' ) }, - inputtoken: { + inputToken: { className: classNames('py-0.375rem px-0', 'flex-1 inline-flex') }, input: ({ props }) => ({ @@ -1486,7 +1498,7 @@ const Tailwind = { 'bg-blue-50 text-blue-700 dark:bg-blue-300 dark:text-white/80': !context.focused && context.selected }) }), - itemgroup: { + itemGroup: { className: classNames('m-0 p-3 text-gray-800 bg-white font-bold', 'dark:bg-gray-900 dark:text-white/80', 'cursor-auto') }, transition: TRANSITIONS.overlay @@ -1506,7 +1518,7 @@ const Tailwind = { ) }, - inputtoken: { + inputToken: { className: classNames('py-1.5 px-0', 'flex flex-1 inline-flex') }, input: { @@ -3152,7 +3164,7 @@ const Tailwind = { context.sorted ? 'bg-blue-50 text-blue-700' : 'bg-slate-50 text-slate-700', // Sort context.sorted ? 'dark:text-white/80 dark:bg-blue-300' : 'dark:text-white/80 dark:bg-gray-900', // Dark Mode { - 'sticky z-[1]': props.frozen || props.frozen === '', // Frozen Columns + 'sticky z-[1]': props && (props.frozen || props.frozen === ''), // Frozen Columns 'border-x border-y': context?.showGridlines, 'overflow-hidden space-nowrap border-y relative bg-clip-padding': context.resizable // Resizable } @@ -3165,7 +3177,7 @@ const Tailwind = { context?.size === 'small' ? 'p-2' : context?.size === 'large' ? 'p-5' : 'p-4', // Size 'dark:text-white/80 dark:border-blue-900/40', // Dark Mode { - 'sticky bg-inherit': props?.frozen || props?.frozen === '', // Frozen Columns + 'sticky bg-inherit': props && (props.frozen || props.frozen === ''), // Frozen Columns 'border-x border-y': context.showGridlines } ) diff --git a/components/lib/radiobutton/RadioButton.js b/components/lib/radiobutton/RadioButton.js index 8b065e2ed2..db8b1c0da7 100644 --- a/components/lib/radiobutton/RadioButton.js +++ b/components/lib/radiobutton/RadioButton.js @@ -1,10 +1,10 @@ import * as React from 'react'; import { PrimeReactContext } from '../api/Api'; +import { useHandleStyle } from '../componentbase/ComponentBase'; import { useMountEffect } from '../hooks/Hooks'; import { Tooltip } from '../tooltip/Tooltip'; import { DomHandler, ObjectUtils, classNames, mergeProps } from '../utils/Utils'; import { RadioButtonBase } from './RadioButtonBase'; -import { useHandleStyle } from '../componentbase/ComponentBase'; export const RadioButton = React.memo( React.forwardRef((inProps, ref) => { @@ -38,36 +38,36 @@ export const RadioButton = React.memo( const radioClicked = event.target instanceof HTMLDivElement; const inputClicked = event.target === inputRef.current; const isInputToggled = inputClicked && event.target.checked !== checked; - const isRadioToggled = radioClicked && (DomHandler.isAttributeEquals(elementRef.current, 'data-p-checked', true) === checked ? !checked : false); - - if (isInputToggled || isRadioToggled) { - const value = !checked; - const eventData = { - originalEvent: event, + const isRadioToggled = radioClicked && (DomHandler.hasClass(elementRef.current, 'p-radiobutton-checked') === checked ? !checked : false); + const value = !checked; + + const eventData = { + originalEvent: event, + value: props.value, + checked: value, + stopPropagation: () => { + event.stopPropagation(); + }, + preventDefault: () => { + event.preventDefault(); + }, + target: { + type: 'radio', + name: props.name, + id: props.id, value: props.value, - checked: value, - stopPropagation: () => { - event.stopPropagation(); - }, - preventDefault: () => { - event.preventDefault(); - }, - target: { - type: 'radio', - name: props.name, - id: props.id, - value: props.value, - checked: value - } - }; - - props.onClick && props.onClick(eventData); - - // do not continue if the user defined click wants to prevent - if (event.defaultPrevented) { - return; + checked: value } + }; + props.onClick && props.onClick(eventData); + + // do not continue if the user defined click wants to prevent + if (event.defaultPrevented) { + return; + } + + if (isInputToggled || isRadioToggled) { props.onChange && props.onChange(eventData); if (isRadioToggled) { diff --git a/components/lib/sidebar/Sidebar.js b/components/lib/sidebar/Sidebar.js index f0832785b9..a547a6d898 100644 --- a/components/lib/sidebar/Sidebar.js +++ b/components/lib/sidebar/Sidebar.js @@ -156,6 +156,7 @@ export const Sidebar = React.forwardRef((inProps, ref) => { }); const createCloseIcon = () => { + const ariaLabel = props.ariaCloseLabel || localeOption('close'); const closeButtonProps = mergeProps( { type: 'button', @@ -176,7 +177,6 @@ export const Sidebar = React.forwardRef((inProps, ref) => { const icon = props.closeIcon || ; const closeIcon = IconUtils.getJSXIcon(icon, { ...closeIconProps }, { props }); - const ariaLabel = props.ariaCloseLabel || localeOption('close'); if (props.showCloseIcon) { return ( diff --git a/components/lib/togglebutton/ToggleButton.js b/components/lib/togglebutton/ToggleButton.js index 9b48bc3b04..b1e18a2645 100644 --- a/components/lib/togglebutton/ToggleButton.js +++ b/components/lib/togglebutton/ToggleButton.js @@ -1,19 +1,23 @@ import * as React from 'react'; import { PrimeReactContext } from '../api/Api'; +import { useHandleStyle } from '../componentbase/ComponentBase'; import { useMountEffect } from '../hooks/Hooks'; import { Ripple } from '../ripple/Ripple'; import { Tooltip } from '../tooltip/Tooltip'; import { DomHandler, IconUtils, ObjectUtils, mergeProps } from '../utils/Utils'; import { ToggleButtonBase } from './ToggleButtonBase'; -import { useHandleStyle } from '../componentbase/ComponentBase'; export const ToggleButton = React.memo( React.forwardRef((inProps, ref) => { + const [focusedState, setFocusedState] = React.useState(false); const context = React.useContext(PrimeReactContext); const props = ToggleButtonBase.getProps(inProps, context); const elementRef = React.useRef(null); const { ptm, cx, isUnstyled } = ToggleButtonBase.setMetaData({ - props + props, + state: { + focused: focusedState + } }); useHandleStyle(ToggleButtonBase.css.styles, isUnstyled, { name: 'togglebutton' }); @@ -43,6 +47,16 @@ export const ToggleButton = React.memo( } }; + const onFocus = (event) => { + setFocusedState(true); + props.onFocus && props.onFocus(event); + }; + + const onBlur = (event) => { + setFocusedState(false); + props.onBlur && props.onBlur(event); + }; + const onKeyDown = (event) => { if (event.keyCode === 32) { toggle(event); @@ -95,8 +109,8 @@ export const ToggleButton = React.memo( className: cx('root', { hasIcon, hasLabel }), style: props.style, onClick: toggle, - onFocus: props.onFocus, - onBlur: props.onBlur, + onFocus: onFocus, + onBlur: onBlur, onKeyDown: onKeyDown, tabIndex: tabIndex, role: 'button', diff --git a/components/lib/treeselect/TreeSelect.js b/components/lib/treeselect/TreeSelect.js index 23900bc2b5..b2f4b594d4 100644 --- a/components/lib/treeselect/TreeSelect.js +++ b/components/lib/treeselect/TreeSelect.js @@ -7,6 +7,7 @@ import { SearchIcon } from '../icons/search'; import { TimesIcon } from '../icons/times'; import { OverlayService } from '../overlayservice/OverlayService'; import { Ripple } from '../ripple/Ripple'; +import { Tooltip } from '../tooltip/Tooltip'; import { Tree } from '../tree/Tree'; import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils, mergeProps } from '../utils/Utils'; import { TreeSelectBase } from './TreeSelectBase'; @@ -33,6 +34,7 @@ export const TreeSelect = React.memo( const hasNoOptions = ObjectUtils.isEmpty(props.options); const isSingleSelectionMode = props.selectionMode === 'single'; const isCheckboxSelectionMode = props.selectionMode === 'checkbox'; + const hasTooltip = ObjectUtils.isNotEmpty(props.tooltip); const metaData = { props, @@ -744,6 +746,7 @@ export const TreeSelect = React.memo( > {content} + {hasTooltip && }
); }) diff --git a/components/lib/treeselect/treeselect.d.ts b/components/lib/treeselect/treeselect.d.ts index 10a30ec2c4..fbb888d9ee 100644 --- a/components/lib/treeselect/treeselect.d.ts +++ b/components/lib/treeselect/treeselect.d.ts @@ -12,6 +12,8 @@ import { CSSTransitionProps as ReactCSSTransitionProps } from 'react-transition- import { ComponentHooks } from '../componentbase/componentbase'; import { CSSTransitionProps } from '../csstransition'; import { PassThroughOptions } from '../passthrough'; +import { TooltipPassThroughOptions } from '../tooltip/tooltip'; +import { TooltipOptions } from '../tooltip/tooltipoptions'; import { TreeNodeTemplateOptions, TreePassThroughOptions, TreeTogglerTemplateOptions } from '../tree/tree'; import { TreeNode } from '../treenode'; import { FormEvent } from '../ts-helpers'; @@ -123,6 +125,11 @@ export interface TreeSelectPassThroughOptions { * Used to control React Transition API. */ transition?: TreeSelectPassThroughTransitionType; + /** + * Uses to pass attributes to the Tooltip component. + * @see {@link TooltipPassThroughOptions} + */ + tooltip?: TooltipPassThroughOptions; } /** @@ -524,6 +531,14 @@ export interface TreeSelectProps extends Omit