Compare commits

..

No commits in common. "b2e35c965c07c874fb968c4ef13e2d74be1d5be7" and "aef6ec8749e327c8c59176395efdd271c4e284ae" have entirely different histories.

3 changed files with 148 additions and 227 deletions

View File

@ -2358,189 +2358,3 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.ant-table-wrapper tfoot > tr > td { .ant-table-wrapper tfoot > tr > td {
padding: 12px 8px; padding: 12px 8px;
} }
/* FilterInput */
.filter-input-operand-icon {
font-size: 8px;
}
.filter-input-operand-icon--chevron {
font-size: 11px;
}
.filter-input-operand-icon--gt {
margin-left: 1px;
}
.filter-input-operand-icon--lt {
margin-right: 1px;
}
.filter-input-wildcard,
[data-wildcard] {
color: var(--color-primary);
}
.filter-input-operand-tag {
margin-inline-end: 0;
margin-right: 0;
height: 18px;
cursor: default;
padding: 0 4px;
margin-top: -2px;
line-height: 1.37;
user-select: none;
pointer-events: none;
flex-shrink: 0;
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
}
.filter-input-operand-chip {
display: inline-flex;
align-items: center;
vertical-align: middle;
margin: 0 2px;
user-select: none;
flex-shrink: 0;
}
.filter-input-property-filter {
display: flex;
flex-direction: column;
max-width: 280px;
margin: 0;
vertical-align: top;
overflow: hidden;
--property-filter-panel-max-height: 220px;
}
.filter-input-property-filter-quick {
flex-shrink: 0;
}
.filter-input-property-filter-divider {
margin: 4px 0;
flex-shrink: 0;
}
.filter-input-property-filter
> .filter-input-property-filter-scroll.scrollbox-inner {
height: auto !important;
max-height: var(--property-filter-panel-max-height);
max-width: 280px;
flex-shrink: 1;
min-height: 0;
}
.filter-input-property-filter
> .filter-input-property-filter-scroll.scrollbox-inner
.simplebar-wrapper {
max-height: var(--property-filter-panel-max-height);
}
.filter-input-property-filter .simplebar-content-wrapper {
max-width: none;
}
.filter-input-property-filter .simplebar-content {
display: inline-block;
}
.filter-input-property-filter-body {
padding: 12px 26px 12px 16px;
max-width: 280px;
box-sizing: border-box;
}
.filter-input-property-filter-content {
width: max-content;
max-width: 100%;
}
.filter-input-affix {
width: 100%;
display: inline-flex;
position: relative;
}
.filter-input-affix:not(.filter-input-affix--disabled) {
cursor: text;
}
.filter-input-affix--disabled {
cursor: not-allowed;
}
.filter-input-affix--elevated {
z-index: 3 !important;
}
.filter-input-editor::-webkit-scrollbar {
display: none;
height: 0;
}
.filter-input-editor {
display: block;
padding: 0;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
background: transparent;
flex: auto;
min-width: 0;
width: 100%;
font-size: inherit;
line-height: inherit;
color: inherit;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
word-break: keep-all;
overflow-wrap: normal;
caret-color: var(--ant-color-text, inherit);
cursor: text;
}
.filter-input-affix--disabled .filter-input-editor {
cursor: not-allowed;
}
.filter-input-placeholder {
position: absolute;
inset-inline-start: var(--ant-input-padding-inline, 11px);
inset-inline-end: var(--ant-input-padding-inline, 11px);
top: 0;
bottom: 0;
display: flex;
align-items: center;
color: var(--ant-color-text-placeholder, rgba(0, 0, 0, 0.25));
pointer-events: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 1;
}
.filter-input-affix--has-clear .filter-input-placeholder {
inset-inline-end: calc(var(--ant-input-padding-inline, 11px) + 22px);
}
.filter-input-popover .ant-popover-inner {
padding: 4px;
}
.filter-input-popover-anchor {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 0;
pointer-events: none;
}

View File

@ -6,7 +6,7 @@ import {
useRef, useRef,
useState useState
} from 'react' } from 'react'
import { ConfigProvider, Popover, Tag } from 'antd' import { ConfigProvider, Popover, Tag, theme } from 'antd'
import { CloseCircleFilled } from '@ant-design/icons' import { CloseCircleFilled } from '@ant-design/icons'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
@ -28,6 +28,8 @@ import NotEqualIcon from '../../Icons/NotEqualIcon'
import EqualIcon from '../../Icons/EqualIcon' import EqualIcon from '../../Icons/EqualIcon'
import { Divider } from 'antd' import { Divider } from 'antd'
const operandIconStyle = { fontSize: 8 }
const PROPERTY_FILTER_PANEL_MAX_HEIGHT = 220 const PROPERTY_FILTER_PANEL_MAX_HEIGHT = 220
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">". // Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
@ -37,24 +39,22 @@ const WILDCARDS = new Set(['*', '?'])
const OPERANDS = [ const OPERANDS = [
{ {
symbol: '..', symbol: '..',
label: ( label: <ChevronRightIcon style={{ ...operandIconStyle, fontSize: 11 }} />,
<ChevronRightIcon className='filter-input-operand-icon filter-input-operand-icon--chevron' />
),
color: 'purple' color: 'purple'
}, },
{ {
symbol: '<>', symbol: '<>',
label: <NotEqualIcon className='filter-input-operand-icon' />, label: <NotEqualIcon style={operandIconStyle} />,
color: 'magenta' color: 'magenta'
}, },
{ {
symbol: '>=', symbol: '>=',
label: <GreaterThanOrEqualToIcon className='filter-input-operand-icon' />, label: <GreaterThanOrEqualToIcon style={operandIconStyle} />,
color: 'volcano' color: 'volcano'
}, },
{ {
symbol: '<=', symbol: '<=',
label: <LessThanOrEqualToIcon className='filter-input-operand-icon' />, label: <LessThanOrEqualToIcon style={operandIconStyle} />,
color: 'volcano' color: 'volcano'
}, },
{ symbol: '|', label: 'OR', color: 'cyan' }, { symbol: '|', label: 'OR', color: 'cyan' },
@ -62,20 +62,18 @@ const OPERANDS = [
{ {
symbol: '>', symbol: '>',
label: ( label: (
<GreaterThanIcon className='filter-input-operand-icon filter-input-operand-icon--gt' /> <GreaterThanIcon style={{ ...operandIconStyle, marginLeft: '1px' }} />
), ),
color: 'orange' color: 'orange'
}, },
{ {
symbol: '<', symbol: '<',
label: ( label: <LessThanIcon style={{ ...operandIconStyle, marginRight: '1px' }} />,
<LessThanIcon className='filter-input-operand-icon filter-input-operand-icon--lt' />
),
color: 'orange' color: 'orange'
}, },
{ {
symbol: '=', symbol: '=',
label: <EqualIcon className='filter-input-operand-icon' />, label: <EqualIcon style={operandIconStyle} />,
color: 'green' color: 'green'
} }
] ]
@ -121,7 +119,7 @@ const tokenize = (raw = '') => {
const renderWildcardNode = (char) => { const renderWildcardNode = (char) => {
const span = document.createElement('span') const span = document.createElement('span')
span.setAttribute('data-wildcard', char) span.setAttribute('data-wildcard', char)
span.className = 'filter-input-wildcard' span.style.color = 'var(--color-primary)'
span.textContent = char span.textContent = char
return span return span
} }
@ -454,6 +452,7 @@ const FilterInput = ({
}) => { }) => {
const { getPrefixCls, direction } = useContext(ConfigProvider.ConfigContext) const { getPrefixCls, direction } = useContext(ConfigProvider.ConfigContext)
const prefixCls = getPrefixCls('input') const prefixCls = getPrefixCls('input')
const { token } = theme.useToken()
const { themeConfig } = useThemeContext() const { themeConfig } = useThemeContext()
const themeConfigRef = useRef(themeConfig) const themeConfigRef = useRef(themeConfig)
themeConfigRef.current = themeConfig themeConfigRef.current = themeConfig
@ -480,7 +479,6 @@ const FilterInput = ({
const [internalValue, setInternalValue] = useState(value ?? '') const [internalValue, setInternalValue] = useState(value ?? '')
const [quickFiltersHeight, setQuickFiltersHeight] = useState(0) const [quickFiltersHeight, setQuickFiltersHeight] = useState(0)
const [quickFilterModalOpen, setQuickFilterModalOpen] = useState(false) const [quickFilterModalOpen, setQuickFilterModalOpen] = useState(false)
const [hover, setHover] = useState(false)
const clearTagRoots = useCallback(() => { const clearTagRoots = useCallback(() => {
tagRootsRef.current.forEach((root) => { tagRootsRef.current.forEach((root) => {
@ -504,7 +502,22 @@ const FilterInput = ({
<Tag <Tag
color={operand?.color ?? 'default'} color={operand?.color ?? 'default'}
bordered={false} bordered={false}
className='filter-input-operand-tag' style={{
marginInlineEnd: 0,
marginRight: 0,
height: '18px',
cursor: 'default',
padding: '0 4px',
marginTop: '-2px',
lineHeight: 1.37,
userSelect: 'none',
pointerEvents: 'none',
flexShrink: 0,
fontWeight: 700,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
}}
> >
{operand?.label ?? symbol} {operand?.label ?? symbol}
</Tag> </Tag>
@ -517,7 +530,12 @@ const FilterInput = ({
const wrapper = document.createElement('span') const wrapper = document.createElement('span')
wrapper.setAttribute('data-operand', symbol) wrapper.setAttribute('data-operand', symbol)
wrapper.setAttribute('contenteditable', 'false') wrapper.setAttribute('contenteditable', 'false')
wrapper.className = 'filter-input-operand-chip' wrapper.style.display = 'inline-flex'
wrapper.style.alignItems = 'center'
wrapper.style.verticalAlign = 'middle'
wrapper.style.margin = '0 2px'
wrapper.style.userSelect = 'none'
wrapper.style.flexShrink = '0'
const root = createRoot(wrapper) const root = createRoot(wrapper)
root.render(renderTagNode(symbol, themeConfigRef.current)) root.render(renderTagNode(symbol, themeConfigRef.current))
@ -904,10 +922,16 @@ const FilterInput = ({
}} }}
className='filter-input-property-filter' className='filter-input-property-filter'
style={{ style={{
'--filter-input-property-filter-max-height': `${quickFiltersHeight + PROPERTY_FILTER_PANEL_MAX_HEIGHT}px` display: 'inline-flex',
flexDirection: 'column',
maxWidth: 280,
maxHeight: quickFiltersHeight + PROPERTY_FILTER_PANEL_MAX_HEIGHT,
margin: -4,
verticalAlign: 'top',
overflow: 'hidden'
}} }}
> >
<div ref={quickFiltersRef} className='filter-input-property-filter-quick'> <div ref={quickFiltersRef} style={{ flexShrink: 0 }}>
<QuickPropertyFilters <QuickPropertyFilters
modelType={propertyFilter.modelType} modelType={propertyFilter.modelType}
propertyName={propertyFilter.propertyName} propertyName={propertyFilter.propertyName}
@ -917,16 +941,24 @@ const FilterInput = ({
useCard={false} useCard={false}
/> />
</div> </div>
<div> <Divider style={{ margin: '4px 0' }} />
<Divider className='filter-input-property-filter-divider' />
</div>
<ScrollBox <ScrollBox
inner inner
smallPadding smallPadding
className='filter-input-property-filter-scroll' style={{
flexShrink: 0,
maxHeight: PROPERTY_FILTER_PANEL_MAX_HEIGHT,
maxWidth: 280
}}
> >
<div className='filter-input-property-filter-body'> <div
<div className='filter-input-property-filter-content'> style={{
padding: '12px 26px 12px 16px',
maxWidth: 280,
boxSizing: 'border-box'
}}
>
<div style={{ width: 'max-content', maxWidth: '100%' }}>
<SimplePropertyFilter <SimplePropertyFilter
modelType={propertyFilter.modelType} modelType={propertyFilter.modelType}
propertyName={propertyFilter.propertyName} propertyName={propertyFilter.propertyName}
@ -1030,18 +1062,20 @@ const FilterInput = ({
const showPlaceholder = !internalValue && !focused const showPlaceholder = !internalValue && !focused
const showClear = allowClear && !!internalValue && !disabled const showClear = allowClear && !!internalValue && !disabled
const affixCls = `${prefixCls}-affix-wrapper` const affixCls = `${prefixCls}-affix-wrapper`
const placeholderPaddingInline =
mergedSize === 'large'
? (token.paddingInlineLG ?? token.paddingSM + 4)
: mergedSize === 'small'
? (token.paddingInlineSM ?? token.paddingXS)
: (token.paddingInline ?? token.paddingSM)
const input = ( const input = (
<span <span
className={classNames( className={classNames(
affixCls, affixCls,
'filter-input-affix',
{ {
[`${affixCls}-focused`]: focused, [`${affixCls}-focused`]: focused,
[`${affixCls}-disabled`]: disabled, [`${affixCls}-disabled`]: disabled,
'filter-input-affix--disabled': disabled,
'filter-input-affix--elevated': focused || hover,
'filter-input-affix--has-clear': showClear,
[`${affixCls}-sm`]: mergedSize === 'small', [`${affixCls}-sm`]: mergedSize === 'small',
[`${affixCls}-lg`]: mergedSize === 'large', [`${affixCls}-lg`]: mergedSize === 'large',
[`${affixCls}-rtl`]: direction === 'rtl', [`${affixCls}-rtl`]: direction === 'rtl',
@ -1053,21 +1087,55 @@ const FilterInput = ({
hashId, hashId,
className className
)} )}
style={style} style={{
width: '100%',
cursor: disabled ? 'not-allowed' : 'text',
...style,
// Affix ::before strut needs inline-flex; display:block stacks it and inflates height.
display: 'inline-flex',
// Stay above Compact siblings so the focus ring isn't covered by Select/Button.
position: 'relative',
zIndex: focused ? 3 : style?.zIndex
}}
onClick={() => { onClick={() => {
if (!disabled && !quickFilterModalOpenRef.current) { if (!disabled && !quickFilterModalOpenRef.current) {
editorRef.current?.focus() editorRef.current?.focus()
} }
}} }}
onMouseEnter={() => { >
setHover(true) <style>{`
}} .filter-input-editor::-webkit-scrollbar {
onMouseLeave={() => { display: none;
setHover(false) height: 0;
}
.filter-input-property-filter .simplebar-content-wrapper {
max-width: none;
}
.filter-input-property-filter .simplebar-content {
display: inline-block;
}
`}</style>
{showPlaceholder && (
<span
aria-hidden
style={{
position: 'absolute',
insetInlineStart: placeholderPaddingInline,
insetInlineEnd: showClear
? placeholderPaddingInline + 22
: placeholderPaddingInline,
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
color: token.colorTextPlaceholder,
pointerEvents: 'none',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
zIndex: 1
}} }}
> >
{showPlaceholder && (
<span aria-hidden className='filter-input-placeholder'>
{placeholder} {placeholder}
</span> </span>
)} )}
@ -1114,6 +1182,31 @@ const FilterInput = ({
if (next !== valueRef.current) emitChange(next) if (next !== valueRef.current) emitChange(next)
onBlur?.(event) onBlur?.(event)
}} }}
style={{
// Match antd's `> input.ant-input` resets inside affix-wrapper
display: 'block',
padding: 0,
border: 'none',
borderRadius: 0,
outline: 'none',
boxShadow: 'none',
background: 'transparent',
flex: 'auto',
minWidth: 0,
width: '100%',
fontSize: 'inherit',
lineHeight: 'inherit',
color: 'inherit',
whiteSpace: 'nowrap',
overflowX: 'auto',
overflowY: 'hidden',
scrollbarWidth: 'none',
msOverflowStyle: 'none',
wordBreak: 'keep-all',
overflowWrap: 'normal',
caretColor: token.colorText,
cursor: disabled ? 'not-allowed' : 'text'
}}
/> />
{showClear && ( {showClear && (
<span className={`${prefixCls}-suffix`}> <span className={`${prefixCls}-suffix`}>
@ -1138,10 +1231,24 @@ const FilterInput = ({
placement='bottomLeft' placement='bottomLeft'
arrow={false} arrow={false}
trigger={[]} trigger={[]}
classNames={{ root: 'filter-input-popover' }} styles={{
body: {
padding: 8
}
}}
> >
{/* Anchor only — keep Popover from wrapping the Compact item (z-index/focus ring). */} {/* Anchor only — keep Popover from wrapping the Compact item (z-index/focus ring). */}
<span aria-hidden className='filter-input-popover-anchor' /> <span
aria-hidden
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: 0,
pointerEvents: 'none'
}}
/>
</Popover> </Popover>
)} )}
</span> </span>

View File

@ -371,7 +371,7 @@ const QuickPropertyFilters = ({
onClick={handleClear} onClick={handleClear}
style={{ style={{
textAlign: 'left', textAlign: 'left',
padding: '5px 12px 5px 12px', padding: '5px 12px',
justifyContent: 'flex-start' justifyContent: 'flex-start'
}} }}
type='text' type='text'
@ -386,7 +386,7 @@ const QuickPropertyFilters = ({
<Button <Button
style={{ style={{
textAlign: 'left', textAlign: 'left',
padding: '5px 8px 5px 12px', padding: '5px 12px',
justifyContent: 'flex-start' justifyContent: 'flex-start'
}} }}
type='text' type='text'
@ -435,7 +435,7 @@ const QuickPropertyFilters = ({
<Text> <Text>
{activeOption.needsRange {activeOption.needsRange
? `Enter a range to continue:` ? `Enter a range to continue:`
: `Enter a ${categoryLabel.toLowerCase()} value where the ${propertyLabel.toLowerCase()} ${activeOption.label.toLowerCase()}:`} : `Enter a ${categoryLabel.toLowerCase()} value to continue:`}
</Text> </Text>
{activeOption.needsRange ? ( {activeOption.needsRange ? (
<Flex vertical gap='middle'> <Flex vertical gap='middle'>