Skip to content

Commit 1858649

Browse files
Merge branch '25_1' of https://github.com/DevExpress/DevExtreme into 25_1
2 parents 20957af + e642666 commit 1858649

11 files changed

Lines changed: 224 additions & 79 deletions

File tree

.github/workflows/packages_publishing.yml

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ env:
2020
NX_SKIP_NX_CACHE: true
2121
FILTER: ${{ github.event_name == 'workflow_dispatch' && inputs.filter || '' }}
2222
SET_TIMESTAMP_VERSION: ${{ inputs.tag == 'daily' }}
23-
MOVE_DAILY_TAG: ${{ inputs.tag == 'daily' }}
24-
MOVE_STABLE_TAG: ${{ inputs.tag == 'stable' }}
2523

2624
jobs:
2725
build:
@@ -145,32 +143,13 @@ jobs:
145143
working-directory: ${{ steps.scopedPackage.outputs.packageDir }}
146144
env:
147145
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
148-
run: |
149-
pnpm set //npm.pkg.github.com/:_authToken="$NODE_AUTH_TOKEN";
150-
pnpm publish --no-git-checks --quiet --ignore-scripts --registry https://npm.pkg.github.com;
151-
152-
- name: Move 'daily' tag
153-
if: ${{ env.MOVE_DAILY_TAG == 'true' }}
154-
env:
155-
PACKAGE_NAME: ${{ steps.scopedPackage.outputs.name }}
156-
PACKAGE_VERSION: ${{ steps.scopedPackage.outputs.version }}
157-
PACKAGE_VERSION_MAJOR: ${{ steps.scopedPackage.outputs.majorVersion }}
158-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
159-
run: |
160-
pnpm set //npm.pkg.github.com/:_authToken="$NODE_AUTH_TOKEN"
161-
pnpm dist-tag add $PACKAGE_NAME@$PACKAGE_VERSION $PACKAGE_VERSION_MAJOR-daily --registry=https://npm.pkg.github.com
162-
163-
- name: Move 'stable' tag
164-
if: ${{ env.MOVE_STABLE_TAG == 'true' }}
165-
env:
166146
PACKAGE_NAME: ${{ steps.scopedPackage.outputs.name }}
167147
PACKAGE_VERSION: ${{ steps.scopedPackage.outputs.version }}
168148
PACKAGE_VERSION_MAJOR: ${{ steps.scopedPackage.outputs.majorVersion }}
169-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
170149
run: |
171-
pnpm set //npm.pkg.github.com/:_authToken="$NODE_AUTH_TOKEN"
172-
pnpm dist-tag add $PACKAGE_NAME@$PACKAGE_VERSION $PACKAGE_VERSION_MAJOR-stable --registry=https://npm.pkg.github.com
173-
150+
pnpm set //npm.pkg.github.com/:_authToken="$NODE_AUTH_TOKEN";
151+
pnpm publish --no-git-checks --quiet --ignore-scripts --tag $PACKAGE_VERSION_MAJOR-${{ inputs.tag }} --registry https://npm.pkg.github.com;
152+
pnpm dist-tag add $PACKAGE_NAME@$PACKAGE_VERSION latest --registry=https://npm.pkg.github.com;
174153
175154
notify:
176155
runs-on: devextreme-shr2

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
"bn.js@>=5.0.0 <5.2.3": "5.2.3",
8888
"brace-expansion@<1.1.13": "1.1.13",
8989
"brace-expansion@>=2.0.0 <2.0.3": "2.0.3",
90+
"brace-expansion@>=4.0.0 <5.0.6": "^5.0.6",
91+
"ws@>=8.0.0 <8.20.1": "^8.20.1",
9092
"braces@<3.0.3": "^3.0.3",
9193
"cookie@<0.7.0": "^0.7.0",
9294
"diff@>=4.0.0 <4.0.4": "4.0.4",

packages/devextreme-scss/scss/widgets/base/treeView/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@include dx-icon($collapse-icon-name);
1313

1414
display: flex;
15+
flex-shrink: 0;
1516
align-items: center;
1617
justify-content: center;
1718
font-size: $font-size;

packages/devextreme/js/__internal/ui/chat/messagelist.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,15 @@ class MessageList extends Widget<Properties> {
746746
}
747747

748748
_setIsReachedBottom(): void {
749-
this._isBottomReached = !this._isContentOverflowing() || this._scrollView.isBottomReached();
749+
if (!this._isContentOverflowing()) {
750+
this._isBottomReached = true;
751+
752+
return;
753+
}
754+
755+
const container = this._scrollableContainer();
756+
const maxScroll = getScrollTopMax(container);
757+
this._isBottomReached = Math.round(maxScroll - Math.ceil(container.scrollTop)) <= 1;
750758
}
751759

752760
_isContentOverflowing(): boolean {

packages/devextreme/js/__internal/ui/m_tag_box.ts

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ function xor(a: boolean, b: boolean): boolean {
3333
return (a || b) && !(a && b);
3434
}
3535

36+
type TagBoxItem = string | number | any;
37+
type SelectedItemsMap = Record<string, TagBoxItem>;
38+
3639
const TAGBOX_TAG_DATA_KEY = 'dxTagData';
3740
const TAGBOX_TAG_DISPLAY_VALUE = 'dxTagDisplayValue';
3841

@@ -51,13 +54,13 @@ const TEXTEDITOR_INPUT_CONTAINER_CLASS = 'dx-texteditor-input-container';
5154

5255
const TAGBOX_MOUSE_WHEEL_DELTA_MULTIPLIER = -0.3;
5356

54-
export interface TagBoxProperties extends Omit<Properties,
55-
'onCustomItemCreating'
56-
| 'onItemClick' | 'onSelectionChanged'
57-
| 'onOpened' | 'onClosed'
58-
| 'onChange' | 'onCopy' | 'onCut' | 'onEnterKey' | 'onFocusIn' | 'onFocusOut' | 'onInput' | 'onKeyDown' | 'onKeyUp' | 'onPaste'
59-
| 'onValueChanged' | 'validationMessagePosition' | 'onContentReady' | 'onDisposing' | 'onOptionChanged' | 'onInitialized'> {
60-
57+
export interface TagBoxProperties extends Omit<
58+
Properties,
59+
'onCustomItemCreating'
60+
| 'onItemClick' | 'onSelectionChanged'
61+
| 'onOpened' | 'onClosed'
62+
| 'onChange' | 'onCopy' | 'onCut' | 'onEnterKey' | 'onFocusIn' | 'onFocusOut' | 'onInput' | 'onKeyDown' | 'onKeyUp' | 'onPaste'
63+
| 'onValueChanged' | 'validationMessagePosition' | 'onContentReady' | 'onDisposing' | 'onOptionChanged' | 'onInitialized'> {
6164
}
6265

6366
class TagBox<
@@ -856,7 +859,7 @@ class TagBox<
856859
// @ts-expect-error ts-error
857860
const isListItemsLoaded = !!listSelectedItems && this._list._dataController.isLoaded();
858861
const selectedItems = listSelectedItems || this.option('selectedItems');
859-
// @ts-expect-error ts-error
862+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
860863
const clientFilterFunction = creator.getLocalFilter(this._valueGetter);
861864
// @ts-expect-error ts-error
862865
const filteredItems = selectedItems.filter(clientFilterFunction);
@@ -903,13 +906,13 @@ class TagBox<
903906
_createTagsData(values, filteredItems) {
904907
const items = [];
905908
const cache = {};
906-
// @ts-expect-error ts-error
909+
// @ts-expect-error _valueGetterExpr is injected by DataExpressionMixin
907910
const isValueExprSpecified = this._valueGetterExpr() === 'this';
908911
const { acceptCustomValue } = this.option();
909912
const filteredValues = {};
910913

911914
filteredItems.forEach((filteredItem) => {
912-
// @ts-expect-error
915+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
913916
const filteredItemValue = isValueExprSpecified ? JSON.stringify(filteredItem) : this._valueGetter(filteredItem);
914917

915918
filteredValues[filteredItemValue] = filteredItem;
@@ -963,7 +966,7 @@ class TagBox<
963966
return item;
964967
}
965968
const selectedItem = this.option('selectedItem');
966-
// @ts-expect-error
969+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
967970
const customItem = this._valueGetter(selectedItem) === value ? selectedItem : value;
968971

969972
return customItem;
@@ -1042,7 +1045,10 @@ class TagBox<
10421045
this._selectedItems = this._getItemsFromPlain(this._valuesToUpdate);
10431046

10441047
if (this._selectedItems.length === this._valuesToUpdate.length) {
1045-
this._tagsToRender = this._selectedItems;
1048+
this._tagsToRender = this._sortSelectedItemsByValues(
1049+
this._selectedItems,
1050+
this._valuesToUpdate,
1051+
);
10461052
this._renderTagsImpl();
10471053
isPlainDataUsed = true;
10481054
d.resolve();
@@ -1109,6 +1115,45 @@ class TagBox<
11091115
return selectedItems;
11101116
}
11111117

1118+
_shouldUseClickOrderForTags(values: TagBox['_valuesToUpdate']): boolean {
1119+
const { maxDisplayedTags, showMultiTagOnly } = this.option();
1120+
1121+
return !showMultiTagOnly
1122+
&& isDefined(maxDisplayedTags)
1123+
&& values.length > maxDisplayedTags;
1124+
}
1125+
1126+
_sortSelectedItemsByValues(
1127+
selectedItems: TagBoxItem[],
1128+
values: TagBoxItem[],
1129+
): TagBoxItem[] {
1130+
if (!this._shouldUseClickOrderForTags(values) || !selectedItems.length) {
1131+
return selectedItems;
1132+
}
1133+
// @ts-expect-error _valueGetterExpr is injected by DataExpressionMixin
1134+
const isValueExprDefault = this._valueGetterExpr() === 'this';
1135+
1136+
const mappedSelectedItems = selectedItems.reduce<SelectedItemsMap>((result, item) => {
1137+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
1138+
const itemValue = isValueExprDefault ? JSON.stringify(item) : this._valueGetter(item);
1139+
result[itemValue] = item;
1140+
1141+
return result;
1142+
}, {});
1143+
1144+
const selectedByOrderItems: TagBoxItem[] = values.reduce((result, currentValue) => {
1145+
const normalizedValue = isValueExprDefault ? JSON.stringify(currentValue) : currentValue;
1146+
const item = mappedSelectedItems[normalizedValue];
1147+
if (isDefined(item)) {
1148+
result.push(item);
1149+
}
1150+
1151+
return result;
1152+
}, []);
1153+
1154+
return selectedByOrderItems;
1155+
}
1156+
11121157
_filterSelectedItems(plainItems, values) {
11131158
const selectedItems = plainItems.filter((dataItem) => {
11141159
let currentValue;
@@ -1176,8 +1221,7 @@ class TagBox<
11761221

11771222
_renderTagsElements(items): void {
11781223
const $multiTag = this._multiTagRequired() && this._renderMultiTag(this._input());
1179-
const showMultiTagOnly = this.option('showMultiTagOnly');
1180-
const maxDisplayedTags = this.option('maxDisplayedTags');
1224+
const { showMultiTagOnly, maxDisplayedTags } = this.option();
11811225

11821226
items.forEach((item, index) => {
11831227
// @ts-expect-error ts-error
@@ -1202,7 +1246,7 @@ class TagBox<
12021246
const $tags = this._tagElements();
12031247

12041248
const selectedItems = this.option('selectedItems') ?? [];
1205-
// @ts-expect-error ts-error
1249+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
12061250
const values = selectedItems.map((item) => this._valueGetter(item));
12071251

12081252
each($tags, (_, tag) => {
@@ -1248,7 +1292,7 @@ class TagBox<
12481292
}
12491293

12501294
_renderTag(item, $input): void {
1251-
// @ts-expect-error ts-error
1295+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
12521296
const value = this._valueGetter(item);
12531297

12541298
if (!isDefined(value)) {
@@ -1395,12 +1439,12 @@ class TagBox<
13951439
const value = this._getValue().slice();
13961440

13971441
each(e.removedItems || [], (_, removedItem) => {
1398-
// @ts-expect-error ts-error
1442+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
13991443
this._removeTag(value, this._valueGetter(removedItem));
14001444
});
14011445

14021446
each(e.addedItems || [], (_, addedItem) => {
1403-
// @ts-expect-error ts-error
1447+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
14041448
this._addTag(value, this._valueGetter(addedItem));
14051449
});
14061450

@@ -1558,7 +1602,7 @@ class TagBox<
15581602
}
15591603

15601604
const dataController = this._dataController;
1561-
// @ts-expect-error ts-error
1605+
// @ts-expect-error _valueGetterExpr is injected by DataExpressionMixin
15621606
const valueGetterExpr = this._valueGetterExpr();
15631607

15641608
if (isString(valueGetterExpr) && valueGetterExpr !== 'this') {
@@ -1580,14 +1624,14 @@ class TagBox<
15801624

15811625
_dataSourceFilterExpr() {
15821626
const filter = [];
1583-
// @ts-expect-error
1627+
// @ts-expect-error _valueGetterExpr is injected by DataExpressionMixin
15841628
this._getValue().forEach((value) => filter.push(['!', [this._valueGetterExpr(), value]]));
15851629

15861630
return filter;
15871631
}
15881632

15891633
_dataSourceFilterFunction(itemData) {
1590-
// @ts-expect-error ts-error
1634+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
15911635
const itemValue = this._valueGetter(itemData);
15921636
let result = true;
15931637

@@ -1636,7 +1680,7 @@ class TagBox<
16361680

16371681
return this
16381682
._getPlainItems(this._list.option('selectedItems'))
1639-
// @ts-expect-error
1683+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
16401684
.map((item) => this._valueGetter(item));
16411685
}
16421686

@@ -1695,15 +1739,15 @@ class TagBox<
16951739
}
16961740

16971741
const previousItemsValuesMap = previousItems.reduce((map, item) => {
1698-
// @ts-expect-error ts-error
1742+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
16991743
const value = this._valueGetter(item);
17001744
map[value] = item;
17011745
return map;
17021746
}, {});
17031747

17041748
const addedItems = [];
17051749
newItems.forEach((item) => {
1706-
// @ts-expect-error ts-error
1750+
// @ts-expect-error _valueGetter is injected by DataExpressionMixin
17071751
const value = this._valueGetter(item);
17081752
if (!previousItemsValuesMap[value]) {
17091753
addedItems.push(item as never);

packages/devextreme/js/__internal/viz/sparklines/base_sparkline.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ const _disposeTooltip = BaseSparkline.prototype._disposeTooltip;
275275
BaseSparkline.prototype._disposeTooltip = function () {
276276
if (this._tooltip) {
277277
_disposeTooltip.apply(this, arguments);
278+
this._tooltipShown = false;
278279
}
279280
};
280281
BaseSparkline.prototype._setTooltipRendererOptions = function () {

packages/devextreme/playground/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ window.addEventListener('load', () =>
2929
cardMinWidth: 320,
3030
columns: ['Company', 'Address', 'City', 'State', 'Zipcode', 'Phone'],
3131
});
32-
}));
32+
}));

packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/tagBox.tests.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,62 @@ QUnit.module('multi tag support', {
10781078
assert.deepEqual(this.getTexts($tagBox.find('.' + TAGBOX_TAG_CLASS)), ['1', '2'], 'tags have correct text');
10791079
});
10801080

1081+
1082+
QUnit.test('TagBox should preserve reverse click order in leading tag when showMultiTagOnly is false', function(assert) {
1083+
const $tagBox = $('#tagBox').dxTagBox({
1084+
items: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
1085+
showSelectionControls: true,
1086+
maxDisplayedTags: 2,
1087+
showMultiTagOnly: false,
1088+
opened: true
1089+
});
1090+
1091+
const tagBox = $tagBox.dxTagBox('instance');
1092+
1093+
this.clock.tick(TIME_TO_WAIT);
1094+
1095+
const $listItems = getListItems(tagBox);
1096+
1097+
$listItems.last().trigger('dxclick');
1098+
$listItems.eq(7).trigger('dxclick');
1099+
$listItems.eq(6).trigger('dxclick');
1100+
1101+
1102+
assert.strictEqual($tagBox.find('.' + TAGBOX_TAG_CLASS).first().text(), '10', 'leading tag has correct text');
1103+
});
1104+
1105+
QUnit.test('TagBox should work correctly with string ID\'s in item when valueExpr is used', function(assert) {
1106+
const items = [
1107+
{ ID: 'a', Name: 'HD Video Player' },
1108+
{ ID: 'b', Name: 'SuperHD Video Player' },
1109+
{ ID: 'c', Name: 'SuperPlasma 50' },
1110+
{ ID: 'd', Name: 'SuperLED 50' }
1111+
];
1112+
const $tagBox = $('#tagBox').dxTagBox({
1113+
items: items,
1114+
valueExpr: 'ID',
1115+
displayExpr: 'Name',
1116+
showSelectionControls: true,
1117+
maxDisplayedTags: 2,
1118+
showMultiTagOnly: false,
1119+
opened: true
1120+
});
1121+
1122+
const tagBox = $tagBox.dxTagBox('instance');
1123+
1124+
this.clock.tick(TIME_TO_WAIT);
1125+
1126+
const $listItems = getListItems(tagBox);
1127+
1128+
$listItems.last().trigger('dxclick');
1129+
$listItems.eq(2).trigger('dxclick');
1130+
$listItems.eq(1).trigger('dxclick');
1131+
1132+
1133+
assert.strictEqual($tagBox.find('.' + TAGBOX_TAG_CLASS).first().text(), items[items.length - 1].Name, 'leading tag has correct text');
1134+
});
1135+
1136+
10811137
QUnit.test('only one multi tag should be rendered when selectAll checked and value changind on runtime', function(assert) {
10821138
let suppressSelectionChanged = false;
10831139

0 commit comments

Comments
 (0)