From f810dfcedd67795f0a858fa02be2451d046ac722 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrinovich <52966626+dmlvr@users.noreply.github.com> Date: Fri, 4 Sep 2026 13:14:44 +0000 Subject: [PATCH 01/45] Scheduler - mock date in demo for screenshots testing (#35054) --- .../ResolveTimeConflicts/Angular/app/app.component.ts | 9 +++++++++ .../Demos/Scheduler/ResolveTimeConflicts/React/App.tsx | 9 +++++++++ .../Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js | 8 ++++++++ .../Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue | 9 +++++++++ .../Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js | 9 +++++++++ 5 files changed, 44 insertions(+) diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts index 96bd4aa81eac..8b275fbc3e96 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts @@ -86,6 +86,15 @@ export class AppComponent { this.form?.validate(); } }); + + this.form?.on('contentReady', (contentReadyEvent: DxFormTypes.ContentReadyEvent) => { + const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); + untilEditor?.on('valueChanged', () => { + contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' + && untilEditor?.option('value', new Date(2026, 8, 3)); + }); + untilEditor?.option('value', new Date(2026, 8, 3)); + }); }; customizeItem = (item: DxFormTypes.SimpleItem): void => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx index 42a44a6958aa..11ccad20985b 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx @@ -173,6 +173,15 @@ const App = () => { formRef.current?.validate(); } }); + + e.component.on('contentReady', (contentReadyEvent: FormTypes.ContentReadyEvent) => { + const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); + untilEditor?.on('valueChanged', () => { + contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' + && untilEditor?.option('value', new Date(2026, 8, 3)); + }); + untilEditor?.option('value', new Date(2026, 8, 3)); + }); }, [setConflictError]); const customizeItem = useCallback((item: FormTypes.SimpleItem) => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js index e13487f0fae2..9cbd4f8a600f 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js @@ -149,6 +149,14 @@ const App = () => { formRef.current?.validate(); } }); + e.component.on('contentReady', (contentReadyEvent) => { + const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); + untilEditor?.on('valueChanged', () => { + contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== + 'until' && untilEditor?.option('value', new Date(2026, 8, 3)); + }); + untilEditor?.option('value', new Date(2026, 8, 3)); + }); }, [setConflictError], ); diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue index 260dc02e5e76..38507b99935d 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue @@ -240,6 +240,15 @@ const onFormInitialized = (e: DxFormTypes.InitializedEvent) => { form?.validate(); } }); + + e.component!.on('contentReady', (contentReadyEvent: DxFormTypes.ContentReadyEvent) => { + const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); + untilEditor?.on('valueChanged', () => { + contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' + && untilEditor?.option('value', new Date(2026, 8, 3)); + }); + untilEditor?.option('value', new Date(2026, 8, 3)); + }); }; const customizeItem = (item: DxFormTypes.SimpleItem) => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js index b6a6887daf51..9358db8a1b7a 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js @@ -43,6 +43,15 @@ $(() => { form.validate(); } }); + + form.on('contentReady', (e) => { + const untilEditor = e.component.getEditor('recurrenceEndUntilEditor'); + untilEditor.on('valueChanged', () => { + e.component.getEditor('recurrenceRepeatEndEditor').option('value') !== 'until' + && untilEditor.option('value', new Date(2026, 8, 3)); + }); + untilEditor.option('value', new Date(2026, 8, 3)); + }); }, items: [ { From 49d66d64af345314ef6b60c8f13fe6b612904ba2 Mon Sep 17 00:00:00 2001 From: Andrei Kharitonov Date: Fri, 4 Sep 2026 13:47:34 +0000 Subject: [PATCH 02/45] Tooltip: ESC key does not close both the Tooltip and its parent overlay (T1334708) (#35032) --- .../js/__internal/ui/overlay/overlay.ts | 24 ++++++++++-- .../js/__internal/ui/popover/popover.ts | 14 +++---- .../js/__internal/ui/popup/popup.ts | 12 ++++-- .../DevExpress.ui.widgets/popup.tests.js | 37 +++++++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/overlay/overlay.ts b/packages/devextreme/js/__internal/ui/overlay/overlay.ts index d6b3068d30ee..0b4a3a9bc546 100644 --- a/packages/devextreme/js/__internal/ui/overlay/overlay.ts +++ b/packages/devextreme/js/__internal/ui/overlay/overlay.ts @@ -565,14 +565,13 @@ class Overlay< super._initTemplates(); } - _isTopOverlay(): boolean { + _isTopFocusableOverlay(): boolean { const overlayStack = this._overlayStack(); for (let i = overlayStack.length - 1; i >= 0; i -= 1) { const tabbableElements = overlayStack[i]._findTabbableBounds(); if (tabbableElements.$first || tabbableElements.$last) { - // @ts-ignore expected: types Overlay and this have no overlap return overlayStack[i] === this; } } @@ -580,6 +579,25 @@ class Overlay< return false; } + _isLastInOverlayStack(): boolean { + const overlayStack = this._overlayStack(); + + return overlayStack[overlayStack.length - 1] === this; + } + + _handlesDocumentEscapeKey(): boolean { + return false; + } + + _isEscapeHandledByOverlayAbove(): boolean { + const overlayStack = this._overlayStack(); + const index = overlayStack.indexOf(this); + + return overlayStack + .slice(index + 1) + .some((overlay) => overlay._handlesDocumentEscapeKey()); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any _overlayStack(): Overlay[] { return OVERLAY_STACK; @@ -1052,7 +1070,7 @@ class Overlay< } _tabKeyHandler(e: KeyboardEvent): void { - if (normalizeKeyName(e) !== TAB_KEY || !this._isTopOverlay()) { + if (normalizeKeyName(e) !== TAB_KEY || !this._isTopFocusableOverlay()) { return; } diff --git a/packages/devextreme/js/__internal/ui/popover/popover.ts b/packages/devextreme/js/__internal/ui/popover/popover.ts index fa5d0fbf46e5..d071cd9f425e 100644 --- a/packages/devextreme/js/__internal/ui/popover/popover.ts +++ b/packages/devextreme/js/__internal/ui/popover/popover.ts @@ -225,19 +225,19 @@ class Popover< _initEscapeKeyHandler(): void { this._documentEscapeKeyHandler = (e: KeyboardEvent): void => { - const { visible } = this.option(); - - const overlayStack = this._overlayStack(); - // @ts-ignore expected: types Overlay and this have no overlap - const isTopOverlay = overlayStack[overlayStack.length - 1] === this; - - if (normalizeKeyName(e) === ESC_KEY_NAME && visible && isTopOverlay) { + if (normalizeKeyName(e) === ESC_KEY_NAME && this._handlesDocumentEscapeKey()) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.hide(); } }; } + _handlesDocumentEscapeKey(): boolean { + const { visible } = this.option(); + + return Boolean(visible) && this._isLastInOverlayStack(); + } + _attachEscapeKeyHandler(): void { const eventName = addNamespace('keydown', this.NAME as string); diff --git a/packages/devextreme/js/__internal/ui/popup/popup.ts b/packages/devextreme/js/__internal/ui/popup/popup.ts index efbd2cf74297..ace3c8ebbc71 100644 --- a/packages/devextreme/js/__internal/ui/popup/popup.ts +++ b/packages/devextreme/js/__internal/ui/popup/popup.ts @@ -245,10 +245,14 @@ class Popup< const e = options.originalEvent; const $target = $(e.target); - if (this._$content && !$target.is(this._$content) - && options.keyName === ESC_KEY_NAME - && !e.isDefaultPrevented() - && !_ignoreCloseOnChildEscape) { + const shouldCloseOnChildEscape = !!this._$content + && !$target.is(this._$content) + && options.keyName === ESC_KEY_NAME + && !e.isDefaultPrevented() + && !_ignoreCloseOnChildEscape + && !this._isEscapeHandledByOverlayAbove(); + + if (shouldCloseOnChildEscape) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.hide(); } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js index 1fcb96b26eef..9b47c993828a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js @@ -33,6 +33,8 @@ import { BUTTON_CLASS } from '__internal/ui/button/button'; import 'fluent_blue_light.css!'; import 'ui/popup'; +import 'ui/tooltip'; +import 'ui/load_panel'; import 'ui/tab_panel'; import 'ui/scroll_view'; import 'ui/date_box'; @@ -2774,6 +2776,41 @@ QUnit.module('keyboard navigation', { assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible when _ignoreCloseOnChildEscape is true'); }); + + QUnit.test('should remain visible when child element presses escape and a tooltip is shown above it (T1334708)', function(assert) { + this.init({ dragEnabled: false }); + + const $input = $('').appendTo(this.popup.$content()); + const tooltip = $('
') + .appendTo('#qunit-fixture') + .dxTooltip({ target: $input, visible: true, animation: null }) + .dxTooltip('instance'); + + assert.strictEqual(tooltip.option('visible'), true, 'tooltip is shown'); + + keyboardMock($input).keyDown('esc'); + + assert.strictEqual(tooltip.option('visible'), false, 'tooltip is hidden'); + assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible'); + + tooltip.dispose(); + }); + + QUnit.test('should be closed on child element escape key press when an overlay that ignores escape is shown above it', function(assert) { + this.init({ dragEnabled: false }); + + const $input = $('').appendTo(this.popup.$content()); + const loadPanel = $('
') + .appendTo('#qunit-fixture') + .dxLoadPanel({ visible: true, animation: null }) + .dxLoadPanel('instance'); + + keyboardMock($input).keyDown('esc'); + + assert.strictEqual(this.popup.option('visible'), false, 'popup is closed'); + + loadPanel.dispose(); + }); }); QUnit.module('rendering', { From 90beeb9f75014adcae68b227f76a40f43c7b444f Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Fri, 4 Sep 2026 14:40:36 +0000 Subject: [PATCH 03/45] chore(security): bump fast-uri, qs and fflate overrides to patched versions (GHSA-5jgf-p345-68v8, GHSA-4mjr-xmp4-gh2g, GHSA-px8p-9vwx-vf98) (#35034) --- pnpm-lock.yaml | 51 +++++++++++++++++++-------------------------- pnpm-workspace.yaml | 5 +++-- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aed252f8908c..7afce36ee551 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,7 +185,7 @@ overrides: axios@<1.18.0: ^1.18.0 braces@<3.0.3: ^3.0.3 semver@<5.7.2: ^5.7.2 - qs: '>=6.15.2' + qs: '>=6.16.0' vite@>=7.0.0 <=7.3.4: ^7.3.5 hono@<4.12.34: ^4.12.34 minimatch@>=9.0.0 < 9.0.7: 9.0.9 @@ -223,13 +223,14 @@ overrides: tar@<7.5.19: ^7.5.19 shell-quote@<=1.8.4: ^1.9.0 babel-traverse: npm:@babel/traverse@^7.23.2 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 '@hono/node-server@<2.0.12': ^2.0.12 dompurify@<=3.4.12: ^3.4.13 nanoid@<3.3.17: ^3.3.17 browserslist@<4.28.7: ^4.28.7 postcss-selector-parser@>=6.1.0 <6.1.3: ^6.1.3 postcss-selector-parser@>=7.1.0 <7.1.3: ^7.1.3 + fflate@>=0.8.0 <0.8.3: ^0.8.3 patchedDependencies: testcafe-hammerhead@31.7.8: 8655c07786177d3b611a05abf6b0ea87d32796eb2601f92a800ef041095f264c @@ -10896,8 +10897,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -10931,8 +10932,8 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -14487,12 +14488,8 @@ packages: resolution: {integrity: sha512-ZvWjbAj4MWAj6bnCc9CnculsXnJr7eoKsvH/8rVpZbqYxP2z05HNQa43ZVwe/dVRcFxgfFHE2CkUqn0sCyLfHw==} hasBin: true - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} - engines: {node: '>=0.6'} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} querystring-es3@0.2.1: @@ -25807,14 +25804,14 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -26437,7 +26434,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -26452,7 +26449,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -27554,7 +27551,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.7 regexp.prototype.flags: 1.5.4 - side-channel: 1.1.0 + side-channel: 1.1.1 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.20 @@ -29180,7 +29177,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.15.2 + qs: 6.16.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -29215,7 +29212,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.3 + qs: 6.16.0 range-parser: 1.3.0 router: 2.2.0(supports-color@7.2.0) send: 1.2.1(supports-color@7.2.0) @@ -29279,7 +29276,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.5: {} + fast-uri@3.1.7: {} fast-wrap-ansi@0.2.2: dependencies: @@ -29313,7 +29310,7 @@ snapshots: fecha@4.2.3: {} - fflate@0.8.2: {} + fflate@0.8.3: {} figures@3.2.0: dependencies: @@ -31953,7 +31950,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 fast-png: 6.4.0 - fflate: 0.8.2 + fflate: 0.8.3 optionalDependencies: canvg: 3.0.11 core-js: 3.49.0 @@ -34277,11 +34274,7 @@ snapshots: qrcode-terminal@0.10.0: {} - qs@6.15.2: - dependencies: - side-channel: 1.1.0 - - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 @@ -37057,7 +37050,7 @@ snapshots: union@0.5.0: dependencies: - qs: 6.15.2 + qs: 6.16.0 unist-util-find-after@4.0.1: dependencies: @@ -37174,7 +37167,7 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.15.2 + qs: 6.16.0 use-sync-external-store@1.6.0(react@18.3.1): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 723fdeb948d3..cc6a4529add4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -19,7 +19,7 @@ overrides: "axios@<1.18.0": ^1.18.0 "braces@<3.0.3": ^3.0.3 "semver@<5.7.2": ^5.7.2 - qs: ">=6.15.2" + qs: ">=6.16.0" "vite@>=7.0.0 <=7.3.4": ^7.3.5 "hono@<4.12.34": "^4.12.34" "minimatch@>=9.0.0 < 9.0.7": 9.0.9 @@ -57,13 +57,14 @@ overrides: "tar@<7.5.19": "^7.5.19" "shell-quote@<=1.8.4": ^1.9.0 "babel-traverse": "npm:@babel/traverse@^7.23.2" - "fast-uri@>=3.0.0 <3.1.5": "^3.1.5" + "fast-uri@>=3.0.0 <3.1.6": "^3.1.6" "@hono/node-server@<2.0.12": "^2.0.12" "dompurify@<=3.4.12": "^3.4.13" "nanoid@<3.3.17": "^3.3.17" "browserslist@<4.28.7": "^4.28.7" "postcss-selector-parser@>=6.1.0 <6.1.3": "^6.1.3" "postcss-selector-parser@>=7.1.0 <7.1.3": "^7.1.3" + "fflate@>=0.8.0 <0.8.3": "^0.8.3" catalog: axe-core: 4.11.3 inferno: ^8.2.3 From 674e0aa519854eec334209b858af20a8b0ad32d0 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Sat, 5 Sep 2026 23:58:17 +0000 Subject: [PATCH 04/45] CI: fail a hung TestCafe test after 3 minutes instead of waiting for the job timeout (#35046) --- e2e/testcafe-devextreme/runner.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/testcafe-devextreme/runner.ts b/e2e/testcafe-devextreme/runner.ts index 0be6eeeafc80..ab47c5657686 100644 --- a/e2e/testcafe-devextreme/runner.ts +++ b/e2e/testcafe-devextreme/runner.ts @@ -16,6 +16,7 @@ import { getCurrentTheme } from './helpers/themeUtils'; const LAUNCH_RETRY_ATTEMPTS = 3; const LAUNCH_RETRY_TIMEOUT = 10000; const FAILED_TESTS_RETRY_ATTEMPTS = 0; +const TEST_EXECUTION_TIMEOUT = 3 * 60 * 1000; const wait = async ( timeout: number, @@ -328,6 +329,7 @@ async function main() { const runOptions: RunOptions = { quarantineMode: false, + testExecutionTimeout: TEST_EXECUTION_TIMEOUT, // @ts-expect-error ts-error hooks: { test: { From ea9296e7a3525240a60393859fd74c9fc648498a Mon Sep 17 00:00:00 2001 From: Alyar Date: Sun, 6 Sep 2026 20:27:19 +0000 Subject: [PATCH 05/45] Grids - DataController: Extract row type knowledge to extenders (#35031) Co-authored-by: Alyar <> --- .../grouping/__tests__/utils.test.ts | 38 ++++++- ...ing_data_controller.data_row_index.test.ts | 81 ++++++++++++++ ...ouping_data_controller.row_changes.test.ts | 36 +++++- .../extenders/grouping_data_controller.ts | 22 +++- .../grids/data_grid/grouping/utils.ts | 16 +++ ...ummary_data_controller.row_changes.test.ts | 1 + .../extenders/summary_data_controller.ts | 13 +++ .../__tests__/__mock__/helpers/row_changes.ts | 16 ++- .../data_controller/data_controller.ts | 37 +++--- .../utils/__tests__/row_changes.test.ts | 105 +++--------------- .../data_controller/utils/row_changes.ts | 36 +----- ...diting_data_controller.row_changes.test.ts | 16 +++ 12 files changed, 278 insertions(+), 139 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts index ff48341edb38..4bf12e9d36b8 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts @@ -5,7 +5,7 @@ import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; const groupRow = (partial: Partial = {}): ProcessedItem => ({ @@ -136,3 +136,39 @@ describe('isSameContinuationState', () => { )).toBe(true); }); }); + +describe('getGroupColumnIndices', () => { + const expandedGroupRow = (partial: Partial = {}): ProcessedItem => groupRow({ + isExpanded: true, + data: { isContinuation: false, isContinuationOnNextPage: false }, + ...partial, + }); + + it('should skip the group expand cell', () => { + const oldItem = expandedGroupRow({ + cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], + }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow())).toEqual([1, 2]); + }); + + it('should return undefined when the old row has no cells', () => { + expect(getGroupColumnIndices(expandedGroupRow(), expandedGroupRow())).toBeUndefined(); + }); + + it('should return undefined when the expanded state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow({ isExpanded: false }))) + .toBeUndefined(); + }); + + it('should return undefined when the continuation state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + const newItem = expandedGroupRow({ + data: { isContinuation: true, isContinuationOnNextPage: false }, + }); + + expect(getGroupColumnIndices(oldItem, newItem)).toBeUndefined(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts new file mode 100644 index 000000000000..6e1d64269b75 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts @@ -0,0 +1,81 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; + +declare class ExposedDataController extends DataController { + public _items: ProcessedItem[]; + + public adjustInsertRowIndex: (visibleRowIndex: number) => number; +} + +const row = (rowType: ProcessedItem['rowType']): ProcessedItem => ({ + rowType, + key: rowType, + data: {}, + values: [], +}); + +const withVisibleRows = async ( + rows: ProcessedItem[], +): Promise<(visibleRowIndex: number) => number> => { + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['name', 'age'], + }); + const dataController = instance.getController('data') as unknown as ExposedDataController; + + dataController._items = rows; + + return (visibleRowIndex: number): number => dataController.adjustInsertRowIndex(visibleRowIndex); +}; + +describe('Grouping data controller data row index', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should count the group rows along with the data rows', async () => { + const dataRowIndex = await withVisibleRows([ + row('data'), row('group'), row('detail'), row('data'), + ]); + + expect(dataRowIndex(3)).toBe(2); + expect(dataRowIndex(4)).toBe(3); + }); + + it('should not count the group footer rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('groupFooter'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should not count the adaptive detail rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detailAdaptive'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should count only the data rows when there are no group rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detail'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should return zero for the first visible index', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(0)).toBe(0); + }); + + it('should count the rows that are there when the index is out of range', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(10)).toBe(2); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts index 4a0559e43a5c..3c7bea04f2e4 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts @@ -12,20 +12,31 @@ interface GroupRowState { isExpanded?: boolean; isContinuation?: boolean; isContinuationOnNextPage?: boolean; + values?: unknown[]; + cells?: ProcessedItem['cells']; } const groupRow = ({ isExpanded = true, isContinuation = false, isContinuationOnNextPage = false, + values = [], + cells, }: GroupRowState = {}): ProcessedItem => ({ rowType: 'group', key: [1], data: { isContinuation, isContinuationOnNextPage }, - values: [], + values, isExpanded, + cells, }); +const renderedCells: ProcessedItem['cells'] = [ + { column: { type: 'groupExpand' } }, + {}, + { column: { dataField: 'name' } }, +]; + const dataRow = (partial: Partial = {}): ProcessedItem => ({ rowType: 'data', key: 1, @@ -46,12 +57,13 @@ describe('Grouping data controller row changes', () => { it('should report a group row when isExpanded changed', async () => { const change = await refreshRow( - groupRow({ isExpanded: true }), + groupRow({ isExpanded: true, cells: renderedCells }), groupRow({ isExpanded: false }), ); expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group row when isContinuation changed', async () => { @@ -68,6 +80,26 @@ describe('Grouping data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }); + it('should diff every group cell but the expand one when values changed', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'], cells: renderedCells }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([[1, 2]]); + }); + + it('should repaint the whole group row when it was never rendered', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'] }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); + it('should not report a data row when isExpanded changed (master detail row)', async () => { const change = await refreshRow( dataRow({ isExpanded: false }), diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts index 440fb56af6d1..769d0aa1590f 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts @@ -3,6 +3,7 @@ import { Deferred, when } from '@js/core/utils/deferred'; import type { Properties } from '@js/ui/data_grid'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { ItemProcessingOptions, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import { countRowsBefore } from '@ts/grids/grid_core/data_controller/utils/row_changes'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { ModuleType, @@ -16,7 +17,7 @@ import type { ChangeRowExpandArgs, GroupItem, ProcessGroupItemsOptions, } from '../types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; export const groupingDataControllerExtender = ( @@ -134,6 +135,25 @@ export const groupingDataControllerExtender = ( return resultItems; } + protected adjustInsertRowIndex(visibleRowIndex: number): number { + const groupRowCount = countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'group'); + + return super.adjustInsertRowIndex(visibleRowIndex) + groupRowCount; + } + + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (oldItem.rowType === 'group' && newItem.rowType === 'group') { + return getGroupColumnIndices(oldItem, newItem); + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + protected isSameRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { if (item1.rowType === 'group' && (!isSameExpandedState(item1, item2) || !isSameContinuationState(item1, item2))) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts index 52a05945ae26..e7e8a71a566e 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts @@ -25,3 +25,19 @@ export function isSameContinuationState(item1: ProcessedItem, item2: ProcessedIt return item1.data?.isContinuation === item2.data?.isContinuation && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; } + +export function getGroupColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, +): number[] | undefined { + const isSameState = isSameExpandedState(oldItem, newItem) + && isSameContinuationState(oldItem, newItem); + + if (!oldItem.cells || !isSameState) { + return undefined; + } + + return oldItem.cells + .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) + .filter((index) => index >= 0); +} diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts index 08455315c79f..fbfd87bbc4ea 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts @@ -55,6 +55,7 @@ describe('Summary data controller row changes', () => { expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group footer when isContinuation changed', async () => { diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts index a32233d8861f..937c110c9ef3 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts @@ -288,6 +288,19 @@ export const summaryDataControllerExtender = ( return super.isSameRowState(item1, item2); } + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (newItem.rowType === DATAGRID_GROUP_FOOTER_ROW_TYPE) { + return undefined; + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + protected _updateItemsCore(change: DataChange): void { const dataSource = this._dataSource; const summaryTotalItems = this.option('summary.totalItems'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts index 67df1f4eda7f..a93f657a28d5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts @@ -1,3 +1,4 @@ +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; import { createDataGrid } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { @@ -10,26 +11,37 @@ declare class ExposedDataController extends DataController { public applyChangesOnly: (change: DataChange) => void; } -const createRefreshChange = (items: ProcessedItem[]): DataChange => ({ +export interface RefreshRowOptions { + gridOptions?: DataGridProperties; + isLiveUpdate?: boolean; +} + +const createRefreshChange = ( + items: ProcessedItem[], + isLiveUpdate?: boolean, +): DataChange => ({ changeType: 'refresh', repaintChangesOnly: true, items, + isLiveUpdate, }); export const refreshRow = async ( oldItem: ProcessedItem, newItem: ProcessedItem, + { gridOptions, isLiveUpdate }: RefreshRowOptions = {}, ): Promise => { const { instance } = await createDataGrid({ dataSource: [], columns: ['name', 'age'], repaintChangesOnly: true, + ...gridOptions, }); const dataController = instance.getController('data') as unknown as ExposedDataController; dataController._items = [oldItem]; - const change = createRefreshChange([newItem]); + const change = createRefreshChange([newItem], isLiveUpdate); dataController.applyChangesOnly(change); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 5b58e6902297..49576bb98d28 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -54,11 +54,9 @@ import { resolvePaginate, syncPaging } from './utils/paging'; import { getRefreshOptions } from './utils/refresh'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, indexRowsByKey, @@ -749,6 +747,16 @@ export class DataController extends modules.Controller { // eslint-disable-next-line @typescript-eslint/no-unused-vars protected getDataIndex(change: DataChange): number { return 0; } + /** + * A store change is indexed by data rows, while an insert index coming from the grid counts + * every visible row. Each module that puts its own countable rows into the stream adds their + * count on top of this one. + * @extended: grouping (DataGrid) + */ + protected adjustInsertRowIndex(visibleRowIndex: number): number { + return countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'data'); + } + /** * @extended: adaptivity, editing, master_detail, virtual_scrolling */ @@ -968,7 +976,8 @@ export class DataController extends modules.Controller { } /** - * @extended: editing_row_based, editing, editing_form_based + * @extended: editing_row_based, editing, editing_form_based, grouping (DataGrid), + * summary (DataGrid) */ protected getChangedColumnIndices( oldItem: ProcessedItem, @@ -976,18 +985,17 @@ export class DataController extends modules.Controller { visibleRowIndex: number, isLiveUpdate?: boolean, ): number[] | undefined { - if (!canDiffColumns(oldItem, newItem)) { + if (oldItem.rowType !== newItem.rowType) { return undefined; } - - switch (newItem.rowType) { - case 'group': - return getGroupColumnIndices(oldItem, newItem); - case 'detail': - return []; - default: - return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); + // The detail type is not owned by a single module: Master-Detail creates these rows, + // while form-based editing reuses the same type for its edit row. Therefore, the check + // remains in the base module for now. + if (newItem.rowType === 'detail') { + return []; } + + return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); } private getChangedColumnIndicesCore( @@ -1150,7 +1158,6 @@ export class DataController extends modules.Controller { } private readonly changingHandler = (e: ChangingEvent): void => { - const rows = this.getVisibleRows(); const dataSource = this.dataSource(); if (!dataSource) { @@ -1159,7 +1166,7 @@ export class DataController extends modules.Controller { e.changes.forEach((change) => { if (change.type === 'insert' && change.index !== undefined && change.index >= 0) { - change.index = getDataRowIndex(rows, change.index); + change.index = this.adjustInsertRowIndex(change.index); } }); }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts index 6a214c39d3a1..3f75575829b7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts @@ -8,16 +8,13 @@ import type { } from '../../types'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, getRowOperation, indexRowsByKey, - isSameGroupRowState, isSameItem, partialUpdateItem, resolveRepaintChangesOnly, @@ -96,84 +93,6 @@ describe('isSameItem', () => { }); }); -describe('isSameGroupRowState', () => { - const groupRow = (partial: Partial): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should return true for the same state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({}))).toBe(true); - }); - - it('should compare the expanded state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({ isExpanded: false }))).toBe(false); - }); - - it('should compare the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: true, isContinuationOnNextPage: false } }), - )).toBe(false); - - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: false, isContinuationOnNextPage: true } }), - )).toBe(false); - }); - - it('should not compare the data beyond the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({ data: { key: 1, isContinuation: false, isContinuationOnNextPage: false } }), - groupRow({ data: { key: 2, isContinuation: false, isContinuationOnNextPage: false } }), - )).toBe(true); - }); -}); - -describe('canDiffColumns', () => { - it('should allow the diff for the rows of the same type', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'data' }))).toBe(true); - }); - - it('should forbid the diff for the rows of different types', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'detail' }))).toBe(false); - }); - - it('should forbid the diff for group footers', () => { - expect(canDiffColumns(row({ rowType: 'groupFooter' }), row({ rowType: 'groupFooter' }))) - .toBe(false); - }); -}); - -describe('getGroupColumnIndices', () => { - const groupRow = (partial: Partial): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should skip the group expand cell', () => { - const oldItem = groupRow({ - cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], - }); - - expect(getGroupColumnIndices(oldItem, groupRow({}))).toEqual([1, 2]); - }); - - it('should return undefined when the old row has no cells', () => { - expect(getGroupColumnIndices(groupRow({}), groupRow({}))).toBeUndefined(); - }); - - it('should return undefined when the group state has changed', () => { - const oldItem = groupRow({ cells: [{}] }); - - expect(getGroupColumnIndices(oldItem, groupRow({ isExpanded: false }))).toBeUndefined(); - }); -}); - describe('getRowKey', () => { it('should tell apart the rows of different types with the same key', () => { expect(getRowKey(row({ key: 1, rowType: 'data' }))) @@ -307,7 +226,7 @@ describe('updateKeptRows', () => { }); }); -describe('getDataRowIndex', () => { +describe('countRowsBefore', () => { const rows = [ row({ rowType: 'data' }), row({ rowType: 'group' }), @@ -315,14 +234,24 @@ describe('getDataRowIndex', () => { row({ rowType: 'data' }), ]; - it('should count the data and group rows before the visible index', () => { - expect(getDataRowIndex(rows, 0)).toBe(0); - expect(getDataRowIndex(rows, 3)).toBe(2); - expect(getDataRowIndex(rows, rows.length)).toBe(3); + it('should count the rows of the requested type before the visible index', () => { + expect(countRowsBefore(rows, 0, 'data')).toBe(0); + expect(countRowsBefore(rows, 3, 'data')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'data')).toBe(2); + }); + + it('should not count the rows of any other type', () => { + expect(countRowsBefore(rows, rows.length, 'group')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'detail')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'groupFooter')).toBe(0); }); it('should count the rows that are there when the index is out of range', () => { - expect(getDataRowIndex(rows, 10)).toBe(3); + expect(countRowsBefore(rows, 10, 'data')).toBe(2); + }); + + it('should return zero for an empty row set', () => { + expect(countRowsBefore([], 3, 'data')).toBe(0); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts index c4ae25369d6e..b713669479e5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts @@ -26,30 +26,6 @@ export function isSameItem( return isSameRowType && (!isDetailRow || isSameEditingState); } -// TODO remove after related checks moved to extenders (duplicated in grouping) -export function isSameGroupRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { - return item1.isExpanded === item2.isExpanded - && item1.data?.isContinuation === item2.data?.isContinuation - && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; -} - -export function canDiffColumns(oldItem: ProcessedItem, newItem: ProcessedItem): boolean { - return oldItem.rowType === newItem.rowType && newItem.rowType !== 'groupFooter'; -} - -export function getGroupColumnIndices( - oldItem: ProcessedItem, - newItem: ProcessedItem, -): number[] | undefined { - if (!oldItem.cells || !isSameGroupRowState(oldItem, newItem)) { - return undefined; - } - - return oldItem.cells - .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) - .filter((index) => index >= 0); -} - /** * Rows of different types may share a key, so the row type is a part of the key * the diff is built on. @@ -122,14 +98,14 @@ export function updateKeptRows( }); } -/** - * A store change is indexed by data rows, while an insert index coming from the - * grid counts every visible row — group rows included. - */ -export function getDataRowIndex(rows: ProcessedItem[], visibleRowIndex: number): number { +export function countRowsBefore( + rows: ProcessedItem[], + visibleRowIndex: number, + rowType: ProcessedItem['rowType'], +): number { const previousRows = rows.slice(0, visibleRowIndex); - return previousRows.filter((row) => row?.rowType === 'data' || row?.rowType === 'group').length; + return previousRows.filter((row) => row?.rowType === rowType).length; } export function getChangedRowIndices( diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts index 77a44b18000c..efddd940e9f5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts @@ -16,6 +16,11 @@ const dataRow = (partial: Partial = {}): ProcessedItem => ({ ...partial, }); +const editFormRow = (partial: Partial = {}): ProcessedItem => dataRow({ + rowType: 'detail', + ...partial, +}); + describe('Editing data controller row changes', () => { beforeEach(beforeTest); afterEach(afterTest); @@ -38,4 +43,15 @@ describe('Editing data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }, ); + + it('should repaint the whole edit form row instead of diffing its columns', async () => { + const change = await refreshRow( + editFormRow({ isEditing: true, values: ['Alex', 15] }), + editFormRow({ isEditing: true, values: ['Bob', 15] }), + { gridOptions: { editing: { mode: 'form' } }, isLiveUpdate: false }, + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); }); From 064edf95c621c2ae3b33a7901f2f7a678a69a6ff Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:23:11 +0000 Subject: [PATCH 06/45] CardView - flaky tests in 26.1 (#35051) --- .../tests/cardView/columnChooser/functional.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts b/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts index 6daade06393f..71f616668eac 100644 --- a/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts +++ b/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts @@ -60,6 +60,7 @@ function testsFactory(testModel: { const cardView = new CardView('#container'); await cardView.apiShowColumnChooser(); + await testModel.assertFirstColumnVisible(t, cardView); await testModel.hideFirstColumn(t, cardView); await testModel.assertFirstColumnHidden(t, cardView); @@ -230,11 +231,13 @@ test('cards should update when column is hidden via column chooser (select mode) await t.expect(initialCaptions).eql(['A', 'B', 'C']); await cardView.apiShowColumnChooser(); + await t.expect(cardView.getColumnChooser().isCheckboxChecked(0)).ok(); await t.click(cardView.getColumnChooser().getCheckbox(0)); const captionsAfterHide = await getCardFieldCaptions(t, cardView, 2); await t.expect(captionsAfterHide).eql(['B', 'C']); + await t.expect(cardView.getColumnChooser().isCheckboxChecked(0)).notOk(); await t.click(cardView.getColumnChooser().getCheckbox(0)); From c4768c3251f20d81c63a4d24ebd378120a82f9c2 Mon Sep 17 00:00:00 2001 From: Alyar Date: Mon, 7 Sep 2026 07:24:08 +0000 Subject: [PATCH 07/45] Grids - DataController: Extract editing state knowledge to extenders (#35042) Co-authored-by: Alyar <> --- .../grids/grid_core/data_controller/data_controller.ts | 10 +--------- .../editing_data_controller.row_changes.test.ts | 10 ++++++++++ .../editing/extenders/editing_data_controller.ts | 5 +++++ .../grids/grid_core/editing/m_editing_utils.ts | 7 +++++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 49576bb98d28..ba98b077e839 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -964,15 +964,7 @@ export class DataController extends modules.Controller { const oldValue = oldRow.values[columnIndex]; const newValue = newRow.values[columnIndex]; - if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) { - return true; - } - - const isCellModified = ( - row: ProcessedItem, - ): boolean => row.modifiedValues?.[columnIndex] !== undefined; - - return isCellModified(oldRow) !== isCellModified(newRow); + return JSON.stringify(oldValue) !== JSON.stringify(newValue); } /** diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts index efddd940e9f5..1184e8adf5fa 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts @@ -54,4 +54,14 @@ describe('Editing data controller row changes', () => { expect(change.rowIndices).toEqual([0]); expect(change.columnIndices).toEqual([undefined]); }); + + it('should report a cell whose modified mark appeared without a new value', async () => { + const change = await refreshRow( + dataRow(), + dataRow({ modified: true, modifiedValues: [undefined, 15] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([[1]]); + }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts index 999590624de4..8b8915a4171a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts @@ -15,6 +15,7 @@ import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import { EDITING_EDITROWKEY_OPTION_NAME } from '../const'; import type { EditingController } from '../m_editing'; +import { isCellModified } from '../m_editing_utils'; export interface EditingDataControllerExtension { _editingController: EditingController; @@ -147,6 +148,10 @@ export const editingDataControllerExtender = ( return true; } + if (isCellModified(oldRow, columnIndex) !== isCellModified(newRow, columnIndex)) { + return true; + } + return super._isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts index ddffd2ed0859..67632b7d5599 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts @@ -1,6 +1,8 @@ import Guid from '@js/core/guid'; import { isObject } from '@js/core/utils/type'; +import type { ProcessedItem } from '../data_controller/types'; + const NEW_ROW_TEMP_KEY_PREFIX = '_DX_KEY_'; const GUID_LENGTH = 36; @@ -80,3 +82,8 @@ export const forEachFormItems = (items, callBack) => { } }); }; + +export const isCellModified = ( + row: ProcessedItem, + columnIndex: number, +): boolean => row.modifiedValues?.[columnIndex] !== undefined; From 0712ad305d6a02a6fd92a9a1fde69dd3927a7185 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov <84278206+Tucchhaa@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:16:41 +0000 Subject: [PATCH 08/45] TreeList - DataSourceAdapter - Extract tree building utils (#35053) Co-authored-by: Eldar Iusupzhanov --- .../m_data_source_adapter.ts | 2 +- .../m_data_source_adapter.ts | 191 ++---- .../tree_list/data_source_adapter/types.ts | 22 + .../utils/__tests__/nodes.test.ts | 637 ++++++++++++++++++ .../data_source_adapter/utils/nodes.ts | 194 ++++++ 5 files changed, 905 insertions(+), 141 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index 4b135fbd8680..2a4db0edfc7c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -673,7 +673,7 @@ export default class DataSourceAdapter extends modules.Controller { /** * @extended: TreeLists's data_source_adapter */ - protected customizeLoadResultHandlerCore(options) { + protected customizeLoadResultHandlerCore(options: LoadOperation): void { if (options.remoteOperations && !options.remoteOperations.paging && Array.isArray(options.data)) { if (options.skip !== undefined) { options.data = options.data.slice(options.skip); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index b76ec5607f45..ae7cc27ff427 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -15,9 +15,15 @@ import type { BeforePushEvent } from '@ts/data/types'; import type { CustomLoadResult } from '@ts/grids/grid_core/data_source_adapter/custom_loader'; import DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import { createDataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/provider'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import treeListCore from '../m_core'; +import type { LoadOperation, NodeByKey, TreeNode } from './types'; +import type { NodesContext } from './utils/nodes'; +import { + convertItemToNode, createNodesByItems, fillNodes, getVisibleNodes, +} from './utils/nodes'; const { queryByOptions } = storeHelper; @@ -67,11 +73,11 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { private _isChildrenLoaded: any; - private _nodeByKey: any; + private _nodeByKey!: NodeByKey; private _isReload: any; - private _rootNode: any; + private _rootNode?: TreeNode; public _isNodesInitializing = false; @@ -139,85 +145,15 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { }); } - private _calculateHasItems(node, options) { - const that = this; - const { parentIds } = options.storeLoadOptions; - let hasItems; - const isFullBranch = isFullBranchFilterMode(that); - - if (that._hasItemsGetter && (parentIds || !options.storeLoadOptions.filter || isFullBranch)) { - hasItems = that._hasItemsGetter(node.data); - } - - if (hasItems === undefined) { - if (!that._isChildrenLoaded[node.key] && options.remoteOperations.filtering && (parentIds || isFullBranch)) { - hasItems = true; - } else if (options.loadOptions.filter && !options.remoteOperations.filtering && isFullBranch) { - hasItems = node.children.length; - } else { - hasItems = node.hasChildren; - } - } - return !!hasItems; - } - - private _fillVisibleItemsByNodes(nodes, options, result) { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].visible) { - result.push(nodes[i]); - } - - if ((this.isRowExpanded(nodes[i].key, options) || !nodes[i].visible) && nodes[i].hasChildren && nodes[i].children.length) { - this._fillVisibleItemsByNodes(nodes[i].children, options, result); - } - } - } - - private _convertItemToNode(item, rootValue, nodeByKey) { - const key = this._keyGetter(item); - let parentId = this._parentIdGetter(item); - - parentId = isDefined(parentId) ? parentId : rootValue; - const parentNode = nodeByKey[parentId] = nodeByKey[parentId] || { key: parentId, children: [] }; - - const node = nodeByKey[key] = nodeByKey[key] || { key, children: [] }; - node.data = item; - node.parent = parentNode; - - return node; - } - - private _createNodesByItems(items, visibleItems) { - const that = this; - const rootValue: any = that.option('rootValue'); - const visibleByKey = {}; - const nodeByKey = that._nodeByKey = {}; - let i; - - if (visibleItems) { - for (i = 0; i < visibleItems.length; i++) { - visibleByKey[this._keyGetter(visibleItems[i])] = true; - } - } - - for (i = 0; i < items.length; i++) { - const node = that._convertItemToNode(items[i], rootValue, nodeByKey); - - if (node.key === undefined) { - return; - } - - node.visible = !visibleItems || !!visibleByKey[node.key]; - if (node.parent) { - node.parent.children.push(node); - } - } - - const rootNode = nodeByKey[rootValue] || { key: rootValue, children: [] }; - - rootNode.level = -1; - - return rootNode; + private _getNodesContext(): NodesContext { + return { + rootValue: this.option('rootValue'), + isFullBranchFilterMode: isFullBranchFilterMode(this), + keyGetter: this._keyGetter, + parentIdGetter: this._parentIdGetter, + hasItemsGetter: this._hasItemsGetter, + isChildrenLoaded: this._isChildrenLoaded, + }; } private _convertDataToPlainStructure(data, parentId?, result?) { @@ -438,11 +374,13 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return d.resolve(data); } - let cachedNodes = keys.map((id) => this.getNodeByKey(id)).filter((node) => node && node.data); + let cachedNodes = keys + .map((id) => this.getNodeByKey(id)) + .filter((node) => node && node.data) as TreeNode[]; if (cachedNodes.length === keys.length) { if (needChildren) { - cachedNodes = cachedNodes.reduce((result, node) => result.concat(node.children), []); + cachedNodes = cachedNodes.reduce((result: TreeNode[], node) => result.concat(node.children), []); } if (cachedNodes.length) { @@ -571,20 +509,20 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { const parentNode = that.getNodeByKey(parentId); if (parentNode) { - const rootValue = that.option('rootValue'); - const node = that._convertItemToNode(change.data, rootValue, that._nodeByKey); + const node = convertItemToNode(change.data, that._nodeByKey, that._getNodesContext()); node.hasChildren = false; - node.level = parentNode.level + 1; + node.level = parentNode.level! + 1; node.visible = true; parentNode.children.push(node); - that._isChildrenLoaded[node.key] = true; + that._isChildrenLoaded[node.key as string] = true; that._setHasItems(parentNode, true); if ((!parentNode.parent || that.isRowExpanded(parentNode.key)) && change.index !== undefined) { + // @ts-expect-error Badly typed items() let index = that.items().indexOf(parentNode) + 1; index += change.index >= 0 ? Math.min(change.index, parentNode.children.length) : parentNode.children.length; @@ -640,61 +578,29 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { } } - private _fillNodes(nodes, options, expandedRowKeys, level?) { - const isFullBranch = isFullBranchFilterMode(this); - - level = level || 0; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - let needToExpand = false; - - // node.hasChildren = false; - this._fillNodes(nodes[i].children, options, expandedRowKeys, level + 1); - - node.level = level; - node.hasChildren = this._calculateHasItems(node, options); - - if (node.visible && node.hasChildren) { - if (isFullBranch) { - if (node.children.filter((node) => node.visible).length) { - needToExpand = true; - } else if (node.children.length) { - treeListCore.foreachNodes(node.children, (node) => { - node.visible = true; - }); - } - } else { - needToExpand = true; - } - if (options.expandVisibleNodes && needToExpand) { - expandedRowKeys.push(node.key); - } - } - - if (node.visible || node.hasChildren) { - node.parent.hasChildren = true; - } - } - } - - private _processTreeStructure(options, visibleItems?) { - let { data } = options; + private _processTreeStructure(options: LoadOperation, visibleItems?: RawItemData[]): void { + let data = options.data as RawItemData[]; const { parentIds } = options.storeLoadOptions; - const expandedRowKeys = []; if (parentIds && parentIds.length || this._isReload) { if (options.fullData) { data = options.fullData; - visibleItems = visibleItems || options.data; + visibleItems ??= options.data as RawItemData[]; } - this._rootNode = this._createNodesByItems(data, visibleItems); + const nodesContext = this._getNodesContext(); + const { rootNode, nodeByKey } = createNodesByItems(data, visibleItems, nodesContext); + + this._nodeByKey = nodeByKey; + this._rootNode = rootNode; + if (!this._rootNode) { - // @ts-expect-error - options.data = new Deferred().reject(errors.Error('E1046', this.getKeyExpr())); + // @ts-expect-error badly typed Deferred + options.data = Deferred().reject(errors.Error('E1046', this.getKeyExpr())); return; } - this._fillNodes(this._rootNode.children, options, expandedRowKeys); + + const expandedRowKeys = fillNodes(this._rootNode.children, options, nodesContext); this._isNodesInitializing = true; if (options.collapseVisibleNodes || expandedRowKeys.length) { @@ -705,18 +611,20 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { this._isNodesInitializing = false; } - const resultData = []; - - this._fillVisibleItemsByNodes(this._rootNode.children, options, resultData); + const resultData = getVisibleNodes( + this._rootNode!.children, + (key) => this.isRowExpanded(key, options), + ); + // @ts-expect-error From here on the rows are nodes, not the loaded items the base type describes. options.data = resultData; this._totalItemsCount = resultData.length; } - protected customizeLoadResultHandlerCore(options) { + protected customizeLoadResultHandlerCore(options: LoadOperation): void { const that = this; const { data } = options; - const filter = options.storeLoadOptions.filter || options.loadOptions.filter; + const filter = options.storeLoadOptions.filter || options.loadOptions?.filter; const filterMode = that.option('filterMode'); let visibleItems; const { parentIds } = options.storeLoadOptions; @@ -730,6 +638,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { if (filterMode === 'matchOnly') { visibleItems = data; } + return that._loadParents(data, options).done((data) => { that._loadChildrenIfNeed(data, options).done((data) => { options.data = data; @@ -847,10 +756,12 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return this._isNodesInitializing ? new Deferred().resolve() : this.load(); } - public getNodeByKey(key) { + public getNodeByKey(key): TreeNode | undefined { if (this._nodeByKey) { return this._nodeByKey[key]; } + + return undefined; } private getNodeLeafKeys() { @@ -918,7 +829,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any public forEachNode(nodeCallback?: (node: any) => void) { - let nodes = []; + let nodes: TreeNode[] = []; let callback; if (arguments.length === 1) { @@ -926,7 +837,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { callback = arguments[0]; const rootNode = this.getRootNode(); - nodes = rootNode && rootNode.children || []; + nodes = rootNode?.children ?? []; } else if (arguments.length === 2) { // eslint-disable-next-line prefer-destructuring callback = arguments[1]; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts new file mode 100644 index 000000000000..443ab021fa11 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts @@ -0,0 +1,22 @@ +import type { LoadOperation as BaseLoadOperation, RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; + +export interface LoadOperation extends BaseLoadOperation { + storeLoadOptions: BaseLoadOperation['storeLoadOptions'] & { + parentIds?: unknown[]; + }; + fullData?: RawItemData[]; + collapseVisibleNodes?: boolean; + expandVisibleNodes?: boolean; +} + +export interface TreeNode { + key: unknown; + children: TreeNode[]; + data?: unknown; + parent?: TreeNode; + level?: number; + visible?: boolean; + hasChildren?: boolean; +} + +export type NodeByKey = Record; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts new file mode 100644 index 000000000000..ea1141373c60 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts @@ -0,0 +1,637 @@ +import { describe, expect, it } from '@jest/globals'; + +import type { LoadOperation, NodeByKey, TreeNode } from '../../types'; +import type { NodesContext } from '../nodes'; +import { + convertItemToNode, createNodesByItems, fillNodes, getVisibleNodes, +} from '../nodes'; + +interface Item { + [field: string]: unknown; + id?: unknown; + parentId?: unknown; + hasItems?: unknown; +} + +const ROOT = 0; + +function createContext(overrides?: Partial): NodesContext { + return { + rootValue: ROOT, + isFullBranchFilterMode: false, + keyGetter: (data): unknown => (data as Item).id, + parentIdGetter: (data): unknown => (data as Item).parentId, + hasItemsGetter: undefined, + isChildrenLoaded: {}, + ...overrides, + }; +} + +function createLoadOptions(options: { + parentIds?: unknown[]; + storeFilter?: unknown; + loadFilter?: unknown; + remoteFiltering?: boolean; + expandVisibleNodes?: boolean; +} = {}): LoadOperation { + return { + storeLoadOptions: { + parentIds: options.parentIds, + filter: options.storeFilter, + }, + loadOptions: { + filter: options.loadFilter, + }, + remoteOperations: { + filtering: options.remoteFiltering, + }, + expandVisibleNodes: options.expandVisibleNodes, + }; +} + +function buildTree(items: Item[], visibleKeys?: unknown[]): { root: TreeNode; nodes: NodeByKey } { + const visibleItems = visibleKeys && items.filter((item) => visibleKeys.includes(item.id)); + const { rootNode, nodeByKey } = createNodesByItems(items, visibleItems, createContext()); + + return { root: rootNode as TreeNode, nodes: nodeByKey }; +} + +describe('convertItemToNode', () => { + it('wires the node to a placeholder parent and registers both by key', () => { + const nodeByKey = {}; + const item = { id: 2, parentId: 1 }; + + const node = convertItemToNode(item, nodeByKey, createContext()); + + expect(node).toMatchObject({ key: 2, data: item, children: [] }); + expect(node.parent).toEqual({ key: 1, children: [] }); + expect(nodeByKey).toEqual({ 1: node.parent, 2: node }); + }); + + const rootFallbackCases: [string, unknown][] = [ + ['undefined', undefined], + ['null', null], + ]; + + it.each(rootFallbackCases)('attaches the item to rootValue when its parent id is %s', (_, parentId) => { + const node = convertItemToNode({ id: 1, parentId }, {}, createContext()); + + expect(node.parent?.key).toBe(ROOT); + }); + + it('reuses an already registered node, keeping the children it accumulated', () => { + const nodeByKey = {}; + const context = createContext(); + + // The child is converted first, so node `1` only exists as a placeholder parent. + const child = convertItemToNode({ id: 2, parentId: 1 }, nodeByKey, context); + child.parent?.children.push(child); + + const parentItem = { id: 1, parentId: ROOT }; + const parent = convertItemToNode(parentItem, nodeByKey, context); + + expect(parent).toBe(child.parent); + expect(parent.data).toBe(parentItem); + expect(parent.children).toEqual([child]); + }); + + it('reuses an already registered parent', () => { + const nodeByKey = {}; + const context = createContext(); + + const first = convertItemToNode({ id: 2, parentId: 1 }, nodeByKey, context); + const second = convertItemToNode({ id: 3, parentId: 1 }, nodeByKey, context); + + expect(second.parent).toBe(first.parent); + }); +}); + +describe('createNodesByItems', () => { + it('builds a tree and indexes every node by key', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 1 }, + ]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.key).toBe(ROOT); + expect(rootNode?.level).toBe(-1); + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + expect(rootNode?.children[0].children.map((node) => node.key)).toEqual([2, 3]); + expect(Object.keys(nodeByKey)).toEqual(['0', '1', '2', '3']); + }); + + it('does not depend on item order — a child may precede its parent', () => { + const items = [ + { id: 2, parentId: 1 }, + { id: 1, parentId: ROOT }, + ]; + + const { rootNode } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + expect(rootNode?.children[0].children.map((node) => node.key)).toEqual([2]); + expect(rootNode?.children[0].data).toBe(items[1]); + }); + + it('leaves an orphaned branch out of the root, but keeps it indexed', () => { + const items = [{ id: 2, parentId: 'missing' }]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.children).toEqual([]); + expect(nodeByKey.missing.children.map((node) => node.key)).toEqual([2]); + }); + + const rootValueCases: [string, unknown][] = [ + ['null', null], + ['a string', 'root'], + ]; + + it.each(rootValueCases)('supports %s as rootValue', (_, rootValue) => { + const items = [{ id: 1, parentId: rootValue }]; + + const { rootNode } = createNodesByItems(items, undefined, createContext({ rootValue })); + + expect(rootNode?.key).toBe(rootValue); + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + }); + + it('returns a synthetic root when there are no items', () => { + const { rootNode, nodeByKey } = createNodesByItems([], undefined, createContext()); + + expect(rootNode).toEqual({ key: ROOT, children: [], level: -1 }); + expect(nodeByKey).toEqual({}); + }); + + it('marks every node visible when visibleItems is not passed', () => { + const items = [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }]; + + const { nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(nodeByKey[1].visible).toBe(true); + expect(nodeByKey[2].visible).toBe(true); + }); + + it('marks only the nodes listed in visibleItems visible', () => { + const items = [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }]; + + const { nodeByKey } = createNodesByItems(items, [items[1]], createContext()); + + expect(nodeByKey[1].visible).toBe(false); + expect(nodeByKey[2].visible).toBe(true); + }); + + it('hides every node when visibleItems is an empty array', () => { + const items = [{ id: 1, parentId: ROOT }]; + + const { nodeByKey } = createNodesByItems(items, [], createContext()); + + expect(nodeByKey[1].visible).toBe(false); + }); + + it('bails out without a root node when an item has no key (E1046)', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: undefined, parentId: 1 }, + { id: 3, parentId: 1 }, + ]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode).toBeUndefined(); + // The adapter keeps this partially filled map, so the nodes seen so far stay indexed. + expect(nodeByKey[1]).toMatchObject({ key: 1 }); + expect(nodeByKey[3]).toBeUndefined(); + }); +}); + +describe('fillNodes', () => { + it('assigns the level of every node depth first', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + { id: 4, parentId: ROOT }, + ]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect([1, 2, 3, 4].map((key) => nodes[key].level)).toEqual([0, 1, 2, 0]); + }); + + it('returns no keys for an empty node list', () => { + expect(fillNodes([], createLoadOptions(), createContext())).toEqual([]); + }); + + describe('hasChildren propagation', () => { + it('propagates up from the visible leaves', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ], [3]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect([1, 2, 3].map((key) => nodes[key].hasChildren)).toEqual([true, true, false]); + expect(root.hasChildren).toBe(true); + }); + + it('stays false on a branch of hidden leaves', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ], []); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect(nodes[1].hasChildren).toBe(false); + expect(root.hasChildren).toBeUndefined(); + }); + + it('is overridden by hasItemsExpr on the node itself', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: false }, + { id: 2, parentId: 1 }, + ], [2]); + + fillNodes(root.children, createLoadOptions(), context); + + expect(nodes[1].hasChildren).toBe(false); + }); + }); + + describe('hasChildren of one node', () => { + interface Fixture { + items: Item[]; + visibleKeys: unknown[]; + } + + const leaf: Fixture = { + items: [{ id: 1, parentId: ROOT }], + visibleKeys: [], + }; + + const withVisibleChild: Fixture = { + items: [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }], + visibleKeys: [2], + }; + + const withHiddenChild: Fixture = { + items: [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }], + visibleKeys: [], + }; + + function calculateHasChildren( + fixture: Fixture, + options: LoadOperation, + context: NodesContext, + ): boolean | undefined { + const { root, nodes } = buildTree(fixture.items, fixture.visibleKeys); + + fillNodes(root.children, options, context); + + return nodes[1].hasChildren; + } + + describe('hasItemsExpr', () => { + const getterCases: [string, unknown, boolean][] = [ + ['true', true, true], + ['false', false, false], + ]; + + it.each(getterCases)('follows the getter when it returns %s', (_, hasItems, expected) => { + const context = createContext({ hasItemsGetter: () => hasItems }); + + // Shaped so the fallback would answer the opposite: the getter has to win. + const fixture = expected ? leaf : withVisibleChild; + + expect(calculateHasChildren(fixture, createLoadOptions(), context)).toBe(expected); + }); + + it('falls back to the children when the getter returns undefined', () => { + const context = createContext({ hasItemsGetter: () => undefined }); + + expect(calculateHasChildren(withVisibleChild, createLoadOptions(), context)).toBe(true); + }); + + it('is ignored while a store filter is applied', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + + it('is used under a store filter when parentIds are requested', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1], parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + // Pins current behaviour: an empty parentIds array is truthy here, while + // _processTreeStructure treats it as "no parent ids requested". + it('is used under a store filter when parentIds is an empty array', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1], parentIds: [] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + it('is used under a store filter in fullBranch mode', () => { + const context = createContext({ + hasItemsGetter: () => true, + isFullBranchFilterMode: true, + }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + }); + + describe('children not loaded yet', () => { + it('assumes items exist under remote filtering when parentIds are requested', () => { + const options = createLoadOptions({ remoteFiltering: true, parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, createContext())).toBe(true); + }); + + it('assumes items exist under remote filtering in fullBranch mode', () => { + const context = createContext({ isFullBranchFilterMode: true }); + const options = createLoadOptions({ remoteFiltering: true }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + it('assumes nothing once the children are loaded', () => { + const context = createContext({ isChildrenLoaded: { 1: true } }); + const options = createLoadOptions({ remoteFiltering: true, parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + + it('assumes nothing without remote filtering', () => { + const options = createLoadOptions({ parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, createContext())).toBe(false); + }); + }); + + describe('local filtering in fullBranch mode', () => { + const options = createLoadOptions({ loadFilter: ['id', '=', 1] }); + const context = createContext({ isFullBranchFilterMode: true }); + + it('reports items for any child that survived the filter, visible or not', () => { + expect(calculateHasChildren(withHiddenChild, options, context)).toBe(true); + }); + + it('reports no items when every child was filtered out', () => { + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + }); + + describe('fallback', () => { + it('reports items once a descendant propagated them', () => { + expect(calculateHasChildren(withVisibleChild, createLoadOptions(), createContext())) + .toBe(true); + }); + + it('reports none for a node nothing propagated into', () => { + expect(calculateHasChildren(leaf, createLoadOptions(), createContext())).toBe(false); + }); + }); + }); + + describe('expandedRowKeys', () => { + it('collects the keys to expand deepest first', () => { + const { root } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(expandedRowKeys).toEqual([2, 1]); + }); + + it('collects nothing when expandVisibleNodes is not set', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ]); + const expandedRowKeys = fillNodes(root.children, createLoadOptions(), createContext()); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[1].hasChildren).toBe(true); + }); + + it('skips a node that is hidden itself', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ], [2]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(nodes[1].hasChildren).toBe(true); + expect(expandedRowKeys).toEqual([]); + }); + + it('skips a node without children', () => { + const { root } = buildTree([{ id: 1, parentId: ROOT }]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(expandedRowKeys).toEqual([]); + }); + }); + + describe('fullBranch mode', () => { + const context = createContext({ + isFullBranchFilterMode: true, + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const options = createLoadOptions({ expandVisibleNodes: true }); + + it('expands a node that kept at least one visible child', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 1 }, + ], [1, 2]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([1]); + expect(nodes[3].visible).toBe(false); + }); + + it('reveals the branch instead of expanding it when every child was filtered out', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + ], [1]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[2].visible).toBe(true); + }); + + it('stops revealing a branch at a node that reports no items of its own', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ], [1]); + + fillNodes(root.children, options, context); + + // foreachNodes only descends into nodes whose hasChildren is already truthy, + // and node 2 reports none once its own children were filtered out. + expect(nodes[2].visible).toBe(true); + expect(nodes[3].visible).toBe(false); + }); + + it('keeps revealing a branch through a node that reports items', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1, hasItems: true }, + { id: 3, parentId: 2 }, + ], [1]); + + fillNodes(root.children, options, context); + + expect(nodes[3].visible).toBe(true); + }); + + it('handles a node that reports items but has no loaded children', () => { + const { root, nodes } = buildTree([{ id: 1, parentId: ROOT, hasItems: true }], [1]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[1].hasChildren).toBe(true); + }); + + it('expands a visible node with children when the mode is off', () => { + const plainContext = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + ], [1]); + const expandedRowKeys = fillNodes(root.children, options, plainContext); + + expect(expandedRowKeys).toEqual([1]); + expect(nodes[2].visible).toBe(false); + }); + }); +}); + +describe('getVisibleNodes', () => { + const noneExpanded = (): boolean => false; + const allExpanded = (): boolean => true; + + // Mirrors the adapter: the tree is filled first, then flattened into rows. + function getVisibleKeys( + items: Item[], + visibleKeys: unknown[] | undefined, + isRowExpanded: (key: unknown) => boolean, + context = createContext(), + ): unknown[] { + const { root } = buildTree(items, visibleKeys); + + fillNodes(root.children, createLoadOptions(), context); + + return getVisibleNodes(root.children, isRowExpanded).map((node) => node.key); + } + + const parentAndChild: Item[] = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ]; + + it('returns the node objects themselves', () => { + const { root, nodes } = buildTree([{ id: 1, parentId: ROOT }]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect(getVisibleNodes(root.children, noneExpanded)[0]).toBe(nodes[1]); + }); + + it('returns nothing for an empty node list', () => { + expect(getVisibleNodes([], noneExpanded)).toEqual([]); + }); + + it('stops at a collapsed node', () => { + expect(getVisibleKeys(parentAndChild, undefined, noneExpanded)).toEqual([1]); + }); + + it('includes the children of an expanded node', () => { + expect(getVisibleKeys(parentAndChild, undefined, allExpanded)).toEqual([1, 2]); + }); + + it('returns the nodes in tree order', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + { id: 4, parentId: ROOT }, + ]; + + expect(getVisibleKeys(items, undefined, allExpanded)).toEqual([1, 2, 3, 4]); + }); + + // How matchOnly/fullBranch surface a match whose ancestors were filtered out. + it('descends through a hidden node without listing it', () => { + expect(getVisibleKeys(parentAndChild, [2], noneExpanded)).toEqual([2]); + }); + + it('does not descend into a node that reports no children', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const items = [ + { id: 1, parentId: ROOT, hasItems: false }, + { id: 2, parentId: 1 }, + ]; + + expect(getVisibleKeys(items, undefined, allExpanded, context)).toEqual([1]); + }); + + it('handles a node that reports children it has not loaded', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const items = [{ id: 1, parentId: ROOT, hasItems: true }]; + + expect(getVisibleKeys(items, undefined, allExpanded, context)).toEqual([1]); + }); + + it('asks about expansion using each node key', () => { + const asked: unknown[] = []; + + getVisibleKeys(parentAndChild, undefined, (key) => { + asked.push(key); + + return true; + }); + + expect(asked).toEqual([1, 2]); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts new file mode 100644 index 000000000000..ffbb7bd84f30 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts @@ -0,0 +1,194 @@ +import { isDefined } from '@js/core/utils/type'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; + +import treeListCore from '../../m_core'; +import type { LoadOperation, NodeByKey, TreeNode } from '../types'; + +export interface NodesContext { + rootValue: unknown; + isFullBranchFilterMode: boolean; + keyGetter: (data: unknown) => unknown; + parentIdGetter: (data: unknown) => unknown; + hasItemsGetter?: (data: unknown) => unknown; + isChildrenLoaded: Record; +} + +export type ConvertContext = Pick; + +export type FillNodesContext = Pick; + +export function convertItemToNode( + item: unknown, + nodeByKey: NodeByKey, + context: ConvertContext, +): TreeNode { + const key = context.keyGetter(item) as string; + const itemParentId = context.parentIdGetter(item); + const parentId = (isDefined(itemParentId) ? itemParentId : context.rootValue) as string; + + const parentNode = nodeByKey[parentId] ?? { key: parentId, children: [] }; + nodeByKey[parentId] = parentNode; + + const node = nodeByKey[key] ?? { key, children: [] }; + nodeByKey[key] = node; + + node.data = item; + node.parent = parentNode; + + return node; +} + +export function createNodesByItems( + items: RawItemData[], + visibleItems: RawItemData[] | undefined, + context: ConvertContext, +): { rootNode?: TreeNode; nodeByKey: NodeByKey } { + const nodeByKey: NodeByKey = {}; + const visibleByKey: Record = {}; + + visibleItems?.forEach((item) => { + visibleByKey[context.keyGetter(item) as string] = true; + }); + + for (const item of items) { + const node = convertItemToNode(item, nodeByKey, context); + + if (node.key === undefined) { + return { nodeByKey }; + } + + node.visible = !visibleItems || !!visibleByKey[node.key as string]; + if (node.parent) { + node.parent.children.push(node); + } + } + + const rootNode = nodeByKey[context.rootValue as string] ?? { + key: context.rootValue, + children: [], + }; + + rootNode.level = -1; + + return { rootNode, nodeByKey }; +} + +function calculateHasChildren( + node: TreeNode, + options: LoadOperation, + context: FillNodesContext, +): boolean { + const { parentIds } = options.storeLoadOptions; + const isFullBranch = context.isFullBranchFilterMode; + const canUseHasItemsGetter = !!parentIds || !options.storeLoadOptions.filter || isFullBranch; + + const hasItemsFromData = context.hasItemsGetter && canUseHasItemsGetter + ? context.hasItemsGetter(node.data) + : undefined; + + if (hasItemsFromData !== undefined) { + return !!hasItemsFromData; + } + + const isChildrenLoaded = context.isChildrenLoaded[node.key as string]; + + if (!isChildrenLoaded && options.remoteOperations?.filtering && (parentIds || isFullBranch)) { + return true; + } + + if (options.loadOptions?.filter && !options.remoteOperations?.filtering && isFullBranch) { + return !!node.children.length; + } + + return !!node.hasChildren; +} + +// In `fullBranch` mode a node whose children are all filtered out stays collapsed +// and its whole branch becomes visible instead. +function resolveNeedToExpand(node: TreeNode, isFullBranch: boolean): boolean { + if (!isFullBranch) { + return true; + } + + if (node.children.some((child) => child.visible)) { + return true; + } + + if (node.children.length) { + treeListCore.foreachNodes(node.children, (child: TreeNode) => { + child.visible = true; + }); + } + + return false; +} + +function fillNodesCore( + nodes: TreeNode[], + options: LoadOperation, + context: FillNodesContext, + expandedRowKeys: unknown[], + level: number, +): void { + nodes.forEach((node) => { + fillNodesCore(node.children, options, context, expandedRowKeys, level + 1); + + node.level = level; + node.hasChildren = calculateHasChildren(node, options, context); + + if (node.visible && node.hasChildren) { + const needToExpand = resolveNeedToExpand(node, context.isFullBranchFilterMode); + + if (options.expandVisibleNodes && needToExpand) { + expandedRowKeys.push(node.key); + } + } + + if (node.parent && (node.visible || node.hasChildren)) { + node.parent.hasChildren = true; + } + }); +} + +export function fillNodes( + nodes: TreeNode[], + options: LoadOperation, + context: FillNodesContext, +): unknown[] { + const expandedRowKeys: unknown[] = []; + + fillNodesCore(nodes, options, context, expandedRowKeys, 0); + + return expandedRowKeys; +} + +function collectVisibleNodes( + nodes: TreeNode[], + isRowExpanded: (key: unknown) => boolean, + result: TreeNode[], +): void { + nodes.forEach((node) => { + if (node.visible) { + result.push(node); + } + + const shouldDescend = (isRowExpanded(node.key) || !node.visible) + && node.hasChildren + && node.children.length > 0; + + if (shouldDescend) { + collectVisibleNodes(node.children, isRowExpanded, result); + } + }); +} + +export function getVisibleNodes( + nodes: TreeNode[], + isRowExpanded: (key: unknown) => boolean, +): TreeNode[] { + const result: TreeNode[] = []; + + collectVisibleNodes(nodes, isRowExpanded, result); + + return result; +} From 9ad425293dfe5b58b9dc48e186c7fac587821505 Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:45:20 +0000 Subject: [PATCH 09/45] chore(security): override fast-uri and qs in sbom package (GHSA-5jgf-p345-68v8, GHSA-4mjr-xmp4-gh2g) (#35069) --- packages/sbom/pnpm-lock.yaml | 19 ++++++++++--------- packages/sbom/pnpm-workspace.yaml | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/sbom/pnpm-lock.yaml b/packages/sbom/pnpm-lock.yaml index 9c823e841f73..fd286ee42947 100644 --- a/packages/sbom/pnpm-lock.yaml +++ b/packages/sbom/pnpm-lock.yaml @@ -8,7 +8,8 @@ overrides: tar@<7.5.19: ^7.5.19 brace-expansion@<5.0.8: ^5.0.8 body-parser@>=2.0.0 <2.3.0: ^2.3.0 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 + qs@>=2.2.5 <6.16.0: ^6.16.0 undici@>=7.23.0 <7.29.0: ^7.29.0 brace-expansion@>=3.0.0 <5.0.9: ^5.0.9 @@ -422,8 +423,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} finalhandler@1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} @@ -691,8 +692,8 @@ packages: resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} engines: {node: '>=18'} - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} quick-lru@5.1.1: @@ -1157,7 +1158,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -1183,7 +1184,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -1381,7 +1382,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-uri@3.1.5: {} + fast-uri@3.1.7: {} finalhandler@1.1.2: dependencies: @@ -1661,7 +1662,7 @@ snapshots: transitivePeerDependencies: - supports-color - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 diff --git a/packages/sbom/pnpm-workspace.yaml b/packages/sbom/pnpm-workspace.yaml index 9f665ac75ee6..f2a0edddd8fc 100644 --- a/packages/sbom/pnpm-workspace.yaml +++ b/packages/sbom/pnpm-workspace.yaml @@ -8,6 +8,7 @@ overrides: tar@<7.5.19: ^7.5.19 brace-expansion@<5.0.8: ^5.0.8 body-parser@>=2.0.0 <2.3.0: ^2.3.0 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 + qs@>=2.2.5 <6.16.0: ^6.16.0 undici@>=7.23.0 <7.29.0: ^7.29.0 brace-expansion@>=3.0.0 <5.0.9: ^5.0.9 From d860df84a5696fea1bebf60d24acc47fea50ce9c Mon Sep 17 00:00:00 2001 From: Anna Shakhova <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:46:30 +0000 Subject: [PATCH 10/45] Grids: create dataSourceController (#35040) --- .../data_source/data_source_controller.ts | 20 + .../data_source/data_source_module.ts | 9 + .../grids/data_grid/m_data_controller.ts | 31 -- .../grids/data_grid/m_widget_base.ts | 4 +- .../module_not_extended/data_controller.ts | 5 + .../data_controller.data_source.test.ts | 65 --- .../data_controller/data_controller.ts | 101 +--- ...data_source_controller.integration.test.ts | 369 +++++++++++++ .../__tests__/data_source_controller.test.ts | 493 ++++++++++++++++++ .../data_source/data_source_controller.ts | 105 ++++ .../js/__internal/grids/grid_core/m_types.ts | 1 + .../data_controller/m_data_controller.ts | 6 - .../data_source/data_source_controller.ts | 9 + .../data_source/data_source_module.ts | 9 + .../grids/tree_list/m_widget_base.ts | 2 + .../testing/helpers/gridBaseMocks.js | 6 +- .../dataController.tests.js | 3 +- .../dataSource.tests.js | 2 +- .../dataController.tests.js | 2 +- 19 files changed, 1058 insertions(+), 184 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts delete mode 100644 packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts delete mode 100644 packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts diff --git a/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts new file mode 100644 index 000000000000..c978b138692e --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts @@ -0,0 +1,20 @@ +import errors from '@js/ui/widget/ui.errors'; +import dataSourceAdapterProvider from '@ts/grids/data_grid/m_data_source_adapter'; +import { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; + +export class DataGridDataSourceController extends DataSourceController { + protected getAdapterProvider(): DataSourceAdapterProvider { + return dataSourceAdapterProvider; + } + + protected getSpecificDataSourceOption(): unknown { + const dataSource = this.option('dataSource'); + + if (dataSource && !Array.isArray(dataSource) && this.option('keyExpr')) { + errors.log('W1011'); + } + + return super.getSpecificDataSourceOption(); + } +} diff --git a/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts new file mode 100644 index 000000000000..87d5621798c0 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts @@ -0,0 +1,9 @@ +import gridCore from '@ts/grids/data_grid/m_core'; + +import { DataGridDataSourceController } from './data_source_controller'; + +gridCore.registerModule('dataSource', { + controllers: { + dataSource: DataGridDataSourceController, + }, +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts deleted file mode 100644 index e6632d7df28f..000000000000 --- a/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts +++ /dev/null @@ -1,31 +0,0 @@ -import errors from '@js/ui/widget/ui.errors'; -import { DataController, dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; - -import type { DataSourceAdapterProvider } from '../grid_core/data_source_adapter/types'; -import gridCore from './m_core'; -import dataSourceAdapterProvider from './m_data_source_adapter'; - -class DataGridDataController extends DataController { - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - return dataSourceAdapterProvider; - } - - protected _getSpecificDataSourceOption() { - const dataSource = this.option('dataSource'); - - if (dataSource && !Array.isArray(dataSource) && this.option('keyExpr')) { - errors.log('W1011'); - } - - return super._getSpecificDataSourceOption(); - } -} - -export { DataGridDataController as DataController }; - -gridCore.registerModule('data', { - defaultOptions: dataControllerModule.defaultOptions, - controllers: { - data: DataGridDataController, - }, -}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts index 1710af383abd..546d0b54c089 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts @@ -1,6 +1,7 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; -import './m_data_controller'; +import './data_source/data_source_module'; +import './module_not_extended/data_controller'; import './module_not_extended/sorting'; import './module_not_extended/rows'; import './module_not_extended/context_menu'; @@ -24,6 +25,7 @@ import gridCore from './m_core'; const DATAGRID_DEPRECATED_TEMPLATE_WARNING = 'Specifying grid templates with the jQuery selector name is now deprecated. Use the DOM Node or the jQuery object that references this selector instead.'; gridCore.registerModulesOrder([ + 'dataSource', 'stateStoring', 'columns', 'selection', diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts new file mode 100644 index 000000000000..5965240a4e70 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts @@ -0,0 +1,5 @@ +import { dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; + +import gridCore from '../m_core'; + +gridCore.registerModule('data', dataControllerModule); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts deleted file mode 100644 index 0d64fec2e747..000000000000 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - afterEach, - beforeEach, - describe, - expect, - it, -} from '@jest/globals'; -import DataSource from '@js/data/data_source'; -import type { DataGridInstance } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; -import { - afterTest, - beforeTest, - createDataGrid, - flushAsync, -} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; - -import { DataController } from '../data_controller'; - -declare class ExposedDataController extends DataController { - public isSharedDataSource?: boolean; -} - -const DATA = [ - { id: 1, value: 'a' }, - { id: 2, value: 'b' }, -]; - -const getIsSharedDataSource = (instance: DataGridInstance): boolean | undefined => { - const dataController = instance.getController('data') as unknown as ExposedDataController; - - return dataController.isSharedDataSource; -}; - -describe('DataController data source', () => { - beforeEach(beforeTest); - afterEach(afterTest); - - describe('isSharedDataSource', () => { - it('should be true when a DataSource instance is passed', async () => { - const sharedDataSource = new DataSource({ store: DATA, key: 'id' }); - - const { instance } = await createDataGrid({ dataSource: sharedDataSource }); - - expect(getIsSharedDataSource(instance)).toBe(true); - }); - - it('should be false when a plain array is passed', async () => { - const { instance } = await createDataGrid({ dataSource: DATA }); - - expect(getIsSharedDataSource(instance)).toBe(false); - }); - - it('should reset to false after switching from a shared DataSource to a plain array', async () => { - const sharedDataSource = new DataSource({ store: DATA, key: 'id' }); - - const { instance } = await createDataGrid({ dataSource: sharedDataSource }); - expect(getIsSharedDataSource(instance)).toBe(true); - - instance.option('dataSource', DATA); - await flushAsync(); - - expect(getIsSharedDataSource(instance)).toBe(false); - }); - }); -}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index ba98b077e839..ec04bb2b8892 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -1,11 +1,8 @@ -import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; -import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; import type { Callback } from '@js/core/utils/callbacks'; import { deferRender } from '@js/core/utils/common'; import { logger } from '@js/core/utils/console'; import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred, when } from '@js/core/utils/deferred'; -import { extend } from '@js/core/utils/extend'; import { isDefined } from '@js/core/utils/type'; import type { StoreChange } from '@js/data/store'; import errors from '@js/ui/widget/ui.errors'; @@ -15,9 +12,10 @@ import type Store from '@ts/data/abstract_store'; import type { DataSource } from '@ts/data/data_source/data_source'; import type { ChangingEvent } from '@ts/data/data_source/types'; import type { Column, ColumnsChanges } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { - ChangedEvent, DataSourceAdapterProvider, LoadOperation, OperationTypes, RawItemData, + ChangedEvent, LoadOperation, OperationTypes, RawItemData, } from '@ts/grids/grid_core/data_source_adapter/types'; import { isLocalStore } from '@ts/grids/grid_core/data_source_adapter/utils/store'; import modules from '@ts/grids/grid_core/m_modules'; @@ -70,8 +68,6 @@ import { generateRowValues } from './utils/row_values'; export class DataController extends modules.Controller { public _dataSource?: DataSourceAdapter | null; - protected isSharedDataSource?: boolean; - protected _items!: ProcessedItem[]; private _cachedProcessedItems!: ProcessedItem[] | null; @@ -124,6 +120,8 @@ export class DataController extends modules.Controller { public rowIndicesChanged!: Callback<[RowIndexCorrection]>; + protected dataSourceController!: DataSourceController; + // TODO public controller public _columnsController!: Controllers['columns']; @@ -138,6 +136,7 @@ export class DataController extends modules.Controller { public init(): void { this._items = []; this._cachedProcessedItems = null; + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._isPaging = false; @@ -641,22 +640,6 @@ export class DataController extends modules.Controller { }); } - protected _getSpecificDataSourceOption(): unknown { - const dataSource = this.option('dataSource'); - - if (Array.isArray(dataSource)) { - return { - store: { - type: 'array', - data: dataSource, - key: this.option('keyExpr'), - }, - }; - } - - return dataSource; - } - /** * @extended: state_storing, virtual_scrolling */ @@ -670,7 +653,9 @@ export class DataController extends modules.Controller { protected _initDataSource(): void { const hadDataSource = !!this._dataSource; - const dataSource = this.recreateDataSource(); + this._disposeDataSource(); + + const dataSource = this.dataSourceController.createDataSource(); this._useSortingGroupingFromColumns = true; this._cachedProcessedItems = null; @@ -684,27 +669,6 @@ export class DataController extends modules.Controller { } } - private recreateDataSource(): DataSource | undefined { - const dataSourceOptions = this._getSpecificDataSourceOption(); - - this._disposeDataSource(); - - if (!dataSourceOptions) { - this.isSharedDataSource = false; - return undefined; - } - - if (dataSourceOptions instanceof DataSourceClass) { - this.isSharedDataSource = true; - return dataSourceOptions as unknown as DataSource; - } - - this.isSharedDataSource = false; - return new DataSourceClass( - extend(true, {}, normalizeDataSourceOptions(dataSourceOptions, {})), - ) as unknown as DataSource; - } - /** * @extended: selection, virtual_scrolling */ @@ -1358,18 +1322,6 @@ export class DataController extends modules.Controller { this.dataSourceChanged.fire(); }; - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - throw new Error('Method not implemented.'); - } - - protected _createDataSourceAdapter(dataSource: DataSource): DataSourceAdapter { - const dataSourceAdapterProvider = this._getDataSourceAdapterProvider(); - const dataSourceAdapter = dataSourceAdapterProvider.create(this.component); - - dataSourceAdapter.init(dataSource); - return dataSourceAdapter; - } - private subscribeToDataSource(dataSourceAdapter: DataSourceAdapter): void { dataSourceAdapter.changed.add(this.dataChangedHandlerProxy); dataSourceAdapter.loadingChanged.add(this.loadingChangedHandler); @@ -1388,29 +1340,17 @@ export class DataController extends modules.Controller { dataSourceAdapter.pushed.remove(this.dataPushedHandlerProxy); } - private setDataSource(dataSource: DataSource | null): void { - const oldDataSource = this._dataSource; - - if (!dataSource && oldDataSource) { - oldDataSource.cancelAll(); - this.unsubscribeFromDataSource(oldDataSource); - oldDataSource.dispose(this.isSharedDataSource); - } - - const dataSourceAdapter = dataSource - ? this._createDataSourceAdapter(dataSource) - : null; + private setDataSource(dataSource: DataSource): void { + const dataSourceAdapter = this.dataSourceController.createAdapter(dataSource); this._dataSource = dataSourceAdapter; - if (dataSourceAdapter) { - this._isLoading = !dataSourceAdapter.isLoaded(); - this._needApplyFilter = true; - this._isAllDataTypesDefined = this._columnsController.isAllDataTypesDefined(); + this._isLoading = !dataSourceAdapter.isLoaded(); + this._needApplyFilter = true; + this._isAllDataTypesDefined = this._columnsController.isAllDataTypesDefined(); - this.changed.add(this.fireDataSourceChanged); - this.subscribeToDataSource(dataSourceAdapter); - } + this.changed.add(this.fireDataSourceChanged); + this.subscribeToDataSource(dataSourceAdapter); } /** @@ -1649,7 +1589,16 @@ export class DataController extends modules.Controller { } protected _disposeDataSource(): void { - this.setDataSource(null); + const oldDataSource = this._dataSource; + + if (oldDataSource) { + // Before unsubscribing: cancelling in-flight loads still notifies this controller. + oldDataSource.cancelAll(); + this.unsubscribeFromDataSource(oldDataSource); + } + + this._dataSource = null; + this.dataSourceController.disposeAdapter(); } public dispose(): void { diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts new file mode 100644 index 000000000000..cfae192b7095 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts @@ -0,0 +1,369 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import DataSourceClass from '@js/data/data_source'; +import type { Properties as TreeListProperties } from '@js/ui/tree_list'; +import TreeList from '@js/ui/tree_list'; +import errors from '@js/ui/widget/ui.errors'; +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { Controllers, InternalGrid } from '@ts/grids/grid_core/m_types'; + +import { DataSourceController } from '../data_source_controller'; + +const TREELIST_CONTAINER_ID = 'treeListContainer'; + +const DATA = [ + { id: 1, parentId: 0, value: 'a' }, + { id: 2, parentId: 1, value: 'b' }, +]; + +const OTHER_DATA = [ + { id: 3, parentId: 0, value: 'c' }, +]; + +interface TreeListInstance extends TreeList { + getController: (name: T) => Controllers[T]; +} + +const createTreeList = ( + options: TreeListProperties = {}, +): { $container: dxElementWrapper; instance: TreeListInstance } => { + const $container = $('
') + .attr('id', TREELIST_CONTAINER_ID) + .appendTo(document.body); + + const instance = new TreeList( + $container.get(0) as HTMLDivElement, + { keyExpr: 'id', parentIdExpr: 'parentId', ...options }, + ) as TreeListInstance; + + jest.runAllTimers(); + + return { $container, instance }; +}; + +const disposeTreeList = ($container: dxElementWrapper): void => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (($container as any).dxTreeList('instance') as TreeList | undefined)?.dispose(); + $container.remove(); +}; + +const getControllerNames = (instance: unknown): string[] => Object + .keys((instance as InternalGrid)._controllers); + +describe('dataSource module registration', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('is reachable from DataGrid', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getController('dataSource')).toBeInstanceOf(DataSourceController); + }); + + it('is reachable from TreeList', () => { + const { $container, instance } = createTreeList({ dataSource: DATA }); + + try { + expect(instance.getController('dataSource')).toBeInstanceOf(DataSourceController); + } finally { + disposeTreeList($container); + } + }); + + it('leaves the getDataSource public method on DataController', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getDataSource()).toBe(instance.getController('data').getDataSource()); + }); + + it('sits at the bottom of the controller order', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(getControllerNames(instance)[0]).toBe('dataSource'); + }); +}); + +describe('dataSource controller holds the adapter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('holds the same adapter object as DataController', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const adapter = instance.getController('data')._dataSource; + + expect(adapter).toBeTruthy(); + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(adapter); + }); + + it('follows the rebuilt adapter when the dataSource option changes', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const dataController = instance.getController('data'); + const firstAdapter = dataSourceController.getAdapter(); + + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dataSourceController.getAdapter()).not.toBe(firstAdapter); + expect(dataSourceController.getAdapter()).toBe(dataController._dataSource); + }); + + it('releases the adapter when the dataSource option is cleared', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + instance.option('dataSource', undefined); + await flushAsync(); + + expect(instance.getController('data')._dataSource).toBeNull(); + expect(dataSourceController.hasAdapter()).toBe(false); + expect(dataSourceController.getAdapter()).toBeNull(); + expect(dataSourceController.getDataSource()).toBeNull(); + expect(dataSourceController.store()).toBeUndefined(); + }); + + it('recovers after the dataSource option is set again', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + instance.option('dataSource', undefined); + await flushAsync(); + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + }); + + it('still holds the same adapter after a refresh', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + const refreshed = instance.refresh(); + await flushAsync(); + await refreshed; + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + }); + + it('releases the adapter on dispose', async () => { + const { $container, instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const dataController = instance.getController('data'); + + instance.dispose(); + $container.remove(); + + expect(dataController._dataSource).toBeNull(); + expect(dataSourceController.hasAdapter()).toBe(false); + }); + + it('holds the adapter in TreeList too', () => { + const { $container, instance } = createTreeList({ dataSource: DATA }); + + try { + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + } finally { + disposeTreeList($container); + } + }); +}); + +describe('dataSource controller reads delegate to the adapter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('delegates store', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getController('dataSource').store()) + .toBe(instance.getController('data').store()); + }); + + it('delegates key', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getController('dataSource').key()).toBe('id'); + }); + + it('unwraps one hop for getDataSource', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.getDataSource()) + .toBe(instance.getController('data').getDataSource()); + expect(dataSourceController.getDataSource()) + .not.toBe(dataSourceController.getAdapter()); + }); + + it('delegates remoteOperations instead of falling back to an empty object', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const adapter = dataSourceController.getAdapter(); + + expect(dataSourceController.remoteOperations()).toBe(adapter?.remoteOperations()); + }); + + it('delegates getDataIndexGetter', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(typeof instance.getController('dataSource').getDataIndexGetter()).toBe('function'); + }); +}); + +describe('dataSource controller resolves its own component adapter provider', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('builds a DataGrid adapter in DataGrid', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const adapter = instance.getController('dataSource').getAdapter(); + + expect(adapter).toBeTruthy(); + expect('forEachNode' in (adapter as object)).toBe(false); + }); + + it('builds a TreeList adapter in TreeList', () => { + const { $container, instance } = createTreeList({ dataSource: DATA }); + + try { + const adapter = instance.getController('dataSource').getAdapter(); + + expect(adapter).toBeTruthy(); + expect('forEachNode' in (adapter as object)).toBe(true); + } finally { + disposeTreeList($container); + } + }); +}); + +describe('dataSource controller owns the dataSource option reading', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('warns W1011 in DataGrid when keyExpr is combined with a non-array dataSource', async () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + + try { + await createDataGrid({ dataSource: { store: { type: 'array', data: DATA } }, keyExpr: 'id' }); + + expect(log).toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + } + }); + + it('does not warn W1011 in DataGrid for an array dataSource', async () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + + try { + await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + expect(log).not.toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + } + }); + + it('does not warn W1011 in TreeList, where the override does not apply', () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + const { $container } = createTreeList({ + dataSource: { store: { type: 'array', data: DATA } }, + keyExpr: 'id', + }); + + try { + expect(log).not.toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + disposeTreeList($container); + } + }); + + it('builds a DataSource from the array option and keys it by keyExpr', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + expect(instance.getController('dataSource').key()).toBe('id'); + }); +}); + +describe('dataSource controller owns adapter disposal', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('spares a DataSource the caller still owns when the grid is disposed', async () => { + const shared = new DataSourceClass({ store: DATA, key: 'id' }); + const dispose = jest.spyOn(shared, 'dispose'); + const { $container, instance } = await createDataGrid({ dataSource: shared }); + + instance.dispose(); + // afterTest reads the component off #gridContainer, so a disposed one must not linger. + $container.remove(); + + expect(dispose).not.toHaveBeenCalled(); + + dispose.mockRestore(); + shared.dispose(); + }); + + it('destroys a DataSource it built itself when the grid is disposed', async () => { + const { $container, instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + const built = instance.getController('dataSource').getDataSource(); + + if (!built) { + throw new Error('expected the controller to have built a DataSource'); + } + + const dispose = jest.spyOn(built, 'dispose'); + + instance.dispose(); + $container.remove(); + + expect(dispose).toHaveBeenCalledTimes(1); + + dispose.mockRestore(); + }); + + it('spares a shared DataSource when the dataSource option is replaced', async () => { + const shared = new DataSourceClass({ store: DATA, key: 'id' }); + const dispose = jest.spyOn(shared, 'dispose'); + const { instance } = await createDataGrid({ dataSource: shared }); + + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dispose).not.toHaveBeenCalled(); + + dispose.mockRestore(); + shared.dispose(); + }); + + it('leaves DataController and the controller agreeing that the adapter is gone', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + instance.option('dataSource', undefined); + await flushAsync(); + + expect(instance.getController('dataSource').hasAdapter()).toBe(false); + expect(instance.getController('dataSource').getAdapter()).toBeNull(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts new file mode 100644 index 000000000000..d64d70ec46ab --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts @@ -0,0 +1,493 @@ +import { + describe, + expect, + it, + jest, +} from '@jest/globals'; +import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; +import type Store from '@ts/data/abstract_store'; +import type { StoreKey } from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; +import type { + DataSourceAdapterProvider, RawItemData, RemoteOperationsOptions, +} from '@ts/grids/grid_core/data_source_adapter/types'; +import type { InternalGrid } from '@ts/grids/grid_core/m_types'; + +import { DataSourceController } from '../data_source_controller'; + +interface AdapterStub { + _dataSource: DataSource; + store: jest.Mock<() => Store | undefined>; + key: jest.Mock<() => StoreKey | undefined>; + remoteOperations: jest.Mock<() => RemoteOperationsOptions>; + getDataIndexGetter: jest.Mock<() => (data: RawItemData) => number>; + dispose: jest.Mock<(isShared?: boolean) => void>; + init: jest.Mock<(dataSource: DataSource) => void>; +} + +interface ProviderStub { + nextAdapter: AdapterStub; + create: jest.Mock<(component: InternalGrid) => DataSourceAdapter>; + extend: jest.Mock<() => void>; +} + +const createAdapterStub = (marker: string): AdapterStub => ({ + _dataSource: { marker } as unknown as DataSource, + store: jest.fn(() => ({ marker } as unknown as Store)), + key: jest.fn(() => marker as StoreKey), + remoteOperations: jest.fn(() => ({ filtering: true } as RemoteOperationsOptions)), + getDataIndexGetter: jest.fn(() => (): number => 0), + dispose: jest.fn(), + init: jest.fn(), +}); + +const asAdapter = (stub: AdapterStub): DataSourceAdapter => stub as unknown as DataSourceAdapter; + +class TestDataSourceController extends DataSourceController { + public providerStub!: DataSourceAdapterProvider; + + protected getAdapterProvider(): DataSourceAdapterProvider { + return this.providerStub; + } + + public readSpecificDataSourceOption(): unknown { + return this.getSpecificDataSourceOption(); + } +} + +const createControllerWith = (): { + controller: DataSourceController; + component: InternalGrid; +} => { + const component = { + _optionCache: {}, + _controllers: {}, + option: jest.fn(), + } as unknown as InternalGrid; + + return { controller: new DataSourceController(component), component }; +}; + +const createController = (): DataSourceController => createControllerWith().controller; + +const createProviderStub = (adapter: AdapterStub): ProviderStub => { + const stub: ProviderStub = { + nextAdapter: adapter, + create: jest.fn(() => asAdapter(stub.nextAdapter)), + extend: jest.fn(), + }; + + return stub; +}; + +const asProvider = ( + stub: ProviderStub, +): DataSourceAdapterProvider => stub as unknown as DataSourceAdapterProvider; + +const SOURCE = { marker: 'source' } as unknown as DataSource; + +const withProvider = (adapter: AdapterStub): { + controller: TestDataSourceController; + component: InternalGrid; + provider: ProviderStub; +} => { + const component = { + _optionCache: {}, + _controllers: {}, + option: jest.fn(), + } as unknown as InternalGrid; + const controller = new TestDataSourceController(component); + const provider = createProviderStub(adapter); + + controller.providerStub = asProvider(provider); + + return { controller, component, provider }; +}; + +const withOptions = (options: Record): TestDataSourceController => { + const component = { + _controllers: {}, + option: jest.fn((name?: string) => (name === undefined ? options : options[name])), + } as unknown as InternalGrid; + + return new TestDataSourceController(component); +}; + +// isShared is private and read only by disposal, so that is where it becomes observable. +const flagHandedToAdapter = ( + controller: TestDataSourceController, +): boolean | undefined => { + const probe = createAdapterStub('probe'); + + controller.providerStub = asProvider(createProviderStub(probe)); + controller.createAdapter(SOURCE); + controller.disposeAdapter(); + + return probe.dispose.mock.calls[0]?.[0]; +}; + +const withAdapter = (marker = 'first'): { + controller: TestDataSourceController; + adapter: AdapterStub; + provider: ProviderStub; +} => { + const adapter = createAdapterStub(marker); + const { controller, provider } = withProvider(adapter); + + controller.createAdapter(SOURCE); + + return { controller, adapter, provider }; +}; + +describe('DataSourceController', () => { + describe('with no adapter', () => { + it('has no adapter right after construction, without init()', () => { + const controller = createController(); + + expect(controller.hasAdapter()).toBe(false); + expect(controller.getAdapter()).toBeNull(); + }); + + it('returns null from getDataSource', () => { + expect(createController().getDataSource()).toBeNull(); + }); + + it('returns undefined from store and key', () => { + const controller = createController(); + + expect(controller.store()).toBeUndefined(); + expect(controller.key()).toBeUndefined(); + }); + + it('returns undefined from getDataIndexGetter', () => { + expect(createController().getDataIndexGetter()).toBeUndefined(); + }); + + it('returns an empty object from remoteOperations, so callers can enumerate it', () => { + const controller = createController(); + + expect(controller.remoteOperations()).toEqual({}); + expect(Object.keys(controller.remoteOperations())).toEqual([]); + }); + }); + + describe('with an adapter', () => { + it('reports the adapter as present and hands back the same object', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.hasAdapter()).toBe(true); + expect(controller.getAdapter()).toBe(asAdapter(adapter)); + }); + + it('delegates store to the adapter once per call', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.store()).toBe(adapter.store.mock.results[0]?.value); + expect(adapter.store).toHaveBeenCalledTimes(1); + }); + + it('delegates key to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.key()).toBe('first'); + expect(adapter.key).toHaveBeenCalledTimes(1); + }); + + it('returns the adapter remoteOperations object as-is', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.remoteOperations()).toEqual({ filtering: true }); + expect(adapter.remoteOperations).toHaveBeenCalledTimes(1); + }); + + it('delegates getDataIndexGetter to the adapter', () => { + const { controller, adapter } = withAdapter(); + const getter = controller.getDataIndexGetter(); + + expect(getter).toBe(adapter.getDataIndexGetter.mock.results[0]?.value); + expect(adapter.getDataIndexGetter).toHaveBeenCalledTimes(1); + }); + + it('returns the inner DataSource from getDataSource, not the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.getDataSource()).toBe(adapter._dataSource); + expect(controller.getDataSource()).not.toBe(asAdapter(adapter)); + }); + }); + + describe('replacing the adapter', () => { + it('follows the new adapter after a replacement', () => { + const { controller, adapter: first, provider } = withAdapter(); + const second = createAdapterStub('second'); + + provider.nextAdapter = second; + controller.createAdapter(SOURCE); + + expect(controller.getAdapter()).toBe(asAdapter(second)); + expect(controller.key()).toBe('second'); + expect(controller.getDataSource()).toBe(second._dataSource); + expect(first.key).not.toHaveBeenCalled(); + }); + + it('returns to the absent state after disposeAdapter', () => { + const { controller } = withAdapter(); + + controller.disposeAdapter(); + + expect(controller.hasAdapter()).toBe(false); + expect(controller.getAdapter()).toBeNull(); + expect(controller.getDataSource()).toBeNull(); + expect(controller.store()).toBeUndefined(); + expect(controller.key()).toBeUndefined(); + expect(controller.getDataIndexGetter()).toBeUndefined(); + expect(controller.remoteOperations()).toEqual({}); + }); + }); + + describe('layering', () => { + it('reads no other controller', () => { + const { controller } = withAdapter(); + const getController = jest.spyOn(controller, 'getController'); + + controller.hasAdapter(); + controller.getAdapter(); + controller.getDataSource(); + controller.store(); + controller.key(); + controller.remoteOperations(); + controller.getDataIndexGetter(); + controller.disposeAdapter(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); + + describe('createAdapter', () => { + it('builds the adapter through the provider, passing the component', () => { + const { controller, component, provider } = withProvider(createAdapterStub('built')); + + controller.createAdapter(SOURCE); + + expect(provider.create).toHaveBeenCalledTimes(1); + expect(provider.create).toHaveBeenCalledWith(component); + }); + + it('initialises the adapter with the given data source', () => { + const adapter = createAdapterStub('built'); + + withProvider(adapter).controller.createAdapter(SOURCE); + + expect(adapter.init).toHaveBeenCalledTimes(1); + expect(adapter.init).toHaveBeenCalledWith(SOURCE); + }); + + it('returns the adapter the provider produced', () => { + const adapter = createAdapterStub('built'); + + const result = withProvider(adapter).controller.createAdapter(SOURCE); + + expect(result).toBe(asAdapter(adapter)); + }); + + it('stores the adapter it built', () => { + const adapter = createAdapterStub('built'); + const { controller } = withProvider(adapter); + + controller.createAdapter(SOURCE); + + expect(controller.hasAdapter()).toBe(true); + expect(controller.getAdapter()).toBe(asAdapter(adapter)); + }); + + it('replaces a previously held adapter without disposing it', () => { + const first = createAdapterStub('first'); + const { controller, provider } = withProvider(first); + const second = createAdapterStub('second'); + + controller.createAdapter(SOURCE); + provider.nextAdapter = second; + controller.createAdapter(SOURCE); + + expect(controller.getAdapter()).toBe(asAdapter(second)); + expect(first.dispose).not.toHaveBeenCalled(); + }); + + it('throws on the base class, where no component has supplied a provider', () => { + expect(() => createController().createAdapter(SOURCE)).toThrow('Method not implemented.'); + }); + }); + + describe('getSpecificDataSourceOption', () => { + it('wraps an array option into an array store, keyed by keyExpr', () => { + const data = [{ id: 1 }]; + + const result = withOptions({ dataSource: data, keyExpr: 'id' }) + .readSpecificDataSourceOption(); + + expect(result).toEqual({ store: { type: 'array', data, key: 'id' } }); + }); + + it('keeps the caller array by reference, leaving the copy to createDataSource', () => { + const data = [{ id: 1 }]; + + const result = withOptions({ dataSource: data, keyExpr: 'id' }) + .readSpecificDataSourceOption() as { store: { data: unknown } }; + + expect(result.store.data).toBe(data); + }); + + it('passes a non-array option straight through', () => { + const config = { store: { type: 'odata', url: 'x' } }; + + const result = withOptions({ dataSource: config }).readSpecificDataSourceOption(); + + expect(result).toBe(config); + }); + + it('returns the unset option as-is, so createDataSource can see it is absent', () => { + expect(withOptions({}).readSpecificDataSourceOption()).toBeUndefined(); + }); + + it('does not throw on the base class, unlike getAdapterProvider', () => { + expect(() => withOptions({}).readSpecificDataSourceOption()).not.toThrow(); + }); + }); + + describe('createDataSource', () => { + it('returns undefined when the dataSource option is absent', () => { + const controller = withOptions({}); + + expect(controller.createDataSource()).toBeUndefined(); + }); + + it('leaves the source not-shared when the dataSource option is absent', () => { + const controller = withOptions({}); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + }); + + it('builds a DataSource from a plain array', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()).toBeInstanceOf(DataSourceClass); + }); + + it('keys the built DataSource by keyExpr', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()?.key()).toBe('id'); + }); + + it('marks what it built not-shared, so disposal may destroy it', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + }); + + it('builds a DataSource from a store config', () => { + const controller = withOptions({ dataSource: { store: { type: 'array', data: [] } } }); + + expect(controller.createDataSource()).toBeInstanceOf(DataSourceClass); + }); + + it('builds a fresh DataSource on every call', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()).not.toBe(controller.createDataSource()); + }); + + it('hands back the very DataSource the caller passed, without rebuilding it', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const controller = withOptions({ dataSource: shared }); + + expect(controller.createDataSource()).toBe(shared); + + shared.dispose(); + }); + + it('marks a caller-owned DataSource shared, so disposal spares it', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const controller = withOptions({ dataSource: shared }); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(true); + + shared.dispose(); + }); + + it('clears the shared flag when the option moves from a DataSource to an array', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const options: Record = { dataSource: shared, keyExpr: 'id' }; + const controller = withOptions(options); + + controller.createDataSource(); + options.dataSource = [{ id: 2 }]; + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + + shared.dispose(); + }); + + it('starts out not-shared, before anything has been created', () => { + expect(flagHandedToAdapter(withOptions({}))).toBe(false); + }); + + it('leaves the held adapter alone — creating a source is not creating an adapter', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + controller.createDataSource(); + + expect(controller.hasAdapter()).toBe(false); + }); + + it('reads no other controller', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + const getController = jest.spyOn(controller, 'getController'); + + controller.createDataSource(); + controller.readSpecificDataSourceOption(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); + + describe('disposeAdapter', () => { + it('disposes the adapter it holds', () => { + const { controller, adapter } = withAdapter(); + + controller.disposeAdapter(); + + expect(adapter.dispose).toHaveBeenCalledTimes(1); + }); + + it('does nothing when there is no adapter to dispose', () => { + expect(() => createController().disposeAdapter()).not.toThrow(); + }); + + it('disposes only once across repeated calls', () => { + const { controller, adapter } = withAdapter(); + + controller.disposeAdapter(); + controller.disposeAdapter(); + + expect(adapter.dispose).toHaveBeenCalledTimes(1); + }); + + it('reads no other controller', () => { + const { controller } = withAdapter(); + const getController = jest.spyOn(controller, 'getController'); + + controller.disposeAdapter(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts new file mode 100644 index 000000000000..7a70ce031ba9 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts @@ -0,0 +1,105 @@ +import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; +import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; +import { extend } from '@js/core/utils/extend'; +import type Store from '@ts/data/abstract_store'; +import type { StoreKey } from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; +import type { + DataSourceAdapterProvider, RawItemData, RemoteOperationsOptions, +} from '@ts/grids/grid_core/data_source_adapter/types'; +import modules from '@ts/grids/grid_core/m_modules'; + +export class DataSourceController extends modules.Controller { + // Absent before the first dataSource assignment and again after a reset. + private adapter: DataSourceAdapter | null = null; + + private isShared = false; + + /** + * @extended: DataGrid's data_source_controller + */ + protected getSpecificDataSourceOption(): unknown { + const dataSource = this.option('dataSource'); + + if (Array.isArray(dataSource)) { + return { + store: { + type: 'array', + data: dataSource, + key: this.option('keyExpr'), + }, + }; + } + + return dataSource; + } + + public createDataSource(): DataSource | undefined { + const dataSourceOptions = this.getSpecificDataSourceOption(); + + if (!dataSourceOptions) { + this.isShared = false; + return undefined; + } + + if (dataSourceOptions instanceof DataSourceClass) { + this.isShared = true; + return dataSourceOptions as unknown as DataSource; + } + + this.isShared = false; + return new DataSourceClass( + extend(true, {}, normalizeDataSourceOptions(dataSourceOptions, {})), + ) as unknown as DataSource; + } + + public getDataSource(): DataSource | null { + return this.adapter?._dataSource ?? null; + } + + /** + * @extended: DataGrid's and TreeList's data_source_controller + */ + protected getAdapterProvider(): DataSourceAdapterProvider { + throw new Error('Method not implemented.'); + } + + public createAdapter(dataSource: DataSource): DataSourceAdapter { + const adapter = this.getAdapterProvider().create(this.component); + + adapter.init(dataSource); + this.adapter = adapter; + + return adapter; + } + + public hasAdapter(): boolean { + return this.adapter !== null; + } + + public getAdapter(): DataSourceAdapter | null { + return this.adapter; + } + + public disposeAdapter(): void { + this.adapter?.dispose(this.isShared); + this.adapter = null; + } + + public store(): Store | undefined { + return this.adapter?.store(); + } + + public key(): StoreKey | undefined { + return this.adapter?.key(); + } + + public remoteOperations(): RemoteOperationsOptions { + return this.adapter?.remoteOperations() ?? {}; + } + + public getDataIndexGetter(): ((data: RawItemData) => number) | undefined { + return this.adapter?.getDataIndexGetter(); + } +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts index 662de74a6193..d056690762f3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts @@ -203,6 +203,7 @@ export interface Controllers { columnsResizer: import('./columns_resizing_reordering/m_columns_resizing_reordering').ColumnsResizerViewController; contextMenu: import('./context_menu/m_context_menu').ContextMenuController; data: import('./data_controller/data_controller').DataController; + dataSource: import('./data_source/data_source_controller').DataSourceController; draggingHeader: import('./columns_resizing_reordering/m_columns_resizing_reordering').DraggingHeaderViewController; // todo: export is dataGrid-only controller editing: import('./editing/m_editing').EditingController; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index e87ee434b1e8..309f50bd4481 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -2,11 +2,9 @@ import { equalByValue } from '@js/core/utils/common'; import { Deferred } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { DataController, dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; import type { RowKey } from '@ts/grids/grid_core/m_types'; import type { DataSourceAdapterTreeList } from '../data_source_adapter/m_data_source_adapter'; -import dataSourceAdapterProvider from '../data_source_adapter/m_data_source_adapter'; import treeListCore from '../m_core'; export class TreeListDataController extends DataController { @@ -16,10 +14,6 @@ export class TreeListDataController extends DataController { return this._dataSource ?? undefined; } - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - return dataSourceAdapterProvider; - } - private _getNodeLevel(node) { let level = -1; while (node.parent) { diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts new file mode 100644 index 000000000000..70f64d785c20 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts @@ -0,0 +1,9 @@ +import { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; +import dataSourceAdapterProvider from '@ts/grids/tree_list/data_source_adapter/m_data_source_adapter'; + +export class TreeListDataSourceController extends DataSourceController { + protected getAdapterProvider(): DataSourceAdapterProvider { + return dataSourceAdapterProvider; + } +} diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts new file mode 100644 index 000000000000..d3b5072248af --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts @@ -0,0 +1,9 @@ +import treeListCore from '@ts/grids/tree_list/m_core'; + +import { TreeListDataSourceController } from './data_source_controller'; + +treeListCore.registerModule('dataSource', { + controllers: { + dataSource: TreeListDataSourceController, + }, +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts index c933bd272e7e..41ccc3606941 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts @@ -1,5 +1,6 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; +import './data_source/data_source_module'; import './data_controller/m_data_controller'; import './module_not_extended/sorting'; import './rows/m_rows'; @@ -19,6 +20,7 @@ import treeListCore from './m_core'; const TREELIST_CLASS = 'dx-treelist'; treeListCore.registerModulesOrder([ + 'dataSource', 'stateStoring', 'columns', 'selection', diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 05441eea0baa..27c1041868bd 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -994,11 +994,15 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo _subscribeToEvents(rootElement) { } }; + // The dataSource controller is a leaf that the data controller resolves in init(), + // so it is always included rather than listed by every caller. + const ALWAYS_INCLUDED_MODULES = ['dataSource']; + exports['setup' + nameWidget + 'Modules'] = function(that, moduleNames, options) { const modules = []; $.each(gridCore.modules, function() { - if($.inArray(this.name, moduleNames) !== -1) { + if($.inArray(this.name, moduleNames) !== -1 || $.inArray(this.name, ALWAYS_INCLUDED_MODULES) !== -1) { modules.push(this); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js index c2fc39385b72..4b78e7d2de73 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js @@ -383,7 +383,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod dataSource.load(); // act - this.dataController.setDataSource(null); + this.dataController._disposeDataSource(); // assert assert.strictEqual(loadingChangedSpy.callCount, 2, 'loadingChanged call count'); @@ -7746,7 +7746,6 @@ QUnit.module('Filtering', { remoteOperations: { filtering: true } }); - this.dataController.isSharedDataSource = true; this.dataController.setDataSource(this.dataSource); let loadingCount = 0; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js index d83fa3a157c7..eccc30888fb8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js @@ -31,7 +31,7 @@ const createDataSource = function(options) { setupDataGridModules(dataGridStub, ['data', 'columns']); - const dataSourceAdapter = dataGridStub.dataController._createDataSourceAdapter(dataSource); + const dataSourceAdapter = dataGridStub.dataSourceController.createAdapter(dataSource); const origItems = dataSourceAdapter.items; const processItems = function(items) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js index ee7a4c5f2ac6..5f2f56fb3699 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js @@ -707,7 +707,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod QUnit.test('There are no exceptions on getting node when hasn\'t datasource', function(assert) { // arrange - this.dataController.setDataSource(undefined); + this.dataController._disposeDataSource(); // act, assert assert.equal(this.getNodeByKey(1), undefined, 'no exceptions'); From 35f2b92c7bd9d136f67ddfc4dfb0479571924a39 Mon Sep 17 00:00:00 2001 From: Alex Lavrov <36633600+alexslavr@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:44:09 +0000 Subject: [PATCH 11/45] CI: Always run full test matrix in visual-test-demos workflow (#35020) --- .github/workflows/visual-tests-demos.yml | 499 ++--------------------- 1 file changed, 27 insertions(+), 472 deletions(-) diff --git a/.github/workflows/visual-tests-demos.yml b/.github/workflows/visual-tests-demos.yml index 8a5f3107baf4..6bf84c43dd7c 100644 --- a/.github/workflows/visual-tests-demos.yml +++ b/.github/workflows/visual-tests-demos.yml @@ -26,130 +26,13 @@ jobs: id: check run: echo "should-run=${{ env.RUN_TESTS }}" >> $GITHUB_OUTPUT - get-changes: - runs-on: ubuntu-latest - needs: check-should-run - if: github.event_name == 'pull_request' && needs.check-should-run.outputs.should-run == 'true' - name: Get changed demos - timeout-minutes: 5 - outputs: - has-changed-demos: ${{ steps.check-changes.outputs.has-changed-demos }} - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Get changed files - uses: DevExpress/github-actions/get-changed-files@get-changed-files - with: - gh-token: ${{ secrets.GITHUB_TOKEN }} - paths: 'apps/demos/Demos/**/*' - output: apps/demos/changed-files.json - - - name: Display changed files - id: check-changes - run: | - HAS_CHANGED="false" - if [ -f "apps/demos/changed-files.json" ]; then - DEMO_COUNT=$(jq length apps/demos/changed-files.json) - echo "Found changed-files.json" - echo "Content of changed-files.json:" - cat apps/demos/changed-files.json - echo "Number of changed files: $DEMO_COUNT" - if [ "$DEMO_COUNT" -gt 0 ]; then - HAS_CHANGED="true" - fi - else - echo "changed-files.json not found" - fi - echo "has-changed-demos=${HAS_CHANGED}" >> $GITHUB_OUTPUT - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: changed-demos - path: apps/demos/changed-files.json - retention-days: 1 - - determine-framework-tests-scope: - runs-on: ubuntu-slim - name: Determine scope for framework tests - needs: [check-should-run, get-changes] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - (needs.get-changes.result == 'success' || needs.get-changes.result == 'skipped') - outputs: - framework-tests-scope: ${{ steps.determine.outputs.framework-tests-scope }} - - steps: - - name: Determine framework tests scope - id: determine - run: | - if [ "${{ github.event_name }}" != "pull_request" ] || [ "${{ contains(github.event.pull_request.labels.*.name, 'force all tests') }}" = "true" ]; then - echo "Framework tests scope: all demos" - echo "framework-tests-scope=all" >> $GITHUB_OUTPUT - elif [ "${{ needs.get-changes.outputs.has-changed-demos }}" = "true" ]; then - echo "Framework tests scope: changed demos" - echo "framework-tests-scope=changed" >> $GITHUB_OUTPUT - else - echo "Framework tests NOT needed" - echo "framework-tests-scope=none" >> $GITHUB_OUTPUT - fi - - determine-jquery-test-matrix: - runs-on: ubuntu-slim - name: Determine jQuery test matrix - needs: [check-should-run, determine-framework-tests-scope] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' - outputs: - matrix: ${{ steps.matrix.outputs.matrix }} - - steps: - - name: Build matrix - id: matrix - run: | - base_matrix=$(cat <<'JSON' - [ - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(1/3)" }, - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(2/3)" }, - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(3/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(1/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(2/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(3/3)" }, - { "STRATEGY": "accessibility", "THEME": "material.blue.light", "CONSTEL": "jquery" }, - { "STRATEGY": "accessibility", "THEME": "fluent.blue.light", "CONSTEL": "jquery" } - ] - JSON - ) - - dark_accessibility_matrix=$(cat <<'JSON' - [ - { "STRATEGY": "accessibility", "THEME": "material.blue.dark", "CONSTEL": "jquery" }, - { "STRATEGY": "accessibility", "THEME": "fluent.blue.dark", "CONSTEL": "jquery" } - ] - JSON - ) - - matrix=$(jq -c -n \ - --argjson base "$base_matrix" \ - --argjson darkAccessibility "$dark_accessibility_matrix" \ - --arg frameworkTestsScope "${{ needs.determine-framework-tests-scope.outputs.framework-tests-scope }}" \ - '{ include: ($base + (if $frameworkTestsScope != "none" then $darkAccessibility else [] end)) }') - - echo "matrix=$matrix" >> $GITHUB_OUTPUT - build-devextreme: runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} name: Build DevExtreme - needs: [check-should-run, determine-framework-tests-scope] + needs: [check-should-run] if: | !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' + needs.check-should-run.outputs.should-run == 'true' env: NODE_OPTIONS: --max-old-space-size=8192 timeout-minutes: 30 @@ -228,15 +111,7 @@ jobs: ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}- ${{ runner.os }}-nx-v2- - - name: DevExtreme - Build - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'none' - shell: bash - run: | - pnpm exec nx build devextreme-scss - pnpm exec nx build devextreme -c testing - - name: DevExtreme - Build-all - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' run: pnpm nx all:build-testing workflows - name: Zip artifacts (for jQuery tests) @@ -252,7 +127,6 @@ jobs: retention-days: 1 - name: Move packages - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' run: | mv ./packages/devextreme/artifacts/npm/devextreme/*.tgz ./devextreme-installer.tgz mv ./packages/devextreme/artifacts/npm/devextreme-dist/*.tgz ./devextreme-dist-installer.tgz @@ -261,7 +135,6 @@ jobs: mv ./packages/devextreme-vue/npm/*.tgz ./devextreme-vue-installer.tgz - name: Copy build artifacts - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' uses: actions/upload-artifact@v7 with: name: devextreme-sources @@ -276,12 +149,10 @@ jobs: name: Build vendor bundles (React, Vue) runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 15 - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' env: NODE_OPTIONS: --max-old-space-size=8192 @@ -337,12 +208,10 @@ jobs: name: ${{ matrix.SHARD_TOTAL == 1 && format('Build demos ({0})', matrix.FRAMEWORK) || format('Build demos ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 30 - needs: [check-should-run, determine-framework-tests-scope, build-devextreme, build-vendor-bundles] + needs: [check-should-run, build-devextreme, build-vendor-bundles] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' && needs.build-vendor-bundles.result == 'success' env: @@ -437,12 +306,10 @@ jobs: lint: name: Check TS and lint code base - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' runs-on: ubuntu-latest @@ -452,14 +319,6 @@ jobs: - name: Get sources uses: actions/checkout@v6 - - name: Download artifacts - if: github.event_name == 'pull_request' - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - name: Download devextreme sources uses: actions/download-artifact@v8 with: @@ -512,7 +371,6 @@ jobs: - name: Run lint on all demos id: lint_all - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' continue-on-error: true working-directory: apps/demos env: @@ -520,51 +378,19 @@ jobs: NODE_OPTIONS: --max-old-space-size=8192 run: pnpm exec nx lint - - name: Run lint on changed demos - id: lint_changed - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' - continue-on-error: true - working-directory: apps/demos - env: - DEBUG: 'eslint:cli-engine,stylelint:standalone' - run: | - pnpm exec nx lint-non-demos - - if [ -f "changed-files.json" ]; then - echo "Running lint-demos on changed files" - CHANGED_DEMOS=$(jq -r '.[].filename' changed-files.json \ - | grep '^apps/demos/Demos/' \ - | sed 's|^apps/demos/||' \ - | while read f; do - [ -f "$f" ] && echo "$f" - done \ - | tr '\n' ' ') - if [ ! -z "$CHANGED_DEMOS" ]; then - echo "Changed demo files: $CHANGED_DEMOS" - pnpm run eslint $CHANGED_DEMOS - else - echo "No demo files changed, skipping lint-demos" - fi - else - echo "changed-files.json not found" - pnpm run lint-demos - fi - - name: Check TS and lint results if: always() env: TS_VUE_OUTCOME: ${{ steps.ts_vue.outcome }} TS_REACT_OUTCOME: ${{ steps.ts_react.outcome }} LINT_ALL_OUTCOME: ${{ steps.lint_all.outcome }} - LINT_CHANGED_OUTCOME: ${{ steps.lint_changed.outcome }} run: | failed=0 for check in \ "Demos - Check Vue TS:$TS_VUE_OUTCOME" \ "Demos - Check React TS:$TS_REACT_OUTCOME" \ - "Run lint on all demos:$LINT_ALL_OUTCOME" \ - "Run lint on changed demos:$LINT_CHANGED_OUTCOME"; do + "Run lint on all demos:$LINT_ALL_OUTCOME"; do name="${check%%:*}" outcome="${check#*:}" @@ -576,124 +402,14 @@ jobs: exit "$failed" - check-generated-demos-changed: - name: Check generated demos (changed only) - runs-on: devextreme-shr2 - timeout-minutes: 20 - needs: [check-should-run, get-changes, build-devextreme, determine-framework-tests-scope] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-devextreme.result == 'success' - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Download changed demos - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - - name: Detect changed React TS demos - id: changed-react-demos - working-directory: apps/demos - run: | - if [ ! -f "changed-files.json" ]; then - echo "changed-files.json not found, skipping generated JS demos check" - echo "has-react-demos=false" >> $GITHUB_OUTPUT - exit 0 - fi - - jq -r '.[].filename' changed-files.json \ - | grep '/React/' \ - | grep -E '\.tsx?$' \ - | sed 's|^apps/demos/||' \ - | sed -E 's|/[^/]*\.tsx?$||' \ - | sort \ - | uniq > changed-react-demos.txt || true - - if [ -s changed-react-demos.txt ]; then - echo "Changed React demos:" - cat changed-react-demos.txt - echo "has-react-demos=true" >> $GITHUB_OUTPUT - else - echo "No React demos found in changed files, skipping conversion" - echo "has-react-demos=false" >> $GITHUB_OUTPUT - fi - - - uses: pnpm/action-setup@v6 - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - with: - run_install: false - - - name: Use Node.js - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - name: Download devextreme sources - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - uses: actions/download-artifact@v8 - with: - name: devextreme-sources - - - name: Get pnpm store directory - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - run: pnpm install --frozen-lockfile - - - name: Install tgz - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - working-directory: apps/demos - run: pnpm add --ignore-workspace --allow-build=core-js --allow-build=inferno devextreme-aspnet-data@5.1.0 devextreme-aspnet-data-nojquery@5.1.0 ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz && rm -f pnpm-workspace.yaml pnpm-lock.yaml - - - name: Check generated JS demos - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - working-directory: apps/demos - run: | - echo "Running convert-to-js on changed files only" - xargs -r pnpm run convert-to-js < changed-react-demos.txt - - git add ./Demos -N - - if git diff --exit-code . ':!package.json' ; then - echo "Generated JS demos are up-to-date" - else - echo "Generated JS demos are outdated. Execute 'pnpm run convert-to-js split' and commit changes." - echo "If you see another diff, ensure that extra listed files have LF endings." - exit 1 - fi - check-generated-demos-all: name: Check generated demos runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 20 - needs: [check-should-run, build-devextreme, determine-framework-tests-scope] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && needs.build-devextreme.result == 'success' steps: @@ -751,16 +467,25 @@ jobs: fi testcafe-jquery: - needs: [check-should-run, build-devextreme, determine-framework-tests-scope, determine-jquery-test-matrix] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-jquery-test-matrix.result == 'success' && needs.build-devextreme.result == 'success' strategy: fail-fast: false - matrix: ${{ fromJson(needs.determine-jquery-test-matrix.outputs.matrix) }} + matrix: + include: + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(1/3) } + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(2/3) } + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(3/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(1/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(2/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(3/3) } + - { STRATEGY: accessibility, THEME: material.blue.light, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: fluent.blue.light, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: material.blue.dark, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: fluent.blue.dark, CONSTEL: jquery } env: ACCESSIBILITY_TESTCAFE_REPORT_PATH: "accessibility_testcafe_report" @@ -909,12 +634,10 @@ jobs: if-no-files-found: ignore testcafe-frameworks-all: - needs: [check-should-run, determine-framework-tests-scope, build-demos, build-vendor-bundles] + needs: [check-should-run, build-demos, build-vendor-bundles] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && needs.build-demos.result == 'success' strategy: fail-fast: false @@ -1096,176 +819,10 @@ jobs: path: ${{ github.workspace }}/apps/demos/testing/artifacts/compared-screenshots/**/* if-no-files-found: ignore - testcafe-frameworks-changed: - needs: [check-should-run, determine-framework-tests-scope, build-demos, build-vendor-bundles] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-demos.result == 'success' - strategy: - fail-fast: false - matrix: - CONSTEL: [react, vue, angular] - THEME: ['fluent.blue.light'] - - runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} - name: ${{ matrix.CONSTEL }}-screenshots-${{ matrix.THEME }} - timeout-minutes: 60 - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Setup Chrome - uses: ./.github/actions/setup-chrome - with: - chrome-version: '150.0.7871.181' - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - name: Download devextreme sources - uses: actions/download-artifact@v8 - with: - name: devextreme-sources - - - name: Download changed demos - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - - uses: pnpm/action-setup@v6 - with: - run_install: false - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Install tgz - working-directory: apps/demos - run: pnpm add --ignore-workspace --allow-build=core-js --allow-build=inferno devextreme-aspnet-data@5.1.0 devextreme-aspnet-data-nojquery@5.1.0 ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz && rm -f pnpm-workspace.yaml pnpm-lock.yaml - - - name: Download demo bundles (React) - if: matrix.CONSTEL == 'react' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-React - path: apps/demos/.demo-bundles/react - - - name: Download demo bundles (Vue) - if: matrix.CONSTEL == 'vue' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Vue - path: apps/demos/.demo-bundles/vue - - # build-demos shards Angular 3 ways regardless of how it's tested here. - - name: Download demo bundles (Angular shard 1) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard1 - path: apps/demos/.demo-bundles/angular-shard1 - - - name: Download demo bundles (Angular shard 2) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard2 - path: apps/demos/.demo-bundles/angular-shard2 - - - name: Download demo bundles (Angular shard 3) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard3 - path: apps/demos/.demo-bundles/angular-shard3 - - - name: Download changes artifacts - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - - # No-op for angular demos (no manifest — see build-vendor-bundles.js). - - name: Download vendor bundles - uses: actions/download-artifact@v8 - with: - name: devextreme-vendor-bundles - path: apps/demos/bundles/vendor - - - name: Unpack demo bundles - run: | - shopt -s nullglob - archives=(apps/demos/.demo-bundles/*/demo-bundles.tgz) - if [ ${#archives[@]} -eq 0 ]; then - echo "No demo bundle archives downloaded for constellation '${{ matrix.CONSTEL }}'" >&2 - exit 1 - fi - for archive in "${archives[@]}"; do - tar -xzf "$archive" -C apps/demos - done - rm -rf apps/demos/.demo-bundles - echo "Unpacked $(find apps/demos/Demos -name 'bundle.js' | wc -l) demo bundle(s)" - - - name: Run Web Server - run: | - python -m http.server 8080 & - python -m http.server 8081 & - python -m http.server 8082 & - python -m http.server 8083 & - - - name: Run TestCafe tests - shell: bash - working-directory: apps/demos - env: - CHANGEDFILEINFOSPATH: changed-files.json - BROWSERS: chrome:headless --window-size=1200,800 --disable-gpu --no-sandbox --disable-dev-shm-usage --disable-partial-raster --disable-skia-runtime-opts --run-all-compositor-stages-before-draw --disable-new-content-rendering-timeout --disable-threaded-animation --disable-threaded-scrolling --disable-checker-imaging --disable-image-animation-resync --use-gl=swiftshader --disable-features=PaintHolding --js-flags=--random-seed=2147483647 --font-render-hinting=none --disable-font-subpixel-positioning - # DEBUG: hammerhead:*,testcafe:* - CONCURRENCY: 1 - CONSTEL: ${{ matrix.CONSTEL }} - THEME: ${{ matrix.THEME }} - # DISABLE_DEMO_TEST_SETTINGS: all # Uncomment to ignore all the visualtestrc.json settings - # DISABLE_DEMO_TEST_SETTINGS: ignore # Uncomment to ignore the `ignore` field - # DISABLE_DEMO_TEST_SETTINGS: comparison-options # Uncomment to ignore the `comparison-options` field - CI_ENV: true # The `ignore` field in the visualtestrc.json should be disabled when running test locally - run: pnpm exec nx test-testcafe - - - name: Sanitize job name - if: ${{ failure() }} - run: echo "JOB_NAME=$(echo "${{ matrix.CONSTEL }}-${{ matrix.THEME }}" | tr '/' '-')" >> $GITHUB_ENV - - - name: Copy screenshots artifacts - if: ${{ failure() }} - uses: actions/upload-artifact@v7 - with: - name: screenshots-${{ env.JOB_NAME }} - path: ${{ github.workspace }}/apps/demos/testing/artifacts/compared-screenshots/**/* - if-no-files-found: ignore - merge-artifacts: runs-on: ubuntu-latest - needs: [testcafe-jquery, testcafe-frameworks-all, testcafe-frameworks-changed] - if: ${{ !cancelled() && (needs.testcafe-jquery.result == 'failure' || needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure') }} + needs: [testcafe-jquery, testcafe-frameworks-all] + if: ${{ !cancelled() && (needs.testcafe-jquery.result == 'failure' || needs.testcafe-frameworks-all.result == 'failure') }} steps: - name: Merge jQuery screenshot artifacts @@ -1278,7 +835,7 @@ jobs: delete-merged: true - name: Merge React screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1287,7 +844,7 @@ jobs: delete-merged: true - name: Merge Vue screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1296,7 +853,7 @@ jobs: delete-merged: true - name: Merge Angular screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1386,12 +943,10 @@ jobs: csp-check-frameworks: name: ${{ matrix.SHARD_TOTAL == 1 && format('CSP check ({0})', matrix.FRAMEWORK) || format('CSP check ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' strategy: fail-fast: false From d2dd8ada2cf22dad659b1781091307ff9bd0a646 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Mon, 7 Sep 2026 10:21:41 +0000 Subject: [PATCH 12/45] Overlay: SpeedDialAction has no right offset when a vertical scrollbar appears after initial render (T1334663) (#35062) --- .../button/floatingActionViewport.ts | 40 +++++ .../common/core/animation/m_position.ts | 12 +- .../__tests__/document_size_callbacks.test.ts | 161 ++++++++++++++++++ .../core/utils/document_size_callbacks.ts | 99 +++++++++++ .../js/__internal/ui/overlay/overlay.ts | 16 ++ .../DevExpress.animation/position.tests.js | 100 ++++++++++- .../DevExpress.ui.widgets/overlay.tests.js | 79 +++++++++ 7 files changed, 496 insertions(+), 11 deletions(-) create mode 100644 e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts create mode 100644 packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts create mode 100644 packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts diff --git a/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts b/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts new file mode 100644 index 000000000000..a9eb89df1ac8 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts @@ -0,0 +1,40 @@ +import { ClientFunction, Selector } from 'testcafe'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { appendElementTo } from '../../../helpers/domUtils'; + +const OVERLAY_CONTENT_CLASS = 'dx-overlay-content'; +const FA_MAIN_BUTTON_CLASS = 'dx-fa-button-main'; +const FAB_OFFSET = 16; + +const getViewportClientSize = ClientFunction(() => ({ + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, +})); + +fixture`FloatingAction - visible area` + .page(url(__dirname, '../../container.html')); + +test('FAB should keep its offset from the visible area edges after scrollbars appear (T1334663)', async (t) => { + const fabContent = Selector(`.${FA_MAIN_BUTTON_CLASS} .${OVERLAY_CONTENT_CLASS}`); + const initialSize = await getViewportClientSize(); + + await t + .expect(fabContent.getBoundingClientRectProperty('right')) + .within(initialSize.width - FAB_OFFSET - 1, initialSize.width - FAB_OFFSET + 1) + .expect(fabContent.getBoundingClientRectProperty('bottom')) + .within(initialSize.height - FAB_OFFSET - 1, initialSize.height - FAB_OFFSET + 1); + + await appendElementTo('#container', 'div', 'tall-content', { width: '3000px', height: '3000px' }); + + const sizeWithScrollbars = await getViewportClientSize(); + + await t + .expect(fabContent.getBoundingClientRectProperty('right')) + .within(sizeWithScrollbars.width - FAB_OFFSET - 1, sizeWithScrollbars.width - FAB_OFFSET + 1) + .expect(fabContent.getBoundingClientRectProperty('bottom')) + .within(sizeWithScrollbars.height - FAB_OFFSET - 1, sizeWithScrollbars.height - FAB_OFFSET + 1); +}).before(async () => createWidget('dxSpeedDialAction', { + icon: 'add', + visible: true, +})); diff --git a/packages/devextreme/js/__internal/common/core/animation/m_position.ts b/packages/devextreme/js/__internal/common/core/animation/m_position.ts index 0c4089d10c8d..ce6207053ab0 100644 --- a/packages/devextreme/js/__internal/common/core/animation/m_position.ts +++ b/packages/devextreme/js/__internal/common/core/animation/m_position.ts @@ -2,7 +2,6 @@ import { move, resetPosition } from '@js/common/core/animation/translator'; import devices from '@js/core/devices'; import domAdapter from '@js/core/dom_adapter'; import $ from '@js/core/renderer'; -import browser from '@js/core/utils/browser'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getBoundingRect } from '@js/core/utils/position'; @@ -21,7 +20,6 @@ const horzRe = /left|right/; const vertRe = /top|bottom/; const collisionRe = /fit|flip|none/; const scaleRe = /scale\(.+?\)/; -const IS_SAFARI = browser.safari; const normalizeAlign = function (raw) { const result = { @@ -258,14 +256,18 @@ const calculatePosition = function (what, options) { if (isWindow(of[0])) { h.atLocation = of.scrollLeft(); v.atLocation = of.scrollTop(); - if (devices.real().deviceType === 'phone' && of[0].visualViewport) { + const device = devices.real(); + + if (device.deviceType === 'phone' && of[0].visualViewport) { h.atLocation = Math.max(h.atLocation, of[0].visualViewport.offsetLeft); v.atLocation = Math.max(v.atLocation, of[0].visualViewport.offsetTop); h.atSize = of[0].visualViewport.width; v.atSize = of[0].visualViewport.height; } else { - h.atSize = of[0].innerWidth > of[0].outerWidth ? of[0].innerWidth : getWidth(of); - v.atSize = of[0].innerHeight > of[0].outerHeight || IS_SAFARI ? of[0].innerHeight : getHeight(of); + const isIos = device.platform === 'ios'; + + h.atSize = getWidth(of); + v.atSize = isIos ? of[0].innerHeight : getHeight(of); } } else if (of[0].nodeType === 9) { h.atLocation = 0; diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts new file mode 100644 index 000000000000..2b5f4d05e1cf --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts @@ -0,0 +1,161 @@ +import { + afterEach, describe, expect, it, jest, +} from '@jest/globals'; +import domAdapter from '@js/core/dom_adapter'; +import documentSizeCallbacks from '@ts/core/utils/document_size_callbacks'; + +type DocumentElement = ReturnType; + +interface Environment { + documentElement: { clientWidth: number; clientHeight: number }; + resizeVisualViewport: () => void; + addEventListenerSpy: jest.SpiedFunction; + removeEventListenerSpy: jest.SpiedFunction; +} + +function setup(): Environment { + const documentElement = { clientWidth: 1000, clientHeight: 800 }; + const visualViewport = new EventTarget(); + + jest.spyOn(domAdapter, 'getDocumentElement') + .mockImplementation(() => documentElement as unknown as DocumentElement); + Object.defineProperty(window, 'visualViewport', { value: visualViewport, configurable: true }); + window.innerWidth = 1024; + window.innerHeight = 768; + + return { + documentElement, + resizeVisualViewport: (): void => { visualViewport.dispatchEvent(new Event('resize')); }, + addEventListenerSpy: jest.spyOn(visualViewport, 'addEventListener'), + removeEventListenerSpy: jest.spyOn(visualViewport, 'removeEventListener'), + }; +} + +describe('documentSizeCallbacks', () => { + const addedHandlers: (() => void)[] = []; + + function addHandler(): jest.Mock { + const handler = jest.fn(); + + addedHandlers.push(handler); + documentSizeCallbacks.add(handler); + + return handler; + } + + afterEach(() => { + addedHandlers.splice(0).forEach((handler) => documentSizeCallbacks.remove(handler)); + delete (window as { visualViewport?: VisualViewport }).visualViewport; + jest.restoreAllMocks(); + }); + + it('should listen to the visual viewport resize once, no matter how many handlers are added', () => { + const { addEventListenerSpy } = setup(); + + addHandler(); + addHandler(); + + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy.mock.calls[0][0]).toBe('resize'); + }); + + it('should not call handlers when the document size has not changed', () => { + const { resizeVisualViewport } = setup(); + const handler = addHandler(); + + resizeVisualViewport(); + + expect(handler).not.toHaveBeenCalled(); + }); + + it('should call handlers when a scrollbar changes the client width', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + documentElement.clientWidth = 985; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should call handlers when a scrollbar changes the client height', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + documentElement.clientHeight = 785; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should leave a window resize to the window resize callbacks', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + window.innerWidth = 1224; + documentElement.clientWidth = 1200; + resizeVisualViewport(); + + expect(handler).not.toHaveBeenCalled(); + + documentElement.clientWidth = 1185; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should hold a handler once even when it is added twice', () => { + const { documentElement, resizeVisualViewport, removeEventListenerSpy } = setup(); + const handler = addHandler(); + + documentSizeCallbacks.add(handler); + + documentElement.clientWidth = 985; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + + documentSizeCallbacks.remove(handler); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + }); + + it('should stop listening once the last handler is removed', () => { + const { removeEventListenerSpy } = setup(); + const first = addHandler(); + + addHandler(); + documentSizeCallbacks.remove(first); + + expect(removeEventListenerSpy).not.toHaveBeenCalled(); + + documentSizeCallbacks.remove(addedHandlers[1]); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + }); + + it('should measure the document anew when the first handler is added again', () => { + const { documentElement, resizeVisualViewport } = setup(); + const first = addHandler(); + + documentSizeCallbacks.remove(first); + documentElement.clientWidth = 985; + + const second = addHandler(); + + resizeVisualViewport(); + + expect(second).not.toHaveBeenCalled(); + }); + + it('should do nothing when the browser has no visual viewport', () => { + setup(); + delete (window as { visualViewport?: VisualViewport }).visualViewport; + + const listenSpy = jest.spyOn(domAdapter, 'listen'); + const handler = addHandler(); + + expect(listenSpy).not.toHaveBeenCalled(); + expect(() => documentSizeCallbacks.remove(handler)).not.toThrow(); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts b/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts new file mode 100644 index 000000000000..a9fcb7f7015d --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts @@ -0,0 +1,99 @@ +import domAdapter from '@js/core/dom_adapter'; +import { Callbacks } from '@ts/core/utils/m_callbacks'; +import windowUtils from '@ts/core/utils/m_window'; + +interface Size { + width: number; + height: number; +} + +type DocumentSizeHandler = () => void; + +interface HandlerList { + add: (handler: DocumentSizeHandler) => void; + remove: (handler: DocumentSizeHandler) => void; + has: () => boolean; + fire: () => void; +} + +const callbacks = Callbacks({ unique: true }) as HandlerList; + +let removeListener: (() => void) | null = null; +let previousDocumentSize: Size | null = null; +let previousWindowSize: Size | null = null; + +function getDocumentSize(): Size { + const documentElement = domAdapter.getDocumentElement(); + + return { + width: documentElement.clientWidth, + height: documentElement.clientHeight, + }; +} + +function getWindowSize(): Size { + const window: Window = windowUtils.getWindow(); + + return { + width: window.innerWidth, + height: window.innerHeight, + }; +} + +function isSameSize(previous: Size | null, current: Size): boolean { + return previous?.width === current.width && previous?.height === current.height; +} + +function handleVisualViewportResize(): void { + const documentSize = getDocumentSize(); + const windowSize = getWindowSize(); + const isDocumentSizeChanged = !isSameSize(previousDocumentSize, documentSize); + const isWindowSizeChanged = !isSameSize(previousWindowSize, windowSize); + + previousDocumentSize = documentSize; + previousWindowSize = windowSize; + + if (isDocumentSizeChanged && !isWindowSizeChanged) { + callbacks.fire(); + } +} + +function getVisualViewport(): VisualViewport | null { + if (!windowUtils.hasWindow()) { + return null; + } + + const window: Window = windowUtils.getWindow(); + + return window.visualViewport; +} + +function add(handler: DocumentSizeHandler): void { + const visualViewport = getVisualViewport(); + + if (!visualViewport) { + return; + } + + callbacks.add(handler); + + if (!removeListener) { + previousDocumentSize = getDocumentSize(); + previousWindowSize = getWindowSize(); + removeListener = domAdapter.listen(visualViewport, 'resize', handleVisualViewportResize); + } +} + +function remove(handler: DocumentSizeHandler): void { + callbacks.remove(handler); + + if (removeListener && !callbacks.has()) { + removeListener(); + removeListener = null; + previousDocumentSize = null; + previousWindowSize = null; + } +} + +export const documentSizeCallbacks = { add, remove }; +export default documentSizeCallbacks; diff --git a/packages/devextreme/js/__internal/ui/overlay/overlay.ts b/packages/devextreme/js/__internal/ui/overlay/overlay.ts index 0b4a3a9bc546..e1a18de96da5 100644 --- a/packages/devextreme/js/__internal/ui/overlay/overlay.ts +++ b/packages/devextreme/js/__internal/ui/overlay/overlay.ts @@ -39,6 +39,7 @@ import type { } from '@js/events'; import type { dxOverlayAnimation, Properties } from '@js/ui/overlay'; import uiErrors from '@js/ui/widget/ui.errors'; +import documentSizeCallbacks from '@ts/core/utils/document_size_callbacks'; import domUtils from '@ts/core/utils/m_dom'; import selectors from '@ts/core/utils/m_selectors'; import windowUtils from '@ts/core/utils/m_window'; @@ -262,6 +263,8 @@ class Overlay< _hideTopOverlayHandler!: () => void; + _documentSizeChangedHandler?: () => void; + _hideAnimationProcessing?: boolean; _showAnimationProcessing?: boolean; @@ -1104,6 +1107,19 @@ class Overlay< if (windowUtils.hasWindow()) { this._toggleHideTopOverlayCallback(enabled); this._toggleHideOnParentsScrollSubscription(enabled); + this._toggleDocumentSizeSubscription(enabled); + } + } + + _toggleDocumentSizeSubscription(subscribe: boolean): void { + this._documentSizeChangedHandler ??= (): void => { + this._dimensionChanged(); + }; + + if (subscribe) { + documentSizeCallbacks.add(this._documentSizeChangedHandler); + } else { + documentSizeCallbacks.remove(this._documentSizeChangedHandler); } } diff --git a/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js b/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js index 84f4b67bd58c..d5ee42099d6a 100644 --- a/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import positionUtils from 'common/core/animation/position'; import translator from '__internal/common/core/animation/translatorModule'; import browser from 'core/utils/browser'; +import devices from '__internal/core/m_devices'; import fixtures from '../../helpers/positionFixtures.js'; import { implementationsMap } from 'core/utils/size'; import { getWindow } from 'core/utils/window.js'; @@ -942,23 +943,30 @@ const testCollision = (name, fixtureName, params, expectedHorzDist, expectedVert assert.deepEqual(positionUtils.offset($event), { left: 100, top: 200 }, 'position.offset() is correct'); }); - QUnit.test('position should return window.innerHeight if window.outerHeight < window.innerHeight', function(assert) { + QUnit.test('position should measure the window by its client height whatever window.outerHeight reports', function(assert) { const $what = $('#what').height(300); const initialInnerHeight = window.innerHeight; const initialOuterHeight = window.outerHeight; + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + try { window.innerHeight = 500; - window.outerHeight = 200; - const resultPosition = setupPosition($what, { - of: $(window) - }); + // 0 is what the browser reports before the first frame, a smaller value comes from a zoomed out page + [0, 200].forEach((outerHeight) => { + window.outerHeight = outerHeight; + + const resultPosition = setupPosition($what, { + of: $(window) + }); - assert.roughEqual(resultPosition.v.location, 100, 50, 'vertical location is correct'); + assert.roughEqual(resultPosition.v.location, 350, 50, `vertical location is correct, outerHeight is ${outerHeight}`); + }); } finally { window.innerHeight = initialInnerHeight; window.outerHeight = initialOuterHeight; + heightStub.restore(); } }); @@ -1018,6 +1026,86 @@ const testCollision = (name, fixtureName, params, expectedHorzDist, expectedVert } }); + QUnit.test('position should measure the window by its client height on desktop Safari', function(assert) { + const $what = $('#what').height(300); + const initialInnerHeight = window.innerHeight; + const initialSafari = browser.safari; + + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + const devicesStub = sinon.stub(devices, 'real').returns({ deviceType: 'desktop', platform: 'generic' }); + + try { + window.innerHeight = 500; + browser.safari = true; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.v.location, 350, 50, 'vertical location is correct'); + } finally { + window.innerHeight = initialInnerHeight; + browser.safari = initialSafari; + heightStub.restore(); + devicesStub.restore(); + } + }); + + QUnit.test('position should measure the window by window.innerHeight on iOS whatever the browser is', function(assert) { + const $what = $('#what').height(300); + const initialInnerHeight = window.innerHeight; + const initialSafari = browser.safari; + + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + const devicesStub = sinon.stub(devices, 'real').returns({ deviceType: 'desktop', platform: 'ios' }); + + try { + window.innerHeight = 500; + + [true, false].forEach((isSafari) => { + browser.safari = isSafari; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.v.location, 100, 50, `vertical location is correct, browser.safari is ${isSafari}`); + }); + } finally { + window.innerHeight = initialInnerHeight; + browser.safari = initialSafari; + heightStub.restore(); + devicesStub.restore(); + } + }); + + QUnit.test('position should measure the window by its client width whatever window.outerWidth reports', function(assert) { + const $what = $('#what').width(300); + const initialInnerWidth = window.innerWidth; + const initialOuterWidth = window.outerWidth; + + const widthStub = sinon.stub(implementationsMap, 'getWidth').returns(1000); + + try { + window.innerWidth = 500; + + // 0 is what the browser reports before the first frame, a smaller value comes from a zoomed out page + [0, 200].forEach((outerWidth) => { + window.outerWidth = outerWidth; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.h.location, 350, 50, `horizontal location is correct, outerWidth is ${outerWidth}`); + }); + } finally { + window.innerWidth = initialInnerWidth; + window.outerWidth = initialOuterWidth; + widthStub.restore(); + } + }); + // T664522 QUnit.test('setup should call resetPosition with finishTransition argument', function(assert) { const resetPositionStub = sinon.stub(translator, 'resetPosition').callsFake(($element, finishTransition) => { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js index 9b60cc53f2b4..44d0d3583082 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js @@ -20,6 +20,7 @@ import * as zIndex from '__internal/ui/overlay/z_index'; import 'ui/scroll_view/ui.scrollable'; import selectors from '__internal/core/utils/m_selectors'; import swatch from '__internal/core/utils/swatch_container'; +import documentSizeCallbacks from '__internal/core/utils/document_size_callbacks'; import keyboardMock from '../../helpers/keyboardMock.js'; import pointerMock from '../../helpers/pointerMock.js'; import nativePointerMock from '../../helpers/nativePointerMock.js'; @@ -4469,3 +4470,81 @@ QUnit.module('Memory Leaks', { assert.strictEqual(positionController._$visualContainer, undefined, 'PositionController._$visualContainer is undefined after dispose'); }); }); + +QUnit.module('document size subscription', { + beforeEach: function() { + fx.off = true; + this.$element = $('#overlay'); + this.addSpy = sinon.spy(documentSizeCallbacks, 'add'); + this.removeSpy = sinon.spy(documentSizeCallbacks, 'remove'); + this.subscribedHandler = () => this.addSpy.lastCall.args[0]; + this.createOverlay = (options) => { + this.overlay = new Overlay(this.$element, options); + + return this.overlay; + }; + }, + afterEach: function() { + if(this.overlay) { + this.overlay.dispose(); + } + this.addSpy.restore(); + this.removeSpy.restore(); + fx.off = false; + } +}, () => { + QUnit.test('overlay should subscribe when shown and unsubscribe when hidden', function(assert) { + const overlay = this.createOverlay({ visible: true }); + + assert.ok(this.addSpy.calledOnce, 'subscribed while visible'); + + const handler = this.subscribedHandler(); + + overlay.hide(); + + assert.ok(this.removeSpy.calledWith(handler), 'unsubscribed when hidden'); + }); + + QUnit.test('overlay should unsubscribe on dispose', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + + overlay.dispose(); + this.overlay = null; + + assert.ok(this.removeSpy.calledWith(handler), 'unsubscribed on dispose'); + }); + + QUnit.test('geometry should be re-rendered when the visible area changes', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + const renderGeometrySpy = sinon.spy(overlay, '_renderGeometry'); + + handler(); + + assert.strictEqual(renderGeometrySpy.callCount, 1, 'geometry is re-rendered'); + }); + + QUnit.test('geometry should be re-rendered for an overlay placed against an element as well', function(assert) { + const overlay = this.createOverlay({ + visible: true, + visualContainer: $('#container') + }); + const handler = this.subscribedHandler(); + const renderGeometrySpy = sinon.spy(overlay, '_renderGeometry'); + + handler(); + + assert.strictEqual(renderGeometrySpy.callCount, 1, 'geometry is re-rendered'); + }); + + QUnit.test('a visible area change should be handled the same way as a window resize', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + const dimensionChangedSpy = sinon.spy(overlay, '_dimensionChanged'); + + handler(); + + assert.strictEqual(dimensionChangedSpy.callCount, 1, '_dimensionChanged is called'); + }); +}); From dc890c7b3785f34fc96fc33c9e043f32cb9347e4 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Mon, 7 Sep 2026 11:49:18 +0000 Subject: [PATCH 13/45] Styles: lint theme variables for branch duplicates, missing !default and dead declarations (#35049) --- packages/devextreme-scss/.stylelintrc.json | 6 +- .../scss/widgets/base/_htmlEditor.scss | 22 +- .../scss/widgets/base/_icon_fonts.scss | 2 +- .../scss/widgets/base/_loadIndicator.scss | 4 +- .../scss/widgets/base/_mixins.scss | 4 +- .../scss/widgets/base/_numberBox.scss | 4 +- .../scss/widgets/base/_pagination.scss | 4 +- .../scss/widgets/base/_recurrenceEditor.scss | 6 +- .../scss/widgets/base/_resizable.scss | 6 +- .../scss/widgets/base/_scrollView.scss | 4 +- .../scss/widgets/base/_slider.scss | 4 +- .../base/cardView/header_panel/_index.scss | 2 +- .../scss/widgets/base/colorBox/_index.scss | 2 +- .../scss/widgets/base/colorView/_index.scss | 24 +- .../scss/widgets/base/diagram/_index.scss | 2 +- .../widgets/base/gridBase/_variables.scss | 26 +- .../gridBase/layout/aiChat/_variables.scss | 12 +- .../scss/widgets/base/list/_index.scss | 2 +- .../scss/widgets/base/pivotGrid/_index.scss | 20 +- .../scss/widgets/base/scheduler/_common.scss | 4 +- .../scss/widgets/base/scheduler/_index.scss | 68 +-- .../base/scheduler/layout/_header.scss | 2 +- .../widgets/base/scheduler/views/_index.scss | 26 +- .../base/scheduler/views/agenda/_index.scss | 4 +- .../scheduler/views/timelines/_index.scss | 2 +- .../scss/widgets/base/treeList/_index.scss | 18 +- .../scss/widgets/base/treeView/_common.scss | 2 +- .../scss/widgets/fluent/_colors.scss | 2 +- .../scss/widgets/fluent/_sizes.scss | 4 +- .../scss/widgets/fluent/accordion/_sizes.scss | 4 +- .../widgets/fluent/actionSheet/_sizes.scss | 2 +- .../scss/widgets/fluent/badge/_sizes.scss | 4 +- .../scss/widgets/fluent/button/_colors.scss | 47 +-- .../scss/widgets/fluent/button/_sizes.scss | 8 +- .../scss/widgets/fluent/calendar/_colors.scss | 5 +- .../scss/widgets/fluent/cardView/_colors.scss | 218 +++------- .../scss/widgets/fluent/cardView/_sizes.scss | 108 +---- .../scss/widgets/fluent/chat/_colors.scss | 4 +- .../scss/widgets/fluent/chat/_sizes.scss | 17 +- .../scss/widgets/fluent/colorView/_index.scss | 4 +- .../scss/widgets/fluent/common/_mixins.scss | 2 +- .../widgets/fluent/dateRangeBox/_index.scss | 2 +- .../scss/widgets/fluent/dateView/_index.scss | 2 +- .../scss/widgets/fluent/diagram/_index.scss | 6 +- .../widgets/fluent/dropDownButton/_sizes.scss | 4 +- .../widgets/fluent/dropDownEditor/_index.scss | 8 +- .../widgets/fluent/fileManager/_colors.scss | 3 - .../widgets/fluent/fileManager/_sizes.scss | 2 +- .../widgets/fluent/fileUploader/_index.scss | 4 +- .../widgets/fluent/fileUploader/_sizes.scss | 4 +- .../scss/widgets/fluent/form/_sizes.scss | 4 +- .../scss/widgets/fluent/gantt/_index.scss | 2 +- .../scss/widgets/fluent/gridBase/_colors.scss | 17 +- .../scss/widgets/fluent/gridBase/_index.scss | 10 +- .../scss/widgets/fluent/gridBase/_sizes.scss | 11 +- .../widgets/fluent/gridBase/_variables.scss | 12 +- .../fluent/gridBase/layout/aiChat/_sizes.scss | 2 +- .../scss/widgets/fluent/informer/_colors.scss | 8 +- .../scss/widgets/fluent/list/_index.scss | 8 +- .../scss/widgets/fluent/menuBase/_colors.scss | 2 +- .../scss/widgets/fluent/menuBase/_sizes.scss | 2 +- .../scss/widgets/fluent/numberBox/_sizes.scss | 10 +- .../scss/widgets/fluent/pivotGrid/_index.scss | 10 +- .../scss/widgets/fluent/popup/_index.scss | 2 +- .../scss/widgets/fluent/popup/_sizes.scss | 10 +- .../widgets/fluent/progressBar/_colors.scss | 6 +- .../widgets/fluent/progressBar/_sizes.scss | 2 +- .../widgets/fluent/radioGroup/_sizes.scss | 3 - .../fluent/recurrenceEditor/_index.scss | 6 +- .../widgets/fluent/scheduler/_colors.scss | 9 +- .../scss/widgets/fluent/scheduler/_index.scss | 8 +- .../scss/widgets/fluent/scheduler/_sizes.scss | 73 +--- .../widgets/fluent/scrollView/_index.scss | 2 +- .../widgets/fluent/scrollable/_sizes.scss | 4 +- .../scss/widgets/fluent/slider/_sizes.scss | 9 - .../fluent/speedDialAction/_index.scss | 4 +- .../fluent/speedDialAction/_sizes.scss | 8 +- .../scss/widgets/fluent/splitter/_colors.scss | 14 +- .../scss/widgets/fluent/splitter/_sizes.scss | 2 +- .../scss/widgets/fluent/switch/_index.scss | 6 +- .../scss/widgets/fluent/switch/_sizes.scss | 13 +- .../widgets/fluent/tabs/variables/_sizes.scss | 21 +- .../scss/widgets/fluent/tagBox/_sizes.scss | 4 +- .../widgets/fluent/textEditor/_colors.scss | 10 +- .../widgets/fluent/textEditor/_index.scss | 8 +- .../widgets/fluent/textEditor/_sizes.scss | 8 +- .../scss/widgets/fluent/tooltip/_sizes.scss | 4 +- .../scss/widgets/fluent/treeView/_sizes.scss | 17 +- .../widgets/fluent/typography/_sizes.scss | 4 +- .../widgets/fluent/validation/_sizes.scss | 3 - .../scss/widgets/generic/_sizes.scss | 2 +- .../scss/widgets/generic/button/_sizes.scss | 3 - .../scss/widgets/generic/cardView/_sizes.scss | 120 ++---- .../scss/widgets/generic/chat/_sizes.scss | 5 +- .../scss/widgets/generic/checkBox/_index.scss | 2 +- .../scss/widgets/generic/checkBox/_sizes.scss | 4 +- .../widgets/generic/colorView/_index.scss | 4 +- .../scss/widgets/generic/common/_mixins.scss | 4 +- .../widgets/generic/contextMenu/_index.scss | 2 +- .../scss/widgets/generic/dateView/_index.scss | 2 +- .../scss/widgets/generic/diagram/_index.scss | 8 +- .../generic/dropDownEditor/_index.scss | 12 +- .../scss/widgets/generic/fieldset/_index.scss | 4 +- .../widgets/generic/fileManager/_colors.scss | 18 - .../widgets/generic/fileManager/_sizes.scss | 4 +- .../widgets/generic/fileUploader/_index.scss | 2 +- .../generic/filterBuilder/_colors.scss | 10 +- .../scss/widgets/generic/gantt/_index.scss | 2 +- .../scss/widgets/generic/gridBase/_index.scss | 18 +- .../scss/widgets/generic/gridBase/_sizes.scss | 20 +- .../widgets/generic/gridBase/_variables.scss | 2 +- .../gridBase/layout/aiChat/_sizes.scss | 2 +- .../widgets/generic/htmlEditor/_colors.scss | 36 -- .../widgets/generic/htmlEditor/_sizes.scss | 12 +- .../widgets/generic/informer/_colors.scss | 64 +-- .../scss/widgets/generic/list/_index.scss | 6 +- .../generic/loadIndicator/_colors.scss | 10 +- .../scss/widgets/generic/menuBase/_index.scss | 8 +- .../widgets/generic/pivotGrid/_sizes.scss | 4 +- .../scss/widgets/generic/popup/_index.scss | 4 +- .../widgets/generic/progressBar/_index.scss | 10 +- .../widgets/generic/rangeSlider/_index.scss | 2 +- .../generic/recurrenceEditor/_index.scss | 2 +- .../widgets/generic/scheduler/_colors.scss | 56 +-- .../widgets/generic/scheduler/_index.scss | 20 +- .../widgets/generic/scheduler/_sizes.scss | 73 +--- .../widgets/generic/scrollable/_sizes.scss | 4 +- .../scss/widgets/generic/slider/_colors.scss | 11 +- .../scss/widgets/generic/slider/_index.scss | 2 +- .../generic/speedDialAction/_index.scss | 6 +- .../generic/speedDialAction/_sizes.scss | 4 +- .../scss/widgets/generic/splitter/_sizes.scss | 2 +- .../scss/widgets/generic/switch/_colors.scss | 11 +- .../scss/widgets/generic/switch/_index.scss | 4 +- .../generic/tabPanel/variables/_borders.scss | 93 +---- .../generic/tabs/variables/_colors.scss | 19 +- .../generic/tabs/variables/_sizes.scss | 11 +- .../borders/_borders-horizontal.scss | 264 ++---------- .../variables/borders/_borders-vertical.scss | 264 ++---------- .../scss/widgets/generic/tagBox/_colors.scss | 11 +- .../widgets/generic/textEditor/_colors.scss | 4 +- .../widgets/generic/textEditor/_index.scss | 16 +- .../widgets/generic/textEditor/_sizes.scss | 4 +- .../scss/widgets/generic/toast/_index.scss | 2 +- .../widgets/generic/treeList/_colors.scss | 10 +- .../widgets/generic/treeView/_colors.scss | 11 +- .../scss/widgets/generic/treeView/_sizes.scss | 8 +- .../scss/widgets/material/_colors.scss | 64 ++- .../widgets/material/actionSheet/_sizes.scss | 2 +- .../scss/widgets/material/button/_colors.scss | 2 +- .../scss/widgets/material/button/_sizes.scss | 7 +- .../widgets/material/cardView/_colors.scss | 296 ++++--------- .../widgets/material/cardView/_sizes.scss | 122 ++---- .../scss/widgets/material/chat/_sizes.scss | 5 +- .../widgets/material/checkBox/_index.scss | 2 +- .../widgets/material/colorView/_index.scss | 4 +- .../scss/widgets/material/common/_mixins.scss | 2 +- .../widgets/material/dateRangeBox/_index.scss | 2 +- .../widgets/material/dateView/_index.scss | 2 +- .../scss/widgets/material/diagram/_index.scss | 6 +- .../material/dropDownButton/_sizes.scss | 4 +- .../material/dropDownEditor/_index.scss | 8 +- .../widgets/material/fileManager/_colors.scss | 3 - .../widgets/material/fileManager/_sizes.scss | 2 +- .../widgets/material/fileUploader/_index.scss | 2 +- .../widgets/material/fileUploader/_sizes.scss | 2 +- .../scss/widgets/material/gantt/_colors.scss | 8 +- .../scss/widgets/material/gantt/_index.scss | 2 +- .../widgets/material/gridBase/_colors.scss | 13 +- .../widgets/material/gridBase/_index.scss | 10 +- .../widgets/material/gridBase/_sizes.scss | 7 +- .../widgets/material/gridBase/_variables.scss | 12 +- .../gridBase/layout/aiChat/_sizes.scss | 2 +- .../widgets/material/htmlEditor/_sizes.scss | 4 +- .../widgets/material/informer/_colors.scss | 8 +- .../scss/widgets/material/list/_index.scss | 10 +- .../scss/widgets/material/menu/_index.scss | 2 +- .../widgets/material/menuBase/_index.scss | 2 +- .../widgets/material/menuBase/_sizes.scss | 2 +- .../widgets/material/pivotGrid/_index.scss | 10 +- .../scss/widgets/material/popup/_index.scss | 2 +- .../widgets/material/progressBar/_index.scss | 6 +- .../widgets/material/radioButton/_index.scss | 2 +- .../widgets/material/rangeSlider/_index.scss | 2 +- .../material/recurrenceEditor/_index.scss | 6 +- .../widgets/material/scheduler/_colors.scss | 6 +- .../widgets/material/scheduler/_index.scss | 14 +- .../widgets/material/scheduler/_sizes.scss | 78 +--- .../widgets/material/scrollView/_index.scss | 2 +- .../widgets/material/scrollable/_sizes.scss | 4 +- .../scss/widgets/material/slider/_index.scss | 10 +- .../scss/widgets/material/slider/_sizes.scss | 3 - .../material/speedDialAction/_index.scss | 6 +- .../material/speedDialAction/_sizes.scss | 8 +- .../widgets/material/splitter/_sizes.scss | 2 +- .../scss/widgets/material/switch/_index.scss | 8 +- .../scss/widgets/material/switch/_sizes.scss | 9 +- .../material/tabs/variables/_sizes.scss | 29 +- .../scss/widgets/material/tagBox/_sizes.scss | 7 +- .../scss/widgets/material/textBox/_index.scss | 6 +- .../widgets/material/textEditor/_colors.scss | 4 +- .../widgets/material/textEditor/_index.scss | 8 +- .../widgets/material/textEditor/_sizes.scss | 16 +- .../widgets/material/treeView/_sizes.scss | 15 +- .../widgets/material/typography/_sizes.scss | 4 +- .../widgets/material/validation/_sizes.scss | 3 - .../tests/module-variable-default.test.ts | 90 ++++ .../no-identical-branch-declarations.test.ts | 392 ++++++++++++++++++ .../devextreme-scss/tests/stylelint-rule.ts | 49 +++ .../tests/unused-elements.test.ts | 76 ++-- .../stylelint/module-variable-default.mjs | 55 +++ .../no-identical-branch-declarations.mjs | 238 +++++++++++ 212 files changed, 1799 insertions(+), 2412 deletions(-) create mode 100644 packages/devextreme-scss/tests/module-variable-default.test.ts create mode 100644 packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts create mode 100644 packages/devextreme-scss/tests/stylelint-rule.ts create mode 100644 packages/devextreme-scss/tools/stylelint/module-variable-default.mjs create mode 100644 packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs diff --git a/packages/devextreme-scss/.stylelintrc.json b/packages/devextreme-scss/.stylelintrc.json index e34adf09d5ce..52bbdc7712f3 100644 --- a/packages/devextreme-scss/.stylelintrc.json +++ b/packages/devextreme-scss/.stylelintrc.json @@ -1,7 +1,9 @@ { "plugins": [ "stylelint-scss", - "@stylistic/stylelint-plugin" + "@stylistic/stylelint-plugin", + "./tools/stylelint/no-identical-branch-declarations.mjs", + "./tools/stylelint/module-variable-default.mjs" ], "extends": "stylelint-config-standard-scss", "ignoreFiles": "!**/*.{css,scss}", @@ -18,6 +20,8 @@ "@stylistic/declaration-colon-space-after": "always-single-line", "@stylistic/declaration-colon-space-before": "never", "declaration-no-important": true, + "dx/module-variable-default": true, + "dx/no-identical-branch-declarations": true, "font-family-name-quotes": "always-unless-keyword", "@stylistic/function-comma-space-after": "always-single-line", "@stylistic/indentation": [2, { "ignore": ["inside-parens"] }], diff --git a/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss b/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss index 5b3818d55082..79636b10de17 100644 --- a/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss +++ b/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss @@ -2,17 +2,17 @@ @use "sass:math"; @use "sass:selector"; -$list-style: decimal lower-alpha lower-roman; -$list-style-width: 1.2em; -$list-style-margin: 0.3em; -$list-style-outer-width: $list-style-margin + $list-style-width; -$max-indent: 9; -$resize-handle-size: 11px; -$touch-resize-handle-size: 15px; -$half-resize-handle-size: math.div($resize-handle-size, 2); -$half-touch-resize-handle-size: math.div($touch-resize-handle-size, 2); - -$transparent-border: 1px solid transparent; +$list-style: decimal lower-alpha lower-roman !default; +$list-style-width: 1.2em !default; +$list-style-margin: 0.3em !default; +$list-style-outer-width: $list-style-margin + $list-style-width !default; +$max-indent: 9 !default; +$resize-handle-size: 11px !default; +$touch-resize-handle-size: 15px !default; +$half-resize-handle-size: math.div($resize-handle-size, 2) !default; +$half-touch-resize-handle-size: math.div($touch-resize-handle-size, 2) !default; + +$transparent-border: 1px solid transparent !default; @function add-counter-reset($counter, $start: 1) { @if $counter > $start { diff --git a/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss b/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss index c6645d033729..2bc650a5f1c8 100644 --- a/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss +++ b/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss @@ -613,7 +613,7 @@ $icons: ( "gridkanban": "\f240", "listbartreeoffset": "\f241", "clearhistory": "\f242", -); +) !default; // stylelint-enable diff --git a/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss b/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss index ac6f0ec94503..eafdf848b572 100644 --- a/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss +++ b/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss @@ -1,7 +1,7 @@ @use "sass:math"; -$loadindicator-width: 32px; -$loadindicator-height: 32px; +$loadindicator-width: 32px !default; +$loadindicator-height: 32px !default; .dx-loadindicator { width: $loadindicator-width; diff --git a/packages/devextreme-scss/scss/widgets/base/_mixins.scss b/packages/devextreme-scss/scss/widgets/base/_mixins.scss index e38a7556b667..5a37174c9974 100644 --- a/packages/devextreme-scss/scss/widgets/base/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/base/_mixins.scss @@ -140,5 +140,5 @@ transform: translate(-50%, -50%); } -$overlay-zindex: 1000; -$max-integer: 2147483647; +$overlay-zindex: 1000 !default; +$max-integer: 2147483647 !default; diff --git a/packages/devextreme-scss/scss/widgets/base/_numberBox.scss b/packages/devextreme-scss/scss/widgets/base/_numberBox.scss index 569695d1163f..e2db3328c299 100644 --- a/packages/devextreme-scss/scss/widgets/base/_numberBox.scss +++ b/packages/devextreme-scss/scss/widgets/base/_numberBox.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$spin-container-width: 22px; -$touch-friendly-spin-container-width: 64px; +$spin-container-width: 22px !default; +$touch-friendly-spin-container-width: 64px !default; .dx-numberbox { position: relative; diff --git a/packages/devextreme-scss/scss/widgets/base/_pagination.scss b/packages/devextreme-scss/scss/widgets/base/_pagination.scss index e4e03edf80db..b73aed4bc164 100644 --- a/packages/devextreme-scss/scss/widgets/base/_pagination.scss +++ b/packages/devextreme-scss/scss/widgets/base/_pagination.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$pagination-paddings: 8px; -$pagination-info-paddings: 9px; +$pagination-paddings: 8px !default; +$pagination-info-paddings: 9px !default; .dx-pager, .dx-pagination { diff --git a/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss b/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss index 28f826acb54d..136bc1ebf27c 100644 --- a/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss +++ b/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss @@ -1,6 +1,6 @@ -$recurrence-interval-number-box-margin-top: 25px; -$recurrence-radio-group-item-padding: 10px; -$recurrence-button-group-item-padding: 8px; +$recurrence-interval-number-box-margin-top: 25px !default; +$recurrence-radio-group-item-padding: 10px !default; +$recurrence-button-group-item-padding: 8px !default; .dx-recurrence-repeat-on, .dx-recurrence-numberbox-interval-wrapper { diff --git a/packages/devextreme-scss/scss/widgets/base/_resizable.scss b/packages/devextreme-scss/scss/widgets/base/_resizable.scss index 8068ae0a863a..a7635864fec7 100644 --- a/packages/devextreme-scss/scss/widgets/base/_resizable.scss +++ b/packages/devextreme-scss/scss/widgets/base/_resizable.scss @@ -1,6 +1,6 @@ -$handle-offset: 0; -$one-side-handle-size: 3px + $handle-offset; -$two-side-handle-size: 6px + $handle-offset; +$handle-offset: 0 !default; +$one-side-handle-size: 3px + $handle-offset !default; +$two-side-handle-size: 6px + $handle-offset !default; .dx-resizable { display: block; diff --git a/packages/devextreme-scss/scss/widgets/base/_scrollView.scss b/packages/devextreme-scss/scss/widgets/base/_scrollView.scss index c125ad2d0d01..920800bdf699 100644 --- a/packages/devextreme-scss/scss/widgets/base/_scrollView.scss +++ b/packages/devextreme-scss/scss/widgets/base/_scrollView.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$pulldown-height: 50px; -$pulldown-padding: 15px; +$pulldown-height: 50px !default; +$pulldown-padding: 15px !default; .dx-scrollable-native { &.dx-scrollable-native-generic { diff --git a/packages/devextreme-scss/scss/widgets/base/_slider.scss b/packages/devextreme-scss/scss/widgets/base/_slider.scss index 1c2ebfa9e3d4..2f6fe5294ea2 100644 --- a/packages/devextreme-scss/scss/widgets/base/_slider.scss +++ b/packages/devextreme-scss/scss/widgets/base/_slider.scss @@ -1,8 +1,8 @@ @use "./mixins" as *; @use "./popover/mixins" as *; -$slider-tooltip-content-height: 28px; -$slider-tooltip-arrow-height: 7px; +$slider-tooltip-content-height: 28px !default; +$slider-tooltip-arrow-height: 7px !default; .dx-slider-label { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss index 33789dd4a50e..f03e72471f49 100644 --- a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss @@ -6,7 +6,7 @@ 2px maximal right header item padding + 4px maximal dxSortable placeholder (drop indicator) width We can increase this number up to the CardView root padding without issues */ -$sortable-side-offset: 6px; +$sortable-side-offset: 6px !default; .dx-cardview-header-item-sorting { display: flex; diff --git a/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss b/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss index 262086e375ee..4174f1803d22 100644 --- a/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss @@ -2,7 +2,7 @@ @use "./mixins" as *; @use "sass:string" as string; -$colorbox-input-left-padding: 40px; +$colorbox-input-left-padding: 40px !default; .dx-colorbox-input-container { &.dx-colorbox-color-is-not-defined { diff --git a/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss index f226a5938c2a..9eb36c7c7061 100644 --- a/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss @@ -1,17 +1,17 @@ @use "../mixins" as *; -$color-view-container-width: 450px; -$color-view-palette-width: 288px; -$color-view-palette-height: 299px; -$color-view-alpha-width: 292px; -$color-view-hue-scale-width: 18px; -$color-view-hue-scale-height: $color-view-palette-height; - -$colorview-palette-handle-size: 28px; -$colorview-transparent-background-color: #ccc; - -$colorview-handle-first-side-size: 36px; -$colorview-handle-second-side-size: 17px; +$color-view-container-width: 450px !default; +$color-view-palette-width: 288px !default; +$color-view-palette-height: 299px !default; +$color-view-alpha-width: 292px !default; +$color-view-hue-scale-width: 18px !default; +$color-view-hue-scale-height: $color-view-palette-height !default; + +$colorview-palette-handle-size: 28px !default; +$colorview-transparent-background-color: #ccc !default; + +$colorview-handle-first-side-size: 36px !default; +$colorview-handle-second-side-size: 17px !default; .dx-colorview-container { width: $color-view-container-width; diff --git a/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss index e6d9d5683a3c..3b10c750588e 100644 --- a/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss @@ -1,6 +1,6 @@ @use "../mixins" as *; -$transparent-border: 1px solid transparent; +$transparent-border: 1px solid transparent !default; .dx-diagram { border: $transparent-border; diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss index c1b6172d3926..e6a121dc6e89 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss @@ -1,13 +1,13 @@ -$grid-cell-padding: 7px; -$grid-texteditor-input-padding: 32px; -$grid-total-footer-paddings: 7px; -$grid-command-select-column-width: 70px; -$grid-command-edit-column-width: 100px; -$grid-command-edit-column-with-icons-width: 80px; -$grid-command-expand-column-width: 30px; -$grid-command-adaptive-column-width: 36px; -$grid-column-header-indicator-width: 14px; -$grid-text-content-margin: 3px; -$grid-sort-index-width: 12px; -$grid-sort-index-offset: 3px; -$grid-ai-assistant-confirm-dialog-button-width: 50px; +$grid-cell-padding: 7px !default; +$grid-texteditor-input-padding: 32px !default; +$grid-total-footer-paddings: 7px !default; +$grid-command-select-column-width: 70px !default; +$grid-command-edit-column-width: 100px !default; +$grid-command-edit-column-with-icons-width: 80px !default; +$grid-command-expand-column-width: 30px !default; +$grid-command-adaptive-column-width: 36px !default; +$grid-column-header-indicator-width: 14px !default; +$grid-text-content-margin: 3px !default; +$grid-sort-index-width: 12px !default; +$grid-sort-index-offset: 3px !default; +$grid-ai-assistant-confirm-dialog-button-width: 50px !default; diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss index 03c18025cb6e..887b8717f691 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss @@ -1,6 +1,6 @@ -$ai-chat-toolbar-item-spacing: 4px; -$ai-chat-message-gap: 8px; -$ai-chat-message-content-gap: 4px; -$ai-chat-message-width: 280px; -$ai-chat-message-regenerate-button-border-radius: 4px; -$ai-chat-action-list-item-text-max-lines: 2; +$ai-chat-toolbar-item-spacing: 4px !default; +$ai-chat-message-gap: 8px !default; +$ai-chat-message-content-gap: 4px !default; +$ai-chat-message-width: 280px !default; +$ai-chat-message-regenerate-button-border-radius: 4px !default; +$ai-chat-action-list-item-text-max-lines: 2 !default; diff --git a/packages/devextreme-scss/scss/widgets/base/list/_index.scss b/packages/devextreme-scss/scss/widgets/base/list/_index.scss index 44c3baa238f4..ce602f27237d 100644 --- a/packages/devextreme-scss/scss/widgets/base/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/list/_index.scss @@ -1,7 +1,7 @@ @use "../mixins" as *; @use "../button/mixins" as *; -$list-searchbox-margin-bottom: 5px; +$list-searchbox-margin-bottom: 5px !default; .dx-list { margin: 0; diff --git a/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss index cfd91f6158e4..f207e0faa181 100644 --- a/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss @@ -23,16 +23,16 @@ $pivotgrid-button-top-padding: null !default; @use "../icon_fonts" as *; @use "./common"; -$pivotgrid-drag-header-border: 1px solid; -$pivotgrid-drag-header-first-shadow: 0 0 1px $pivotgrid-drag-header-first-shadow-color; -$pivotgrid-drag-header-second-shadow: 0 1px 3px $pivotgrid-drag-header-second-shadow-color; -$pivotgrid-border-width: 1px; -$pivotgrid-border: $pivotgrid-border-width solid; -$pivotgrid-icon-size: 14px; -$pivotgrid-common-field-padding: 2px; -$pivotgrid-filter-area-border-spacing: $pivotgrid-common-left-right-padding - $pivotgrid-common-field-padding $pivotgrid-header-top-bottom-padding - $pivotgrid-common-field-padding; -$pivotgrid-expand-icon-offset: -5px; -$pivotgrid-expand-icon-text-offset: 0; +$pivotgrid-drag-header-border: 1px solid !default; +$pivotgrid-drag-header-first-shadow: 0 0 1px $pivotgrid-drag-header-first-shadow-color !default; +$pivotgrid-drag-header-second-shadow: 0 1px 3px $pivotgrid-drag-header-second-shadow-color !default; +$pivotgrid-border-width: 1px !default; +$pivotgrid-border: $pivotgrid-border-width solid !default; +$pivotgrid-icon-size: 14px !default; +$pivotgrid-common-field-padding: 2px !default; +$pivotgrid-filter-area-border-spacing: $pivotgrid-common-left-right-padding - $pivotgrid-common-field-padding $pivotgrid-header-top-bottom-padding - $pivotgrid-common-field-padding !default; +$pivotgrid-expand-icon-offset: -5px !default; +$pivotgrid-expand-icon-text-offset: 0 !default; .dx-pivotgrid-fields-container { .dx-position-indicator { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss index 757318499b2b..996034b1de34 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss @@ -1,8 +1,8 @@ @use "sass:math"; @use "../../base/mixins" as *; -$scheduler-appointment-collector-margin: 3px; -$scheduler-appointment-collector-height: 22px; +$scheduler-appointment-collector-margin: 3px !default; +$scheduler-appointment-collector-height: 22px !default; .dx-scheduler { .dx-empty-message { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss index 5aed1e0f20f4..c42229cab532 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss @@ -52,52 +52,52 @@ $agenda-appointment-recurrence-icon-color: null !default; $scheduler-tooltip-list-focused-bg: $scheduler-tooltip-list-focused-bg ); -$scheduler-appointment-min-size: 4px; -$scheduler-appointment-content-padding: 5px 7px; -$scheduler-appointment-reduced-content-padding: 40px; +$scheduler-appointment-min-size: 4px !default; +$scheduler-appointment-content-padding: 5px 7px !default; +$scheduler-appointment-reduced-content-padding: 40px !default; -$scheduler-hovered-appointment-top-shadow-size: 5px; -$scheduler-focused-cell-shadow: inset 0 0 0 1px $scheduler-appointment-base-color; +$scheduler-hovered-appointment-top-shadow-size: 5px !default; +$scheduler-focused-cell-shadow: inset 0 0 0 1px $scheduler-appointment-base-color !default; -$scheduler-vertical-appointment-resizable-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color, inset 0 -2px 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-shadow: inset 0 2px 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-resizing-shadow: inset 0 -2px 0 0 $scheduler-appointment-start-color, $scheduler-vertical-appointment-shadow; +$scheduler-vertical-appointment-resizable-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color, inset 0 -2px 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-shadow: inset 0 2px 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-resizing-shadow: inset 0 -2px 0 0 $scheduler-appointment-start-color, $scheduler-vertical-appointment-shadow !default; -$scheduler-appointment-dragging-shadow: 7px 7px 15px 0 rgba(50, 50, 50, 0.2); +$scheduler-appointment-dragging-shadow: 7px 7px 15px 0 rgba(50, 50, 50, 0.2) !default; -$scheduler-horizontal-appointment-resizable-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color, inset -2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-resizable-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color, inset 2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-shadow: inset 2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-shadow-rtl: inset -2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-resizing-shadow: inset -2px 0 0 0 $scheduler-appointment-start-color, $scheduler-horizontal-appointment-shadow; +$scheduler-horizontal-appointment-resizable-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color, inset -2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-resizable-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color, inset 2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-shadow: inset 2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-shadow-rtl: inset -2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-resizing-shadow: inset -2px 0 0 0 $scheduler-appointment-start-color, $scheduler-horizontal-appointment-shadow !default; -$scheduler-grouped-appointment-month-text-size: 12px; +$scheduler-grouped-appointment-month-text-size: 12px !default; -$scheduler-base-border: 1px solid; -$scheduler-transparent-top-border: 1px solid transparent; -$scheduler-accent-border: 1px solid; +$scheduler-base-border: 1px solid !default; +$scheduler-transparent-top-border: 1px solid transparent !default; +$scheduler-accent-border: 1px solid !default; -$scheduler-reduced-icon-offset: 5px; -$scheduler-recurrence-icon-offset: 20px; +$scheduler-reduced-icon-offset: 5px !default; +$scheduler-recurrence-icon-offset: 20px !default; -$scheduler-agenda-appointment-margin: 5px; -$scheduler-agenda-last-in-date-appointment-margin: 20px; +$scheduler-agenda-appointment-margin: 5px !default; +$scheduler-agenda-last-in-date-appointment-margin: 20px !default; -$scheduler-small-size-factor: 0.5; +$scheduler-small-size-factor: 0.5 !default; -$scheduler-small-size-element-offset: $scheduler-left-column-width * $scheduler-small-size-factor; +$scheduler-small-size-element-offset: $scheduler-left-column-width * $scheduler-small-size-factor !default; -$scheduler-time-indicator-font-size: 30px; -$scheduler-time-indicator-size: 1px; -$scheduler-time-indicator-top: 14px; -$scheduler-time-indicator-left: 12px; +$scheduler-time-indicator-font-size: 30px !default; +$scheduler-time-indicator-size: 1px !default; +$scheduler-time-indicator-top: 14px !default; +$scheduler-time-indicator-left: 12px !default; -$scheduler-time-indicator-shadow-color: rgba(255, 255, 255, 0.1); -$scheduler-time-indicator-shadow: 0 1px 0 0 $scheduler-time-indicator-shadow-color; -$scheduler-time-indicator-text-shadow: $scheduler-time-indicator-shadow-color 1px 0 0; +$scheduler-time-indicator-shadow-color: rgba(255, 255, 255, 0.1) !default; +$scheduler-time-indicator-shadow: 0 1px 0 0 $scheduler-time-indicator-shadow-color !default; +$scheduler-time-indicator-text-shadow: $scheduler-time-indicator-shadow-color 1px 0 0 !default; @use "./appointment" with ( $scheduler-transparent-top-border: $scheduler-transparent-top-border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss index f58cba3e9c35..76a342a3bfbb 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss @@ -1,4 +1,4 @@ -$arrow-down: "\f016"; +$arrow-down: "\f016" !default; @mixin header-base( $border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index bdbe38bd195e..dec89907e065 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -28,24 +28,24 @@ $scheduler-first-month-cell-background-color: null !default; $scheduler-panel-text-color: null !default; -$scheduler-timepanel-cell-offset: 20px; -$scheduler-workspace-info-font-size: 20px; -$scheduler-small-font-size: 14px; +$scheduler-timepanel-cell-offset: 20px !default; +$scheduler-workspace-info-font-size: 20px !default; +$scheduler-small-font-size: 14px !default; -$scheduler-current-time-cell-border-size: 2px; +$scheduler-current-time-cell-border-size: 2px !default; -$scheduler-workspace-date-table-cell-height: 50px; -$scheduler-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 1.5; -$scheduler-collapsed-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 0.5; -$scheduler-vertical-grouped-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height; -$scheduler-header-panel-table-cell-height: $scheduler-workspace-date-table-cell-height - 10px; -$scheduler-group-header-table-cell-height: 30px; +$scheduler-workspace-date-table-cell-height: 50px !default; +$scheduler-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 1.5 !default; +$scheduler-collapsed-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 0.5 !default; +$scheduler-vertical-grouped-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height !default; +$scheduler-header-panel-table-cell-height: $scheduler-workspace-date-table-cell-height - 10px !default; +$scheduler-group-header-table-cell-height: 30px !default; -$scheduler-group-header-table-cell-width: 80px; +$scheduler-group-header-table-cell-width: 80px !default; -$scheduler-month-date-text-size: 16px; +$scheduler-month-date-text-size: 16px !default; -$scheduler-month-date-text-padding: 6px; +$scheduler-month-date-text-padding: 6px !default; @use "./day" with ( $scheduler-base-border: $scheduler-base-border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss index 9602a3aaadc9..61291d143064 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss @@ -4,8 +4,8 @@ $scheduler-small-font-size: null !default; $scheduler-workspace-info-font-size: null !default; $scheduler-other-month-cell-opacity: null !default; -$scheduler-agenda-left-column-width: 70px; -$scheduler-group-header-agenda-font-size: 18px; +$scheduler-agenda-left-column-width: 70px !default; +$scheduler-group-header-agenda-font-size: 18px !default; .dx-scheduler-agenda { .dx-scheduler-date-table-scrollable { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss index b8469f2b1e83..8805dcef56c5 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss @@ -6,7 +6,7 @@ $scheduler-base-border: null !default; $scheduler-base-border-color: null !default; $scheduler-accent-border-color: null !default; -$scheduler-timeline-min-height: 100px; +$scheduler-timeline-min-height: 100px !default; .dx-scheduler-timeline-week, .dx-scheduler-timeline-work-week, diff --git a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss index 95667f2a6758..30e7affaa09d 100644 --- a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss @@ -24,15 +24,15 @@ $base-focus-color: null !default; $treelist-focused-border-color: $datagrid-focused-border-color, ); -$treelist-border: $datagrid-border; -$treelist-drag-header-border-color: $datagrid-drag-header-border-color; -$treelist-row-error-bg: $datagrid-row-error-bg; -$treelist-base-color: $datagrid-base-color; -$treelist-base-background-color: $datagrid-base-background-color; -$treelist-border-color: $datagrid-border-color; -$treelist-focused-border-color: $datagrid-focused-border-color; -$treelist-text-stub-background-image-path: $datagrid-text-stub-background-image-path; -$treelist-row-error-color: $datagrid-row-error-color; +$treelist-border: $datagrid-border !default; +$treelist-drag-header-border-color: $datagrid-drag-header-border-color !default; +$treelist-row-error-bg: $datagrid-row-error-bg !default; +$treelist-base-color: $datagrid-base-color !default; +$treelist-base-background-color: $datagrid-base-background-color !default; +$treelist-border-color: $datagrid-border-color !default; +$treelist-focused-border-color: $datagrid-focused-border-color !default; +$treelist-text-stub-background-image-path: $datagrid-text-stub-background-image-path !default; +$treelist-row-error-color: $datagrid-row-error-color !default; .dx-treelist-borders { > .dx-treelist-headers, diff --git a/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss b/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss index 644abfdb53c3..86223bf07535 100644 --- a/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss +++ b/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss @@ -1,7 +1,7 @@ @use "../mixins" as *; @use "../../base/icon_fonts" as *; -$treeview-searchbox-margin-bottom: 5px; +$treeview-searchbox-margin-bottom: 5px !default; .dx-treeview-loadindicator-wrapper { text-align: center; diff --git a/packages/devextreme-scss/scss/widgets/fluent/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/_colors.scss index bbceda66881c..f0b0ffd43d08 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/_colors.scss @@ -250,7 +250,7 @@ $scrollview-pulldown-path: null !default; $base-danger-hover: color.adjust($base-danger, $saturation: 0.44%, $space: hsl, $lightness: 7.45%) !default; $base-danger-active: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1.8deg, $saturation: -0.9%, $lightness: 28.8%) !default; - $base-danger-selected: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 0.7deg, $saturation: 1.3%, $lightness: 14.9%); + $base-danger-selected: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 0.7deg, $saturation: 1.3%, $lightness: 14.9%) !default; $scrollview-pulldown-path: data-uri("images/widgets/fluent/color-schemes/dark/pulldown.png") !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss index a4017a22e04a..70129095fd6d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss @@ -7,7 +7,7 @@ $fluent-base-inline-block-height: null !default; $fluent-base-icon-size: null !default; $fluent-base-icon-margin: 8px !default; $fluent-base-inline-horizontal-padding: null !default; -$fluent-base-border-radius: null !default; +$fluent-base-border-radius: 4px !default; $fluent-html-editor-add-image-dialog-base-padding: 5px !default; /** @@ -39,7 +39,6 @@ $fluent-button-icon-margin: null !default; $fluent-base-inline-block-height: 20px !default; $fluent-base-icon-size: 20px !default; $fluent-base-inline-horizontal-padding: 12px !default; - $fluent-base-border-radius: 4px !default; $fluent-button-height: 32px !default; $fluent-button-icon-size: 20px !default; $fluent-button-icon-margin: 8px !default; @@ -56,7 +55,6 @@ $fluent-button-icon-margin: null !default; $fluent-base-inline-block-height: 16px !default; $fluent-base-icon-size: 16px !default; $fluent-base-inline-horizontal-padding: 8px !default; - $fluent-base-border-radius: 4px !default; $fluent-button-height: 24px !default; $fluent-button-icon-size: 16px !default; $fluent-button-icon-margin: 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss index 176f3edb5ad4..78b26f936b03 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss @@ -13,7 +13,7 @@ $accordion-item-border-radius: $fluent-base-border-radius !default; * $name 10. Item expanded margin * $type text */ -$fluent-accordion-item-opened-margin: null !default; +$fluent-accordion-item-opened-margin: 0 !default; @if $size == "default" { $fluent-accordion-title-horizontal-padding: 12px !default; @@ -21,7 +21,6 @@ $fluent-accordion-item-opened-margin: null !default; $fluent-accordion-title-height: 44px !default; $accordion-title-icon-margin: 8px !default; $accordion-title-icon-size: 20px !default; - $fluent-accordion-item-opened-margin: 0 !default; } @else if $size == "compact" { @@ -30,5 +29,4 @@ $fluent-accordion-item-opened-margin: null !default; $fluent-accordion-title-height: 32px !default; $accordion-title-icon-margin: 6px !default; $accordion-title-icon-size: 16px !default; - $fluent-accordion-item-opened-margin: 0 !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss index 5694e65f75d3..15fdcab8742f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss @@ -1,2 +1,2 @@ -$fluent-actionsheet-item-margin-value: 12px; +$fluent-actionsheet-item-margin-value: 12px !default; $fluent-actionsheet-item-margin: 0 0 $fluent-actionsheet-item-margin-value 0 !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss index 2c99c408f03a..59af08ff7a51 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss @@ -1,17 +1,15 @@ @use "../sizes" as *; -$fluent-badge-padding: null !default; +$fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: null !default; $badge-line-height: null !default; @if $size == "default" { - $fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: 12px !default; $badge-line-height: 20px !default; } @else if $size == "compact" { - $fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: 10px !default; $badge-line-height: 16px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss index de2be8586ff2..354fabaa5455 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss @@ -84,13 +84,13 @@ $button-default-selected-color: null !default; * $name 30. Hover state background color * $type color */ -$button-default-hover-bg: null !default; +$button-default-hover-bg: $base-accent-hover !default; /** * $name 40. Focused state background color * $type color */ -$button-default-focused-bg: null !default; +$button-default-focused-bg: $base-accent-hover !default; /** * $name 50. Active state background color @@ -113,7 +113,6 @@ $button-default-outlined-border-color: $base-accent !default; $button-default-outlined-bg: transparent !default; $button-default-outlined-hover-bg: null !default; -$button-default-outlined-focused-bg: null !default; $button-default-outlined-active-bg: null !default; $button-default-outlined-selected-bg: null !default; @@ -168,11 +167,10 @@ $button-danger-selected-bg: $base-danger-selected !default; $button-danger-outlined-color: $base-danger !default; $button-danger-outlined-border-color: $base-danger !default; $button-danger-outlined-color-hover: null !default; -$button-danger-outlined-color-active: null !default; +$button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: null !default; $button-danger-outlined-bg: transparent !default; $button-danger-outlined-hover-bg: null !default; -$button-danger-outlined-focused-bg: null !default; $button-danger-outlined-active-bg: null !default; $button-danger-outlined-selected-bg: null !default; @@ -200,12 +198,6 @@ $button-success-selected-color: null !default; */ $button-success-hover-bg: null !default; -/** -* $name 40. Focused state background color -* $type color -*/ -$button-success-focused-bg: null !default; - /** * $name 50. Active state background color * $type color @@ -222,10 +214,8 @@ $button-success-outlined-color: $base-success !default; $button-success-outlined-border-color: $base-success !default; $button-success-outlined-color-hover: null !default; $button-success-outlined-color-active: null !default; -$button-success-outlined-selected-color: null !default; $button-success-outlined-bg: transparent !default; $button-success-outlined-hover-bg: null !default; -$button-success-outlined-focused-bg: null !default; $button-success-outlined-active-bg: null !default; $button-success-outlined-selected-bg: null !default; @@ -233,14 +223,10 @@ $button-accent-foreground-color: null !default; @if $mode == "light" { $button-accent-foreground-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -2deg, $saturation: -9.10%, $lightness: -23.53%) !default; - - $button-default-hover-bg: $base-accent-hover !default; - $button-default-focused-bg: $base-accent-hover !default; $button-default-active-bg: $base-accent-active !default; $button-default-selected-bg: $base-accent-selected !default; $button-success-hover-bg: color.adjust($base-success, $saturation: 0.63%, $space: hsl, $lightness: -2.75%) !default; - $button-success-focused-bg: $button-success-hover-bg !default; $button-success-active-bg: color.adjust($base-success, $saturation: -0.95%, $space: hsl, $lightness: -19.22%) !default; $button-success-selected-bg: color.adjust($base-success, $saturation: -0.22%, $space: hsl, $lightness: -12.16%) !default; @@ -250,12 +236,10 @@ $button-accent-foreground-color: null !default; $button-default-outlined-active-bg: extcolor.adjust-if-not-achromatic($color: $button-default-outlined-hover-bg, $hue: -1deg, $saturation: 17.00%, $lightness: -17.06%) !default; $button-danger-outlined-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1.5deg, $saturation: 0.6%, $lightness: 46.7%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: extcolor.adjust-if-not-achromatic($color: $button-danger-outlined-hover-bg, $hue: 358.9deg, $saturation: 2.2%, $lightness: -13.9%) !default; $button-danger-outlined-selected-bg: extcolor.adjust-if-not-achromatic($color: $button-danger-outlined-active-bg, $hue: 359.1deg, $saturation: -0.6%, $lightness: 7.1%) !default; $button-success-outlined-hover-bg: color.adjust($base-success, $saturation: -29.8%, $space: hsl, $lightness: 68.8%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($base-success, $saturation: -34.9%, $space: hsl, $lightness: 46.1%) !default; $button-success-outlined-selected-bg: color.adjust($base-success, $saturation: -35%, $space: hsl, $lightness: 57.6%) !default; } @@ -266,25 +250,19 @@ $button-accent-foreground-color: null !default; $button-default-outlined-active-bg: color.adjust($button-default-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-danger-outlined-hover-bg: color.mix($base-bg, $base-danger, 90%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: color.adjust($button-danger-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-danger-outlined-selected-bg: color.adjust($button-danger-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-success-outlined-hover-bg: color.mix($base-bg, $base-success, 90%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($button-success-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-success-outlined-selected-bg: color.adjust($button-success-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; } - $button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; - $button-danger-outlined-color-hover: $base-danger-selected !default; - $button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: $base-danger-selected !default; $button-success-outlined-color-hover: color.adjust($base-success, $saturation: -0.22%, $space: hsl, $lightness: -12.16%) !default; $button-success-outlined-color-active: color.adjust($base-success, $saturation: -0.95%, $space: hsl, $lightness: -19.22%) !default; - $button-success-outlined-selected-color: $button-success-outlined-color-hover; $button-default-color: $base-inverted-text-color !default; $button-default-selected-color: $base-inverted-text-color !default; @@ -297,37 +275,28 @@ $button-accent-foreground-color: null !default; @if $mode == "dark" { $button-accent-foreground-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 0.9deg, $saturation: -2.2%, $lightness: 22.4%) !default; - - $button-default-hover-bg: $base-accent-hover !default; - $button-default-focused-bg: $base-accent-hover !default; $button-default-active-bg: $button-accent-foreground-color !default; $button-default-selected-bg: color.adjust($button-default-active-bg, $saturation: -6.4%, $space: hsl, $lightness: 5.3%) !default; $button-success-hover-bg: color.adjust($base-success, $saturation: 5.4%, $space: hsl, $lightness: 22.5%) !default; - $button-success-focused-bg: $button-success-hover-bg !default; $button-success-active-bg: color.adjust($base-success, $saturation: 10.6%, $space: hsl, $lightness: 45.3%) !default; $button-success-selected-bg: color.adjust($base-success, $saturation: 5.3%, $space: hsl, $lightness: 34.1%) !default; $button-default-outlined-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 356.3deg, $saturation: -14.7%, $lightness: -49.4%) !default; - $button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; $button-default-outlined-active-bg: $base-accent-selected !default; $button-default-outlined-selected-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -12.33%, $lightness: -41.18%) !default; $button-danger-outlined-color-hover: $base-danger-active !default; - $button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: $base-danger-active !default; $button-danger-outlined-hover-bg: color.adjust($base-danger, $saturation: -5.1%, $space: hsl, $lightness: -53.5%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: color.adjust($base-danger, $saturation: -4.6%, $space: hsl, $lightness: -22.9%) !default; $button-danger-outlined-selected-bg: color.adjust($base-danger, $saturation: -4.3%, $space: hsl, $lightness: -40.4%) !default; $button-success-outlined-color-hover: color.adjust($base-success, $saturation: 10.6%, $space: hsl, $lightness: 45.3%) !default; $button-success-outlined-color-active: $button-success-outlined-color-hover !default; - $button-success-outlined-selected-color: $button-success-outlined-color-hover !default; $button-success-outlined-hover-bg: color.adjust($base-success, $saturation: 39.4%, $space: hsl, $lightness: -42.7%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($base-success, $saturation: 39%, $space: hsl, $lightness: -33.1%) !default; $button-success-outlined-selected-bg: color.adjust($base-success, $saturation: 40.1%, $space: hsl, $lightness: -35.7%) !default; @@ -340,6 +309,16 @@ $button-accent-foreground-color: null !default; $button-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; } +/** +* $name 40. Focused state background color +* $type color +*/ +$button-success-focused-bg: $button-success-hover-bg !default; +$button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; +$button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; +$button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; +$button-success-outlined-selected-color: $button-success-outlined-color-hover !default; + $button-default-icon-color: $button-default-color !default; $button-normal-icon-color: $button-normal-color !default; $button-danger-icon-color: $button-danger-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss index 72d7c8a9e8cd..44ca7a7ee796 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss @@ -2,8 +2,6 @@ $fluent-button-horizontal-padding: null !default; $fluent-button-vertical-padding: null !default; -$fluent-button-icon-padding: null !default; -$fluent-button-icon-text-padding: null !default; $fluent-button-content-line-height: 0 !default; $fluent-button-text-line-height: normal !default; $fluent-button-text-transform: none !default; @@ -15,17 +13,15 @@ $fluent-button-box-shadow-size: 0 1px 3px !default; @if $size == "default" { $fluent-button-horizontal-padding: 12px !default; $fluent-button-vertical-padding: 6px !default; - $fluent-button-icon-padding: 6px; - $fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; } @else if $size == "compact" { $fluent-button-horizontal-padding: 8px !default; $fluent-button-vertical-padding: 4px !default; - $fluent-button-icon-padding: 4px !default; - $fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; } +$fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; + :root { --dx-button-padding-inline: #{$fluent-button-horizontal-padding}; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss index 6fdc41b02872..b24780264b8f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss @@ -24,7 +24,6 @@ $calendar-header-color: $calendar-color !default; $calendar-cell-other-color: null !default; $calendar-bg: transparent !default; $calendar-hover-bg: null !default; -$calendar-cell-hover-border-color: null !default; $calendar-cell-hover-color: $calendar-color !default; /** @@ -44,7 +43,6 @@ $calendar-week-number-border-radius: 0 !default; @if $mode == "light" { $calendar-hover-bg: $button-default-outlined-selected-bg !default; - $calendar-cell-hover-border-color: $calendar-hover-bg !default; $calendar-cell-active-bg: $button-default-outlined-active-bg !default; $calendar-cell-other-color: color.adjust($calendar-color, $lightness: 23.92%, $space: hsl) !default; $calendar-week-number-cell-color: color.adjust($calendar-color, $lightness: 23.92%, $space: hsl) !default; @@ -54,7 +52,6 @@ $calendar-week-number-border-radius: 0 !default; @if $mode == "dark" { $calendar-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 358.4deg, $saturation: -8.6%, $lightness: -26.7%) !default; - $calendar-cell-hover-border-color: $calendar-hover-bg !default; $calendar-cell-active-bg: $base-accent-selected !default; $calendar-cell-other-color: color.adjust($calendar-color, $lightness: -32.16%, $space: hsl) !default; $calendar-week-number-cell-color: color.adjust($calendar-color, $lightness: -32.16%, $space: hsl) !default; @@ -62,4 +59,6 @@ $calendar-week-number-border-radius: 0 !default; $calendar-week-number-cell-bg: $base-hover-bg !default; } +$calendar-cell-hover-border-color: $calendar-hover-bg !default; + $calendar-cell-other-hover-color: $calendar-cell-other-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss index 7c1d3a4c5b92..7d0f1ab8613e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss @@ -19,19 +19,19 @@ * $name 100. Background color * $type color */ -$cardview-fluent-common-bg-color: null !default; +$cardview-fluent-common-bg-color: $base-typography-bg !default; /** * $name 102. Link color * $type color */ -$cardview-fluent-common-link-color: null !default; +$cardview-fluent-common-link-color: $base-link-color !default; /** * $name 103. Text color * $type color */ -$cardview-fluent-common-text-color: null !default; +$cardview-fluent-common-text-color: $base-text-color !default; // #endregion @@ -41,31 +41,31 @@ $cardview-fluent-common-text-color: null !default; * $name 200. Background color * $type color */ -$cardview-fluent-card-bg-color: null !default; +$cardview-fluent-card-bg-color: $base-bg !default; /** * $name 201. Border color * $type color */ -$cardview-fluent-card-border-color: null !default; +$cardview-fluent-card-border-color: $base-border-color !default; /** * $name 203. Divider color * $type color */ -$cardview-fluent-card-divider-color: null !default; +$cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; /** * $name 204. Text color * $type color */ -$cardview-fluent-card-text-color: null !default; +$cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; /** * $name 206. Cover background color * $type color */ -$cardview-fluent-card__cover-bg-color: null !default; +$cardview-fluent-card__cover-bg-color: #FFF !default; /** * $name 207. "Empty cover" background color @@ -79,7 +79,7 @@ $cardview-fluent-card__cover--noimage-bg-color: null !default; */ $cardview-fluent-card__cover--noimage__icon-color: null !default; -$cardview-fluent-card--focused-outline-color: null !default; +$cardview-fluent-card--focused-outline-color: $base-accent !default; // #endregion @@ -97,35 +97,17 @@ $cardview-fluent-card--selected-bg-color: null !default; */ $cardview-fluent-card--selected-border-color: null !default; -/** -* $name 302. Divider color -* $type color -*/ -$cardview-fluent-card--selected-divider-color: null !default; - /** * $name 303. Text color * $type color */ -$cardview-fluent-card--selected-text-color: null !default; +$cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; /** * $name 305. Cover background color * $type color */ -$cardview-fluent-card--selected__cover-bg-color: null !default; - -/** -* $name 306. "Empty cover" background color -* $type color -*/ -$cardview-fluent-card--selected__cover--noimage-bg-color: null !default; - -/** -* $name 307. "Empty cover" icon color -* $type color -*/ -$cardview-fluent-card--selected__cover--noimage__icon-color: null !default; +$cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; // #endregion @@ -141,33 +123,33 @@ $cardview-fluent-header-panel__item-bg-color: null !default; * $name 401. Border color * $type color */ -$cardview-fluent-header-panel__item-border-color: null !default; +$cardview-fluent-header-panel__item-border-color: $base-border-color !default; /** * $name 403. Text color * $type color */ -$cardview-fluent-header-panel__item-text-color: null !default; +$cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; /** * $name 405. Icon color * $type color */ -$cardview-fluent-header-panel__item__icon-color: null !default; +$cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; /** * $name 406. Active "Filter" icon color * $type color */ -$cardview-fluent-header-panel__item__header-filter-icon--selected-color: null !default; +$cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; /** * $name 407. Sort index text color * $type color */ -$cardview-fluent-header-panel__item__sort-index-text-color: null !default; +$cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; -$cardview-fluent-header-panel__item--focused-outline-color: null !default; +$cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // #endregion @@ -213,17 +195,11 @@ $cardview-fluent-header-panel__item--hovered__sort-index-text-color: null !defau // #region HEADER PANEL ITEM (DISABLED) -/** -* $name 600. Background color -* $type color -*/ -$cardview-fluent-header-panel__item--disabled-bg-color: null !default; - /** * $name 601. Border color * $type color */ -$cardview-fluent-header-panel__item--disabled-border-color: null !default; +$cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; /** * $name 602. Text color @@ -253,29 +229,23 @@ $cardview-fluent-header-panel__item--disabled__sort-index-text-color: null !defa // #region HEADER PANEL ITEM (ON DRAG) -/** -* $name 700. Background color -* $type color -*/ -$cardview-fluent-header-panel__item--dragged-bg-color: null !default; - /** * $name 701. Border color * $type color */ -$cardview-fluent-header-panel__item--dragged-border-color: null !default; +$cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; /** * $name 702. Text color * $type color */ -$cardview-fluent-header-panel__item--dragged-text-color: null !default; +$cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; /** * $name 704. "Drag allowed" icon color * $type color */ -$cardview-fluent-header-panel__item--dragged__allowance-icon-color: null !default; +$cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; /** * $name 705. "Drag blocked" icon color @@ -287,7 +257,7 @@ $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: null !defa * $name 706. Box shadow color * $type color */ -$cardview-fluent-header-panel__item--dragged-box-shadow-color: null !default; +$cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // #endregion @@ -303,19 +273,19 @@ $cardview-fluent-header-panel__dropzone-bg-color: null !default; * $name 802. Border color * $type color */ -$cardview-fluent-header-panel__dropzone-border-color: null !default; +$cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; /** * $name 803. Text color * $type color */ -$cardview-fluent-header-panel__dropzone-text-color: null !default; +$cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; /** * $name 804. Icon color * $type color */ -$cardview-fluent-header-panel__dropzone__icon-color: null !default; +$cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; // #endregion @@ -325,25 +295,25 @@ $cardview-fluent-header-panel__dropzone__icon-color: null !default; * $name 900. Background color * $type color */ -$cardview-fluent-filter-panel-bg-color: null !default; +$cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; /** * $name 902. Border color * $type color */ -$cardview-fluent-filter-panel-border-color: null !default; +$cardview-fluent-filter-panel-border-color: $base-border-color !default; /** * $name 903. Text color * $type color */ -$cardview-fluent-filter-panel-text-color: null !default; +$cardview-fluent-filter-panel-text-color: $base-accent !default; /** * $name 904. Icon color * $type color */ -$cardview-fluent-filter-panel__icon-color: null !default; +$cardview-fluent-filter-panel__icon-color: $base-accent !default; // #endregion @@ -353,7 +323,7 @@ $cardview-fluent-filter-panel__icon-color: null !default; * $name 1000. Background color * $type color */ -$cardview-fluent-nodata-view-bg-color: null !default; +$cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; /** * $name 1001. Text color @@ -381,7 +351,7 @@ $cardview-fluent-nodata-view__icon-bg-color: null !default; * $name 1100. Background color * $type color */ -$cardview-fluent-card__content__field-value--highlighted-bg-color: null !default; +$cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; /** * $name 1102. Text color @@ -393,52 +363,28 @@ $cardview-fluent-card__content__field-value--highlighted-text-color: null !defau * $name 1104. Box shadow color * $type color */ -$cardview-fluent-card__content__field-value--highlighted-box-shadow-color: null !default; +$cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; // #endregion // #region COLUMN CHOOSER -$cardview-fluent-columnchooser--empty__message-color: null !default; +$cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; // #endregion @if $mode == "light" { - // Common - $cardview-fluent-common-bg-color: $base-typography-bg !default; - $cardview-fluent-common-link-color: $base-link-color !default; - $cardview-fluent-common-text-color: $base-text-color !default; - // Card - $cardview-fluent-card-bg-color: $base-bg !default; - $cardview-fluent-card-border-color: $base-border-color !default; - $cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; - $cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__cover-bg-color: #FFF !default; $cardview-fluent-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -12.16%, $space: hsl) !default; $cardview-fluent-card__cover--noimage__icon-color: color.adjust($cardview-fluent-common-text-color, $lightness: 23.92%, $space: hsl) !default; - $cardview-fluent-card--focused-outline-color: $base-accent !default; - // Card - selected $cardview-fluent-card--selected-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 4deg, $saturation: -11.38%, $lightness: 55.49%) !default; $cardview-fluent-card--selected-border-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 3deg, $saturation: 2.21%, $lightness: 44.31%) !default; - $cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; - $cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; - $cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; - $cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; - $cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-fluent-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -5.88%, $space: hsl) !default; - $cardview-fluent-header-panel__item-border-color: $base-border-color !default; - $cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; - - $cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-fluent-header-panel__item--hovered-bg-color: color.adjust($cardview-fluent-header-panel__item-bg-color, $lightness: -1.96%, $space: hsl) !default; @@ -449,84 +395,38 @@ $cardview-fluent-columnchooser--empty__message-color: null !default; $cardview-fluent-header-panel__item--hovered__sort-index-text-color: $cardview-fluent-header-panel__item__sort-index-text-color !default; // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; $cardview-fluent-header-panel__item--disabled-text-color: color.adjust($cardview-fluent-header-panel__item-text-color, $lightness: 60.00%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__icon-color: color.adjust($cardview-fluent-header-panel__item__icon-color, $lightness: 60.00%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__header-filter-icon--selected-color: color.adjust($cardview-fluent-header-panel__item__header-filter-icon--selected-color, $saturation: -34.10%, $space: hsl, $lightness: 27.06%) !default; $cardview-fluent-header-panel__item--disabled__sort-index-text-color: color.adjust($cardview-fluent-header-panel__item__sort-index-text-color, $lightness: 60.00%, $space: hsl) !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; - $cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; - $cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -4deg, $saturation: 22.80%, $lightness: -9.61%) !default; - $cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 4deg, $saturation: -11.38%, $lightness: 55.49%) !default; - $cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; - $cardview-fluent-filter-panel-border-color: $base-border-color !default; - $cardview-fluent-filter-panel-text-color: $base-accent !default; - $cardview-fluent-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; $cardview-fluent-nodata-view-text-color: color.adjust($cardview-fluent-common-text-color, $lightness: 23.92%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-color: $cardview-fluent-nodata-view-text-color !default; $cardview-fluent-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: -9.80%, $space: hsl) !default; // Search highlight - $cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; $cardview-fluent-card__content__field-value--highlighted-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; } @if $mode == "dark" { - // Common - $cardview-fluent-common-bg-color: $base-typography-bg !default; - $cardview-fluent-common-link-color: $base-link-color !default; - $cardview-fluent-common-text-color: $base-text-color !default; - // Card - $cardview-fluent-card-bg-color: $base-bg !default; - $cardview-fluent-card-border-color: $base-border-color !default; - $cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; - $cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__cover-bg-color: #FFF !default; $cardview-fluent-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -5.88%, $space: hsl) !default; $cardview-fluent-card__cover--noimage__icon-color: color.adjust($base-text-color, $lightness: -32.16%, $space: hsl) !default; - $cardview-fluent-card--focused-outline-color: $base-accent !default; - // Card - selected $cardview-fluent-card--selected-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -14.69%, $lightness: -49.41%) !default; $cardview-fluent-card--selected-border-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -3deg, $saturation: -11.06%, $lightness: -36.27%) !default; - $cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; - $cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; - $cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; - $cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; - $cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-fluent-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -12.16%, $space: hsl) !default; - $cardview-fluent-header-panel__item-border-color: $base-border-color !default; - $cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; - - $cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-fluent-header-panel__item--hovered-bg-color: color.adjust($cardview-fluent-header-panel__item-bg-color, $lightness: 18.04%, $space: hsl) !default; @@ -537,48 +437,56 @@ $cardview-fluent-columnchooser--empty__message-color: null !default; $cardview-fluent-header-panel__item--hovered__sort-index-text-color: $cardview-fluent-common-text-color !default; // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; $cardview-fluent-header-panel__item--disabled-text-color: color.adjust($cardview-fluent-header-panel__item-text-color, $lightness: -56.08%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__icon-color: color.adjust($cardview-fluent-header-panel__item__icon-color, $lightness: -56.08%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__header-filter-icon--selected-color: color.adjust($cardview-fluent-header-panel__item__header-filter-icon--selected-color, $saturation: -6.72%, $space: hsl, $lightness: -18.04%) !default; $cardview-fluent-header-panel__item--disabled__sort-index-text-color: color.adjust($cardview-fluent-header-panel__item__sort-index-text-color, $lightness: -56.08%, $space: hsl) !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; - $cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; - $cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: color.adjust($base-danger, $saturation: -0.27%, $space: hsl, $lightness: -7.45%) !default; - $cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -14.69%, $lightness: -49.41%) !default; - $cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; - $cardview-fluent-filter-panel-border-color: $base-border-color !default; - $cardview-fluent-filter-panel-text-color: $base-accent !default; - $cardview-fluent-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; $cardview-fluent-nodata-view-text-color: color.adjust($cardview-fluent-common-text-color, $lightness: -32.16%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-color: color.adjust($cardview-fluent-common-text-color, $lightness: -32.16%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: 3.92%, $space: hsl) !default; // Search highlight - $cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; $cardview-fluent-card__content__field-value--highlighted-text-color: $base-inverted-text-color !default; - $cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; } +/** +* $name 302. Divider color +* $type color +*/ +$cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; + +/** +* $name 306. "Empty cover" background color +* $type color +*/ +$cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; + +/** +* $name 307. "Empty cover" icon color +* $type color +*/ +$cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; + +/** +* $name 600. Background color +* $type color +*/ +$cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; + +/** +* $name 700. Background color +* $type color +*/ +$cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; + $cardview-bg-color: $cardview-fluent-common-bg-color !default; $cardview-link-color: $cardview-fluent-common-link-color !default; $cardview-text-color: $cardview-fluent-common-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss index a47f1f1d3df2..92390ea11dec 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-fluent-common-border-radius: null !default; +$cardview-fluent-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-common-text-weight: null !default; +$cardview-fluent-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-fluent-common-padding: null !default; // #region CARD -$cardview-fluent-card-min-width: null !default; +$cardview-fluent-card-min-width: 250px !default; -$cardview-fluent-card-border-width: null !default; +$cardview-fluent-card-border-width: 1px !default; /** * $name 202. Border radius @@ -67,7 +67,7 @@ $cardview-fluent-card__header__selection-checkbox-padding-right: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card__field-caption-weight: null !default; +$cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: null !default; @@ -90,7 +90,7 @@ $cardview-fluent-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card--selected__field-caption-weight: null !default; +$cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; // #endregion @@ -110,13 +110,13 @@ $cardview-fluent-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-fluent-header-panel__item-border-width: null !default; +$cardview-fluent-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-fluent-header-panel__item-border-radius: null !default; +$cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-vertical: null !default; @@ -127,16 +127,16 @@ $cardview-fluent-header-panel__item-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item-text-weight: null !default; +$cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; -$cardview-fluent-header-panel__item__icon-size: null !default; +$cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; $cardview-fluent-header-item__left-padding: null !default; $cardview-fluent-header-item__right-padding: null !default; @@ -150,14 +150,14 @@ $cardview-fluent-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--hovered-text-weight: null !default; +$cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // #endregion @@ -168,14 +168,14 @@ $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: null !defa * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--disabled-text-weight: null !default; +$cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // #endregion @@ -186,10 +186,10 @@ $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: null !def * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--dragged-text-weight: null !default; +$cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; -$cardview-fluent-header-item__sort-indicator__border-size: null; -$cardview-fluent-header-item__sort-indicator__border-radius: null; +$cardview-fluent-header-item__sort-indicator__border-size: null !default; +$cardview-fluent-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -199,7 +199,7 @@ $cardview-fluent-header-panel__dropzone-height: null !default; $cardview-fluent-header-panel__dropzone-top: null !default; -$cardview-fluent-header-panel__dropzone-border-width: null !default; +$cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -215,7 +215,7 @@ $cardview-fluent-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-fluent-filter-panel-border-radius: null !default; +$cardview-fluent-filter-panel-border-radius: 0 !default; // #endregion @@ -235,16 +235,16 @@ $cardview-fluent-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-fluent-card__content__field-value--highlighted-border-radius: null !default; +$cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-fluent-card__content__field-value--highlighted-border-padding: null !default; +$cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card__content__field-value--highlighted-text-weight: null !default; +$cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -259,21 +259,16 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau @if $size == "default" { // Common - $cardview-fluent-common-border-radius: 16px !default; - $cardview-fluent-common-text-weight: 400 !default; $cardview-fluent-common-cards-row-gap: 16px !default; $cardview-fluent-common-cards-column-gap: 16px !default; $cardview-fluent-common-gap: 24px !default; $cardview-fluent-common-padding: 24px !default; // Card - $cardview-fluent-card-min-width: 250px !default; - $cardview-fluent-card-border-width: 1px !default; $cardview-fluent-card-border-radius: 8px !default; $cardview-fluent-card__header-height: 48px !default; $cardview-fluent-card__header-text-size: 16px !default; $cardview-fluent-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: 180px !default; $cardview-fluent-card__cover--noimage__icon-size: 48px !default; $cardview-fluent-card__content-padding: 12px !default; @@ -281,9 +276,6 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-card__content__cell-padding-vertical: 6px !default; $cardview-fluent-card__content__cell-padding-horizontal: 12px !default; - // Card - selected - $cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; - // Header $cardview-fluent-header-gap: 0 !default; @@ -292,48 +284,25 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-header-panel--empty-line-height: 32px !default; // Header panel item - default - $cardview-fluent-header-panel__item-border-width: 1px !default; - $cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-horizontal: 8px !default; $cardview-fluent-header-panel__item-padding-vertical: 5px !default; $cardview-fluent-header-item__left-padding: 6px !default; $cardview-fluent-header-item__right-padding: 2px !default; - $cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; - $cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; - - // Header panel item - hovered - $cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; $cardview-fluent-header-item__sort-indicator__border-size: 2px !default; $cardview-fluent-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-height: 44px !default; $cardview-fluent-header-panel__dropzone-top: -6px !default; - $cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; $cardview-fluent-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-fluent-filter-panel-border-radius: 0 !default; - // Empty view $cardview-fluent-nodata-view__icon-container-size: 64px !default; $cardview-fluent-nodata-view__icon-container-margin: 12px !default; $cardview-fluent-nodata-view__icon-size: 40px !default; - // Search highlight - $cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-fluent-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-fluent-column-chooser__select-all-item-horizontal-padding: 14px !default; @@ -341,21 +310,16 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau @else if $size == "compact" { // Common - $cardview-fluent-common-border-radius: 16px !default; - $cardview-fluent-common-text-weight: 400 !default; $cardview-fluent-common-cards-row-gap: 12px !default; $cardview-fluent-common-cards-column-gap: 12px !default; $cardview-fluent-common-gap: 16px !default; $cardview-fluent-common-padding: 16px !default; // Card - $cardview-fluent-card-min-width: 250px !default; - $cardview-fluent-card-border-width: 1px !default; $cardview-fluent-card-border-radius: 6px !default; $cardview-fluent-card__header-height: 36px !default; $cardview-fluent-card__header-text-size: 14px !default; $cardview-fluent-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: 140px !default; $cardview-fluent-card__cover--noimage__icon-size: 32px !default; $cardview-fluent-card__content-padding: 8px !default; @@ -363,9 +327,6 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-card__content__cell-padding-vertical: 4px !default; $cardview-fluent-card__content__cell-padding-horizontal: 8px !default; - // Card - selected - $cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; - // Header $cardview-fluent-header-gap: 4px !default; @@ -374,48 +335,25 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-header-panel--empty-line-height: 24px !default; // Header panel item - default - $cardview-fluent-header-panel__item-border-width: 1px !default; - $cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-horizontal: 6px !default; $cardview-fluent-header-panel__item-padding-vertical: 3px !default; $cardview-fluent-header-item__left-padding: 3px !default; $cardview-fluent-header-item__right-padding: 1px !default; - $cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; - $cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; - - // Header panel item - hovered - $cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; $cardview-fluent-header-item__sort-indicator__border-size: 1px !default; $cardview-fluent-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-height: 32px !default; $cardview-fluent-header-panel__dropzone-top: -4px !default; - $cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; $cardview-fluent-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-fluent-filter-panel-border-radius: 0 !default; - // Empty view $cardview-fluent-nodata-view__icon-container-size: 48px !default; $cardview-fluent-nodata-view__icon-container-margin: 8px !default; $cardview-fluent-nodata-view__icon-size: 32px !default; - // Search highlight - $cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-fluent-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-fluent-column-chooser__select-all-item-horizontal-padding: 12px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss index 2d7ee1140d81..7f53705ade4e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss @@ -125,7 +125,7 @@ $chat-typingindicator-circle-bg-color: null !default; $chat-file-container-background-color: $base-bg !default; $chat-file-container-box-shadow: null !default; -$chat-file-secondary-color: null !default; +$chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: null !default; @@ -144,7 +144,6 @@ $chat-suggestions-box-shadow: null !default; $chat-typingindicator-circle-bg-color-center: color.adjust($base-bg, $lightness: -56.08%, $space: hsl) !default; $chat-file-container-box-shadow: 0 0 2px 0 color.change($base-shadow-color, $alpha: 0.12), 0 1px 2px 0 color.change($base-shadow-color, $alpha: 0.14) !default; - $chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: 0 4px 12px 0 color.change($base-shadow-color, $alpha: 0.04), 0 4px 24px 0 color.change($base-shadow-color, $alpha: 0.02) !default; @@ -176,7 +175,6 @@ $chat-suggestions-box-shadow: null !default; $chat-typingindicator-circle-bg-color-center: color.adjust($base-bg, $lightness: 43.92%, $space: hsl) !default; $chat-file-container-box-shadow: 0 0 2px 0 color.change($base-shadow-color, $alpha: 0.24), 0 1px 2px 0 color.change($base-shadow-color, $alpha: 0.28) !default; - $chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: 0 4px 24px 0 color.change($base-shadow-color, $alpha: 0.02), 0 4px 12px 0 color.change($base-shadow-color, $alpha: 0.04) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss index f8069845cec3..cad0368631dd 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss @@ -14,7 +14,7 @@ $chat-bubble-delete-gap: null !default; $chat-bubble-delete-icon-size: null !default; $chat-messagebox-padding: null !default; $chat-messagebox-textarea-padding: null !default; -$chat-messagebox-textarea-border-radius: null !default; +$chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-toolbar-min-height: null !default; $chat-messagebox-gap: 8px !default; $chat-messagebox-files-container-padding: 3px !default; @@ -32,7 +32,7 @@ $chat-information-edited-gap: 2px !default; $chat-alerts-padding-block: null !default; $chat-alerts-padding-inline: 8px !default; -$chat-alerts-row-gap: null !default; +$chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: null !default; $chat-suggestions-padding: null !default; @@ -57,12 +57,10 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; $chat-typingindicator-bubble-padding-inline: null !default; -$chat-typingindicator-bubble-border-radius: null !default; $chat-typingindicator-bubble-column-gap: 4px !default; $chat-typingindicator-circle-size: null !default; $chat-typingindicator-circle-size-center: null !default; @@ -90,14 +88,12 @@ $chat-bubble-gap: 8px !default; $chat-bubble-delete-gap: 4px !default; $chat-bubble-delete-icon-size: 20px !default; $chat-messagebox-padding: 20px !default; - $chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-textarea-padding: 12px !default; $chat-messagebox-toolbar-min-height: 32px !default; $chat-messagelist-padding: 20px !default; $chat-messagegroup-padding: 24px !default; $chat-alerts-padding-block: 12px !default; - $chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: 6px !default; $chat-suggestions-padding: 20px !default; @@ -116,8 +112,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 12px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; - $chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; $chat-confirmation-popup-toolbar-gap: 8px !default; @@ -130,14 +124,12 @@ $chat-bubble-gap: 8px !default; $chat-bubble-delete-gap: 2px !default; $chat-bubble-delete-icon-size: 16px !default; $chat-messagebox-padding: 16px !default; - $chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-textarea-padding: 8px !default; $chat-messagebox-toolbar-min-height: 24px !default; $chat-messagelist-padding: 16px !default; $chat-messagegroup-padding: 12px !default; $chat-alerts-padding-block: 8px !default; - $chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: 5px !default; $chat-suggestions-padding: 16px !default; @@ -156,10 +148,11 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 12) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; - $chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; $chat-confirmation-popup-toolbar-gap: 4px !default; $chat-file-icon-font-size: 16px !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; +$chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss index bef18943d2af..4259a2430fb2 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$fluent-colorview-alpha-width: $fluent-colorview-palette-width + 4; -$fluent-colorview-hue-scale-height: $fluent-colorview-palette-height; +$fluent-colorview-alpha-width: $fluent-colorview-palette-width + 4 !default; +$fluent-colorview-hue-scale-height: $fluent-colorview-palette-height !default; .dx-colorview-container { width: $fluent-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss index 07d36367e858..83f2458bf9b9 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss @@ -7,7 +7,7 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss index c9c93f884a25..7540a81f6463 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss @@ -9,7 +9,7 @@ @use "../../base/icon_fonts" as *; @use "../textEditor" as *; -$fluent-editor-custom-button-margin: 6px; +$fluent-editor-custom-button-margin: 6px !default; .dx-daterangebox { @include dx-dropdowneditor-button-icon-styling(daterangepicker); diff --git a/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss index 276fbf644f98..67bb91377d8d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss @@ -6,7 +6,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$fluent-dateview-roller-padding: math.div($fluent-dateview-roller-height - $fluent-dateview-roller-item-height, 2); +$fluent-dateview-roller-padding: math.div($fluent-dateview-roller-height - $fluent-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss index 9b8af08066b7..d7d9b61bed19 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss @@ -15,10 +15,10 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$fluent-diagram-toolbar-icon-size: $fluent-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; +$fluent-diagram-toolbar-icon-size: $fluent-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; -$diagram-toolbox-title-size: 18px; +$diagram-toolbox-title-size: 18px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss index b6f005a34d5d..b970e8c4cacd 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss @@ -6,12 +6,12 @@ $fluent-dropdownbutton-icon-spindown-right-size: null !default; @if $size == "default" { $fluent-dropdownbutton-action-horizontal-padding: 12px !default; - $fluent-dropdownbutton-icon-spindown-size: 24px; + $fluent-dropdownbutton-icon-spindown-size: 24px !default; $fluent-dropdownbutton-icon-spindown-right-size: 16px !default; } @else if $size == "compact" { $fluent-dropdownbutton-action-horizontal-padding: 8px !default; - $fluent-dropdownbutton-icon-spindown-size: 18px; + $fluent-dropdownbutton-icon-spindown-size: 18px !default; $fluent-dropdownbutton-icon-spindown-right-size: 12px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss index c21aed8508d0..1a767ca16beb 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss @@ -8,17 +8,17 @@ @use "../../base/dropDownEditor"; @use "../../base/dropDownEditor/mixins" as *; -$fluent-dropdowneditor-container-button-size: $fluent-base-icon-size; +$fluent-dropdowneditor-container-button-size: $fluent-base-icon-size !default; $fluent-dropdowneditor-invalid-badge-offset: $fluent-dropdowneditor-container-button-size + - $fluent-invalid-badge-halfhorizontal-padding; + $fluent-invalid-badge-halfhorizontal-padding !default; $fluent-dropdowneditor-invalid-badge-with-clear-offset: $fluent-dropdowneditor-container-button-size * 2 + - $fluent-invalid-badge-halfhorizontal-padding; + $fluent-invalid-badge-halfhorizontal-padding !default; -$fluent-dropdowneditor-invalid-badge-size: $fluent-invalid-badge-size + $fluent-invalid-badge-horizontal-padding; +$fluent-dropdowneditor-invalid-badge-size: $fluent-invalid-badge-size + $fluent-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $fluent-dropdowneditor-container-button-size; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss index 9c5aed4a9f7a..f0a6358c9177 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss @@ -16,14 +16,11 @@ $filemanager-file-item-hover-bg: $base-hover-bg !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; $filemanager-dirs-tree-item-focus-bg: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $mode == "light" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $mode == "dark" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss index c20681b38015..6d827e99f941 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss @@ -15,7 +15,7 @@ $filemanager-large-icon-size: null !default; $filemanager-progress-panel-title-padding: null !default; $filemanager-progress-box-button-close-margin-top: null !default; -$filemanager-toolbar-height: $fluent-toolbar-height; +$filemanager-toolbar-height: $fluent-toolbar-height !default; @if $size == "default" { $filemanager-toolbar-separator-height: 36px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss index 985d8a670669..feaf4bcd5aa4 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss @@ -7,8 +7,8 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$fluent-fileuploader-file-wrapper-border-size: 3px; -$fluent-fileuploader-file-container-boxshadow: 0 1px 3px 0 color.change($base-shadow-color, $alpha: 0.1); +$fluent-fileuploader-file-wrapper-border-size: 3px !default; +$fluent-fileuploader-file-container-boxshadow: 0 1px 3px 0 color.change($base-shadow-color, $alpha: 0.1) !default; .dx-fileuploader-wrapper { padding: $fluent-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss index fa118fa0c463..c41cfcdc2c1b 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss @@ -7,7 +7,7 @@ $fluent-fileuploader-file-cancel-button-margin-left: 4px !default; $fluent-fileuploader-file-container-button-size: null !default; $fluent-fileuploader-vertical-padding: 7px !default; -$fluent-fileuploader-files-container-padding: null !default; +$fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: null !default; $fluent-fileuploader-file-icon-font-size: null !default; $fluent-fileuploader-files-container-rounding: 4px !default; @@ -17,7 +17,6 @@ $fluent-fileuploader-files-container-rounding: 4px !default; $fluent-fileuploader-file-container-text-margin: 15px !default; $fluent-fileuploader-file-container-button-margin: 8px !default; $fluent-fileuploader-file-container-button-size: 32px !default; - $fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: 0 0 0 4px !default; $fluent-fileuploader-file-icon-font-size: 20px !default; } @@ -27,7 +26,6 @@ $fluent-fileuploader-files-container-rounding: 4px !default; $fluent-fileuploader-file-container-text-margin: 6px !default; $fluent-fileuploader-file-container-button-margin: 2px !default; $fluent-fileuploader-file-container-button-size: 28px !default; - $fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: 0 0 4px !default; $fluent-fileuploader-file-icon-font-size: 16px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss index 384113d46730..465839839624 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss @@ -1,6 +1,6 @@ @use "../sizes" as *; -$fluent-form-group-caption-bottom-margin: null !default; +$fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: null !default; $fluent-form-group-content-bottom-padding: null !default; $fluent-form-group-caption-font-size: null !default; @@ -17,7 +17,6 @@ $fluent-form-label-icon-size: $fluent-base-icon-size !default; $fluent-form-group-custom-caption-icon-margin: null !default; @if $size == "default" { - $fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: 16px !default; $fluent-form-group-content-bottom-padding: 16px !default; $fluent-form-group-caption-font-size: 20px !default; @@ -30,7 +29,6 @@ $fluent-form-group-custom-caption-icon-margin: null !default; } @else if $size == "compact" { - $fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: 12px !default; $fluent-form-group-content-bottom-padding: 12px !default; $fluent-form-group-caption-font-size: 16px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss index c6e20ce847e9..06d100805263 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss @@ -9,7 +9,7 @@ @use "../form/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss index c154f911500c..7ee20bfdcd22 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss @@ -34,7 +34,7 @@ $datagrid-drag-header-border-color: color.change($base-accent, $alpha: 0.5) !def $datagrid-drag-header-shadow-color: null !default; $datagrid-drag-header-second-shadow-color: null !default; $datagrid-header-drag-bg: $datagrid-base-background-color !default; -$header-filter-color: null !default; +$header-filter-color: $datagrid-columnchooser-item-color !default; /** * $name 51. Selected row background color @@ -75,7 +75,7 @@ $datagrid-row-hovered-color: $base-hover-color !default; $datagrid-cell-modified-border-color: $button-success-text-hover-bg !default; $datagrid-cell-removed-border-color: $datagrid-border-color !default; $datagrid-row-removed-bg: $datagrid-hover-bg !default; -$datagrid-cell-removed-text-color: null !default; +$datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; /** * $name 45. Invalid data faded color @@ -88,7 +88,7 @@ $datagrid-nodata-color: $base-label-color !default; * $name 90. Group row text color * $type color */ -$datagrid-group-row-color: null !default; +$datagrid-group-row-color: $datagrid-columnchooser-item-color !default; /** * $name 95. Group row background color @@ -159,8 +159,8 @@ $datagrid-text-stub-background-image-path: null !default; $datagrid-filter-panel-color: $base-accent !default; $datagrid-draggable-column-text-color: null !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity !default; @if $mode == "light" { @if $color == "blue" { @@ -172,31 +172,24 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-row-focused-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -1deg, $saturation: 0%, $lightness: 26.47%) !default; $datagrid-filter-row-background-color: $base-typography-bg !default; } - - $datagrid-group-row-color: $datagrid-columnchooser-item-color !default; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: -12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.1) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: $datagrid-columnchooser-bg !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; $datagrid-text-stub-background-image-path: data-uri("images/widgets/fluent/color-schemes/light/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-draggable-column-text-color: color.adjust($base-text-color, $lightness: 60%, $space: hsl) !default; } @if $mode == "dark" { $datagrid-row-focused-bg: #0c3b5e !default; $datagrid-filter-row-background-color: color.adjust($datagrid-base-background-color, $lightness: 2%, $space: hsl) !default; - $datagrid-group-row-color: $datagrid-columnchooser-item-color !default; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: 12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: color.adjust($datagrid-columnchooser-bg, $lightness: 3%, $space: hsl) !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; $datagrid-text-stub-background-image-path: data-uri("images/widgets/fluent/color-schemes/dark/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-draggable-column-text-color: color.adjust($base-text-color, $lightness: -63.9%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss index 26ed9c35829f..27842a42af13 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss @@ -28,11 +28,11 @@ @use "layout/cell" as *; @use "layout/aiChat" as *; -$fluent-grid-base-border-hidden: 1px solid transparent; -$fluent-grid-base-row-border: 1px solid transparent; -$fluent-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$fluent-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; -$fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-height; +$fluent-grid-base-border-hidden: 1px solid transparent !default; +$fluent-grid-base-row-border: 1px solid transparent !default; +$fluent-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$fluent-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; +$fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-height !default; @mixin grid-base($widget-name) { @include fluent-grid-base-cell($widget-name); diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss index caaa91c8ec2f..bc836c3c34a0 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss @@ -17,14 +17,13 @@ $fluent-grid-base-menu-item-padding: null !default; $fluent-grid-base-column-chooser-horizontal-padding: null !default; $fluent-grid-base-column-chooser-paddings: null !default; $fluent-grid-base-header-panel-padding: null !default; -$fluent-grid-base-grouppanel-item-margin: null !default; +$fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: null !default; $fluent-grid-base-command-edit-column-with-icons-width: null !default; $fluent-grid-base-adaptive-column-height: null !default; -$fluent-grid-base-treeview-search-margin: null !default; +$fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: null !default; $fluent-filter-row-between-editor-padding: null !default; -$fluent-command-column-additional-right-margin: null !default; $fluent-grid-base-treeview-select-all-item-offset: null !default; $fluent-grid-base-header-line-height: 18px !default; @@ -57,16 +56,13 @@ $fluent-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $fluent-grid-base-column-chooser-horizontal-padding: 24px !default; $fluent-grid-base-column-chooser-paddings: 16px $fluent-grid-base-column-chooser-horizontal-padding 9px !default; $fluent-grid-base-header-panel-padding: 8px 0 !default; - $fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: 115px !default; $fluent-grid-base-command-edit-column-with-icons-width: 93px !default; $fluent-grid-base-adaptive-column-height: 40px !default; - $fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: 17px !default; $fluent-filter-row-between-editor-padding: 12px !default; - $fluent-command-column-additional-right-margin: 0 !default; $fluent-grid-base-treeview-select-all-item-offset: 34px !default; @@ -92,16 +88,13 @@ $fluent-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $fluent-grid-base-column-chooser-horizontal-padding: 8px !default; $fluent-grid-base-column-chooser-paddings: 8px $fluent-grid-base-column-chooser-horizontal-padding 8px !default; $fluent-grid-base-header-panel-padding: 6px 0 !default; - $fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: 105px !default; $fluent-grid-base-command-edit-column-with-icons-width: 80px !default; $fluent-grid-base-adaptive-column-height: 26px !default; - $fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: 14px !default; $fluent-filter-row-between-editor-padding: 8px !default; - $fluent-command-column-additional-right-margin: 2px !default; $fluent-grid-base-treeview-select-all-item-offset: 28px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss index 1ec5dd5b0875..2c57c3b85b93 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss @@ -2,9 +2,9 @@ @use "colors" as *; @use "sizes" as *; -$fluent-grid-base-border-color: $datagrid-border-color; -$fluent-grid-base-cell-vertical-padding: math.round(math.div($fluent-grid-base-cell-height - $fluent-grid-base-cell-line-height, 2)); -$fluent-grid-base-cell-padding: $fluent-grid-base-cell-vertical-padding $fluent-grid-base-cell-horizontal-padding; -$fluent-grid-base-header-cell-vertical-padding: math.round(math.div($fluent-grid-base-header-height - $fluent-grid-base-header-line-height, 2)); -$fluent-grid-base-command-select-column-width: $fluent-grid-base-checkbox-size + 2 * $fluent-grid-base-cell-horizontal-padding; -$grid-masterdetail-padding: 16px; +$fluent-grid-base-border-color: $datagrid-border-color !default; +$fluent-grid-base-cell-vertical-padding: math.round(math.div($fluent-grid-base-cell-height - $fluent-grid-base-cell-line-height, 2)) !default; +$fluent-grid-base-cell-padding: $fluent-grid-base-cell-vertical-padding $fluent-grid-base-cell-horizontal-padding !default; +$fluent-grid-base-header-cell-vertical-padding: math.round(math.div($fluent-grid-base-header-height - $fluent-grid-base-header-line-height, 2)) !default; +$fluent-grid-base-command-select-column-width: $fluent-grid-base-checkbox-size + 2 * $fluent-grid-base-cell-horizontal-padding !default; +$grid-masterdetail-padding: 16px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss index 23463e560ea2..bf760a009072 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $fluent-base-icon-size; +$ai-chat-message-icon-size: $fluent-base-icon-size !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss index 8fc917a37748..256a14dca7d3 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss @@ -8,11 +8,11 @@ $informer-info-color: $base-text-color !default; $informer-info-background-color: null !default; @if $mode == "light" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -358deg, $saturation: 0.58%, $lightness: 46.67%); - $informer-info-background-color: color.adjust($base-text-color, $lightness: 74.90%, $space: hsl); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -358deg, $saturation: 0.58%, $lightness: 46.67%) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 74.90%, $space: hsl) !default; } @if $mode == "dark" { - $informer-error-background-color: color.adjust($base-danger, $saturation: -5.06%, $space: hsl, $lightness: -53.53%); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -76.08%, $space: hsl); + $informer-error-background-color: color.adjust($base-danger, $saturation: -5.06%, $space: hsl, $lightness: -53.53%) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -76.08%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss index 12d1d175891c..21aefb71d99d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss @@ -13,10 +13,10 @@ @use "../radioGroup/colors" as *; @use "../badge"; -$fluent-list-item-border-width: 1px; -$fluent-list-border-width: 1px; -$fluent-list-border: $fluent-list-border-width solid; -$fluent-list-item-border: $fluent-list-item-border-width solid; +$fluent-list-item-border-width: 1px !default; +$fluent-list-border-width: 1px !default; +$fluent-list-border: $fluent-list-border-width solid !default; +$fluent-list-item-border: $fluent-list-item-border-width solid !default; .dx-state-disabled { &.dx-list { diff --git a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss index f55789e57034..1f1279d587f3 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss @@ -1,3 +1,3 @@ @use "../colors" as *; -$menu-item-color-disabled: $base-foreground-disabled; +$menu-item-color-disabled: $base-foreground-disabled !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss index 63d09d1d7dbf..dd2ef1a21d2f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss @@ -3,7 +3,7 @@ $fluent-menu-item-line-height: null !default; $fluent-menu-item-with-icon-horizontal-padding: null !default; $fluent-menu-horizontal-padding: null !default; -$fluent-menu-icon-to-text-offset: 4px; +$fluent-menu-icon-to-text-offset: 4px !default; @if $size == "default" { $fluent-menu-item-line-height: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss index 09bdf6b8492b..a166d36fbe1f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss @@ -1,12 +1,4 @@ @use "../sizes" as *; $fluent-numberbox-spin-container-width: 24px !default; -$fluent-numberbox-touch-friendly-spin-container-width: null !default; - -@if $size == "default" { - $fluent-numberbox-touch-friendly-spin-container-width: 48px !default; -} - -@else if $size == "compact" { - $fluent-numberbox-touch-friendly-spin-container-width: 48px !default; -} +$fluent-numberbox-touch-friendly-spin-container-width: 48px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss index 3871987a599d..0afa3b10b188 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss @@ -32,12 +32,12 @@ $pivotgrid-button-top-padding: $pivotgrid-button-top-padding, ); -$fluent-pivotgrid-headers-font-size: 14px; -$fluent-pivotgrid-area-font-size: 13px; -$fluent-pivotgrid-chevron-icon-size: 20px; +$fluent-pivotgrid-headers-font-size: 14px !default; +$fluent-pivotgrid-area-font-size: 13px !default; +$fluent-pivotgrid-chevron-icon-size: 20px !default; -$fluent-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15); -$fluent-pivotgridfieldchooser-field-min-height: 36px; +$fluent-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15) !default; +$fluent-pivotgridfieldchooser-field-min-height: 36px !default; .dx-pivotgrid-fields-container { &.dx-drag { diff --git a/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss index 75423b3fb670..3f1362182fa1 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss @@ -8,7 +8,7 @@ @use "../../base/popup"; @use "../overlay"; -$fluent-popup-toolbar-label-font-size: $fluent-m-font-size; +$fluent-popup-toolbar-label-font-size: $fluent-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss index d2d88fcd38d3..5553608c8b33 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss @@ -12,8 +12,8 @@ $fluent-popup-toolbar-height: null !default; $fluent-popup-button-margin: 0 8px !default; $fluent-popup-toolbar-item-spacing: 8px !default; -$fluent-popup-content-shadow: null !default; -$fluent-popup-focused-content-shadow: null !default; +$fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; +$fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; @if $size == "default" { $fluent-popup-content-padding: 24px !default; @@ -24,9 +24,6 @@ $fluent-popup-focused-content-shadow: null !default; $fluent-popup-toolbar-horizontal-padding: 24px !default; $fluent-popup-bottom-toolbar-height: 48px !default; $fluent-popup-toolbar-height: 48px !default; - - $fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; - $fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; } @else if $size == "compact" { @@ -38,9 +35,6 @@ $fluent-popup-focused-content-shadow: null !default; $fluent-popup-toolbar-horizontal-padding: 16px !default; $fluent-popup-bottom-toolbar-height: 36px !default; $fluent-popup-toolbar-height: 36px !default; - - $fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; - $fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; } :root { diff --git a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss index 85165ce798f0..a81d38c91d00 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss @@ -18,9 +18,9 @@ $progressbar-range-disabled-bg: $base-foreground-disabled !default; $progressbar-range-indeterminate-color: $base-accent !default; $progressbar-range-invalid-color: $base-invalid-color !default; -$progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; -$progressbar-disabled-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-disabled-bg, transparent 95%; -$progressbar-invalid-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-invalid-color, transparent 95%; +$progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; +$progressbar-disabled-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-disabled-bg, transparent 95% !default; +$progressbar-invalid-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-invalid-color, transparent 95% !default; /** * $name 30. Label color diff --git a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss index 91677287e082..d093147ac5d1 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss @@ -3,7 +3,7 @@ $progressbar-height: null !default; $progressbar-border-radius: $base-border-radius !default; $progressbar-status-margin-top: null !default; -$progressbar-indeterminate-background-width: 90%; +$progressbar-indeterminate-background-width: 90% !default; @if $size == "default" { $progressbar-height: 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss index 47f7cd1cd4ed..ada30c55a57e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss @@ -1,14 +1,11 @@ @use "../sizes" as *; $fluent-radio-button-vertical-margin: null !default; -$fluent-radio-group-horizontal-gap: null !default; @if $size == "default" { - $fluent-radio-group-horizontal-gap: 16px !default; $fluent-radio-button-vertical-margin: 12px !default; } @else if $size == "compact" { - $fluent-radio-group-horizontal-gap: 12px !default; $fluent-radio-button-vertical-margin: 6px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss index a3bad1a3542a..cded1ffcecac 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss @@ -4,9 +4,9 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$fluent-recurrence-number-box-margin: 11px; -$fluent-recurrence-button-group-padding: 6px; -$fluent-recurrence-radio-group-margin: 15px; +$fluent-recurrence-number-box-margin: 11px !default; +$fluent-recurrence-button-group-padding: 6px !default; +$fluent-recurrence-radio-group-margin: 15px !default; .dx-recurrence-numberbox-interval { margin-right: $fluent-recurrence-number-box-margin; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss index c307c71d772d..edaae60d331c 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss @@ -23,7 +23,6 @@ $scheduler-accent-border-color: $base-border-color !default; * $type color */ $scheduler-appointment-text-color: null !default; -$scheduler-appointment-focus-color: null !default; /** * $name 40. Appointment background color @@ -36,7 +35,7 @@ $scheduler-appointment-base-color: null !default; * $type color */ $scheduler-appointment-start-color: color.change($base-shadow-color, $alpha: 0.3) !default; -$scheduler-appointment-active-color: $button-default-outlined-active-bg; +$scheduler-appointment-active-color: $button-default-outlined-active-bg !default; /** * $name 60. Focused cell color @@ -87,7 +86,7 @@ $header-panel-cell-date: null !default; @if $mode == "light" { $scheduler-appointment-text-color: #000 !default; - $header-panel-cell-date: #424242; + $header-panel-cell-date: #424242 !default; @if $color == "blue" { $scheduler-appointment-base-color: color.adjust($base-accent, $lightness: 48%, $space: hsl) !default; @@ -107,7 +106,7 @@ $header-panel-cell-date: null !default; @if $mode == "dark" { $scheduler-appointment-base-color: color.adjust($base-accent, $lightness: -48%, $space: hsl) !default; $scheduler-appointment-text-color: #fff !default; - $header-panel-cell-date: #adadad; + $header-panel-cell-date: #adadad !default; $scheduler-cell-hover-bg: $base-hover-bg !default; @if $color == "blue" { @@ -122,4 +121,4 @@ $header-panel-cell-date: null !default; } $scheduler-first-month-cell-background-color: color.adjust($scheduler-appointment-base-color, $alpha: -0.85) !default; -$scheduler-appointment-focus-color: $scheduler-appointment-text-color; +$scheduler-appointment-focus-color: $scheduler-appointment-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss index b7136eab49d5..28f18745f2d9 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss @@ -55,13 +55,13 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$fluent-scheduler-all-day-table-cell-height: $fluent-scheduler-workspace-date-table-cell-height * 1.5; +$fluent-scheduler-all-day-table-cell-height: $fluent-scheduler-workspace-date-table-cell-height * 1.5 !default; -$fluent-scheduler-month-date-text-size: 14px; +$fluent-scheduler-month-date-text-size: 14px !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$fluent-scheduler-agenda-time-panel-cell-padding: 8px; +$fluent-scheduler-agenda-time-panel-cell-padding: 8px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss index 6fb2abec8776..1db3d084feb0 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss @@ -4,8 +4,7 @@ @use "../toolbar/sizes" as *; $fluent-scheduler-workspace-date-table-cell-height: null !default; -$fluent-scheduler-toolbar-item-offset: null !default; -$fluent-scheduler-navigator-height: null !default; +$fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-header-panel-day-font-size: null !default; $fluent-scheduler-appointment-tooltip-title-font-size: null !default; $fluent-scheduler-appointment-tooltip-date-font-size: null !default; @@ -18,8 +17,8 @@ $fluent-scheduler-dropdown-button-height: null !default; $fluent-scheduler-dropdown-button-padding: null !default; $fluent-scheduler-dropdown-button-margin: null !default; $fluent-scheduler-dropdown-button-vertical-margin: null !default; -$fluent-scheduler-dropdown-button-adaptive-margin: null !default; -$fluent-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: null !default; $fluent-tooltip-marker-dimension: null !default; $fluent-scheduler-appointment-tooltip-title-margin-top: null !default; @@ -34,19 +33,19 @@ $fluent-scheduler-appointment-popup-all-day-item-height: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: null !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: null !default; -$fluent-scheduler-appointment-short-content-padding: null !default; -$fluent-scheduler-appointment-15min-height: null !default; -$fluent-scheduler-appointment-20min-height: null !default; -$fluent-scheduler-appointment-25min-height: null !default; -$fluent-scheduler-appointment-10min-recurrence-padding-right: null !default; -$fluent-scheduler-appointment-10min-title-font-size: null !default; -$fluent-scheduler-appointment-10min-title-line-height: null !default; -$fluent-scheduler-appointment-10min-icon-font-size: null !default; -$fluent-scheduler-appointment-10min-icon-right: null !default; +$fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; +$fluent-scheduler-appointment-15min-height: 17px !default; +$fluent-scheduler-appointment-20min-height: 24px !default; +$fluent-scheduler-appointment-25min-height: 32px !default; +$fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; +$fluent-scheduler-appointment-10min-title-font-size: 8px !default; +$fluent-scheduler-appointment-10min-title-line-height: 12px !default; +$fluent-scheduler-appointment-10min-icon-font-size: 12px !default; +$fluent-scheduler-appointment-10min-icon-right: 2px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: null !default; -$fluent-scheduler-appointment-15min-title-font-size: null !default; +$fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: null !default; -$fluent-scheduler-appointment-15min-icon-font-size: null !default; +$fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: null !default; $fluent-scheduler-appointment-20min-recurrence-padding-right: null !default; $fluent-scheduler-appointment-20min-icon-right: null !default; @@ -60,7 +59,7 @@ $fluent-scheduler-timeline-date-table-cell-height: 50px !default; */ $scheduler-border-radius: 0 !default; -$fluent-scheduler-navigator-icon-font-size: $fluent-base-icon-size; +$fluent-scheduler-navigator-icon-font-size: $fluent-base-icon-size !default; $fluent-scheduler-appointment-month-text-size: 12px !default; $fluent-scheduler-workspace-info-text-size: 16px !default; $fluent-scheduler-all-day-title-font-size: 12px !default; @@ -90,14 +89,12 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-collapsed-all-day-table-cell-height: 32px !default; $header-panel-time-cell-padding: 12px !default; - $header-day-element-height: 26px; + $header-day-element-height: 26px !default; $fluent-scheduler-workspace-date-table-cell-height: 38px !default; - $fluent-scheduler-navigator-height: 36px !default; $fluent-scheduler-header-panel-day-font-size: 16px !default; $fluent-scheduler-header-height: 46px !default; $fluent-scheduler-header-offset: 0 12px !default; - $fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-appointment-tooltip-title-font-size: 16px !default; $fluent-scheduler-appointment-tooltip-date-font-size: 14px !default; @@ -111,8 +108,6 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-dropdown-button-padding: 4px !default; $fluent-scheduler-dropdown-button-margin: 5px 1px 3px 3px !default; $fluent-scheduler-dropdown-button-vertical-margin: 5px 3px 3px 3px !default; - $fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: 6px !default; $fluent-tooltip-marker-dimension: 12px !default; @@ -128,23 +123,10 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: 12px !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: 32px !default; - $agenda-appointment-title-font-size: 14px; - - $fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; - $fluent-scheduler-appointment-15min-height: 17px !default; - $fluent-scheduler-appointment-20min-height: 24px !default; - $fluent-scheduler-appointment-25min-height: 32px !default; - - $fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-10min-title-font-size: 8px !default; - $fluent-scheduler-appointment-10min-title-line-height: 12px !default; - $fluent-scheduler-appointment-10min-icon-font-size: 12px !default; - $fluent-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 14px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: 18px !default; - $fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: 2px !default; $fluent-scheduler-appointment-20min-icon-right: 2px !default; @@ -155,14 +137,12 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-collapsed-all-day-table-cell-height: 24px !default; $header-panel-time-cell-padding: 8px !default; - $header-day-element-height: 24px; + $header-day-element-height: 24px !default; $fluent-scheduler-workspace-date-table-cell-height: 28px !default; - $fluent-scheduler-navigator-height: 28px !default; $fluent-scheduler-header-panel-day-font-size: 14px !default; $fluent-scheduler-header-height: 34px !default; $fluent-scheduler-header-offset: 2px 8px !default; - $fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-appointment-tooltip-title-font-size: 14px !default; $fluent-scheduler-appointment-tooltip-date-font-size: 11px !default; @@ -176,8 +156,6 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-dropdown-button-padding: 2px !default; $fluent-scheduler-dropdown-button-margin: 2px 1px 2px 3px !default; $fluent-scheduler-dropdown-button-vertical-margin: 2px 1px 2px 1px !default; - $fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: 8px !default; $fluent-tooltip-marker-dimension: 18px !default; @@ -193,23 +171,10 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: 8px !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: 24px !default; - $agenda-appointment-title-font-size: 13px; - - $fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; - $fluent-scheduler-appointment-15min-height: 17px !default; - $fluent-scheduler-appointment-20min-height: 24px !default; - $fluent-scheduler-appointment-25min-height: 32px !default; - - $fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-10min-title-font-size: 8px !default; - $fluent-scheduler-appointment-10min-title-line-height: 12px !default; - $fluent-scheduler-appointment-10min-icon-font-size: 12px !default; - $fluent-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 13px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: 16px !default; - $fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: 12px !default; - $fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: 5px !default; $fluent-scheduler-appointment-20min-recurrence-padding-right: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss index 12a4b458fc80..fa971352b542 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss @@ -6,7 +6,7 @@ @use "../scrollable"; @use "../../base/scrollView"; -$scrollview-loading-indicator-size: 42px; +$scrollview-loading-indicator-size: 42px !default; .dx-scrollview-scrollbottom-text, .dx-scrollview-pull-down-text { diff --git a/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss index 993a6b25a95a..0cfcdad10d93 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$fluent-scrollbar-size-thin: 6px; -$fluent-scrollbar-size: 13px; +$fluent-scrollbar-size-thin: 6px !default; +$fluent-scrollbar-size: 13px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss index 788adabaef81..b9af6d044e39 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss @@ -3,31 +3,22 @@ $fluent-slider-handle-size: null !default; $fluent-slider-wrapper-height: null !default; $fluent-slider-tooltip-width: null !default; -$fluent-slider-top-tooltip-margin: null !default; $fluent-slider-height: 28px !default; // for fieldset height $fluent-slider-radius: 50% !default; $fluent-slider-track-height: null !default; $fluent-slider-track-border-radius: 2px !default; -$fluent-slider-handle-height: null !default; -$fluent-slider-handle-inner-size: null !default; @if $size == "default" { $fluent-slider-track-height: 4px !default; - $fluent-slider-handle-height: 18px !default; $fluent-slider-handle-size: 20px !default; - $fluent-slider-handle-inner-size: 18px !default; $fluent-slider-wrapper-height: 32px !default; $fluent-slider-tooltip-width: 34px !default; - $fluent-slider-top-tooltip-margin: -5px !default; } @else if $size == "compact" { $fluent-slider-track-height: 2px !default; - $fluent-slider-handle-height: 14px !default; $fluent-slider-handle-size: 16px !default; - $fluent-slider-handle-inner-size: 14px !default; $fluent-slider-wrapper-height: 26px !default; $fluent-slider-tooltip-width: 28px !default; - $fluent-slider-top-tooltip-margin: -2px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss index 4ddec0093191..d1143ea3fcea 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss @@ -5,8 +5,8 @@ @use "../button/colors" as *; @use "../overlay"; -$fluent-fa-button-label-offset: 16px; -$fluent-fa-button-label-padding: 4px 8px; +$fluent-fa-button-label-offset: 16px !default; +$fluent-fa-button-label-padding: 4px 8px !default; @include dx-fa-button-mixin( $fluent-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss index 8862d4505a19..bfd200a0685b 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss @@ -8,13 +8,13 @@ $fluent-fa-button-size: null !default; @if $size == "default" { $fluent-main-fa-button-size: 44px !default; $fluent-fa-button-size: 44px !default; - $fluent-main-fa-button-horizontal-padding: 16px; - $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 8px; + $fluent-main-fa-button-horizontal-padding: 16px !default; + $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 8px !default; } @else if $size == "compact" { $fluent-main-fa-button-size: 32px !default; $fluent-fa-button-size: 32px !default; - $fluent-main-fa-button-horizontal-padding: 12px; - $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 4px; + $fluent-main-fa-button-horizontal-padding: 12px !default; + $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss index 44c8a9441194..31e5ce8db750 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss @@ -10,19 +10,19 @@ $splitter-resize-handle-color: null !default; * $name 30. Focused state icon color * $type color */ -$splitter-resize-handle-color-focused: null !default; +$splitter-resize-handle-color-focused: $base-inverted-text-color !default; /** * $name 40. Handle background color * $type color */ -$splitter-resize-handle-bg: null !default; +$splitter-resize-handle-bg: $base-border-color !default; /** * $name 50. Handle hover state background color * $type color */ -$splitter-resize-handle-bg-hover: null !default; +$splitter-resize-handle-bg-hover: $base-border-color-hover !default; /** * $name 60. Handle focused state background color @@ -32,18 +32,10 @@ $splitter-resize-handle-bg-focused: $base-accent !default; @if $mode == "light" { $splitter-resize-handle-color: $base-icon-color !default; - $splitter-resize-handle-color-focused: $base-inverted-text-color !default; - - $splitter-resize-handle-bg: $base-border-color !default; - $splitter-resize-handle-bg-hover: $base-border-color-hover !default; } @if $mode == "dark" { $splitter-resize-handle-color: $base-text-color !default; - $splitter-resize-handle-color-focused: $base-inverted-text-color !default; - - $splitter-resize-handle-bg: $base-border-color !default; - $splitter-resize-handle-bg-hover: $base-border-color-hover !default; } /** diff --git a/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss index a0e0b973b604..94d7ce69c89a 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 20px; +$splitter-resize-handle-icon-size: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss index f2dcd883f771..55c05689deca 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss @@ -5,9 +5,9 @@ @use "../../base/switch"; @use "../../base/switch/mixins" as *; -$fluent-switch-transition: all 0.2s cubic-bezier(0.33, 0, 0.67, 1); -$fluent-switch-handle-on-shadow: 0 0 0 $fluent-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1); -$fluent-switch-shadow: none; +$fluent-switch-transition: all 0.2s cubic-bezier(0.33, 0, 0.67, 1) !default; +$fluent-switch-handle-on-shadow: 0 0 0 $fluent-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1) !default; +$fluent-switch-shadow: none !default; @include dx-switch( $fluent-switch-width, diff --git a/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss index c2817e8f9714..8906ee1bef54 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss @@ -3,11 +3,9 @@ $fluent-switch-width: null !default; $switch-handle-margin: null !default; $switch-padding: 8px !default; -$switch-container-vertical-margin: null !default; -$switch-container-horizontal-padding: null !default; +$switch-container-vertical-margin: 0 !default; $fluent-switch-height: null !default; $switch-handle-height: null !default; -$switch-container-height: null !default; $fluent-switch-shadow-blur-radius: 0 !default; $fluent-switch-container-adjustment: 6px !default; @@ -16,18 +14,15 @@ $fluent-switch-container-adjustment: 6px !default; $fluent-switch-width: 40px !default; $switch-handle-height: 14px !default; $switch-handle-margin: 3px !default; - $switch-container-vertical-margin: 0 !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $fluent-switch-height: 20px !default; - $switch-container-height: $fluent-switch-height !default; } @else if $size == "compact" { $fluent-switch-width: 32px !default; $switch-handle-height: 12px !default; $switch-handle-margin: 2px !default; - $switch-container-vertical-margin: 0 !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $fluent-switch-height: 16px !default; - $switch-container-height: $fluent-switch-height !default; } + +$switch-container-horizontal-padding: $switch-handle-margin !default; +$switch-container-height: $fluent-switch-height !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss index 1793c8ce0a4a..0c73f52e0640 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss @@ -1,22 +1,21 @@ @use "../../sizes" as *; $fluent-tabs-item-gap: 4px !default; -$fluent-tabs-item-padding: null !default; $fluent-tabs-item-padding-top-bottom: null !default; $fluent-tabs-item-padding-right-left: null !default; -$fluent-tab-height: null !default; +$fluent-tab-height: auto !default; $fluent-tab-font-weight-basic: 400 !default; $fluent-tab-font-weight-accent: 500 !default; -$fluent-base-icon-margin: null !default; +$fluent-base-icon-margin: 8px !default; $fluent-tabs-icon-size: $fluent-base-icon-size !default; $fluent-tab-nav-button-icon-width: null !default; $fluent-tab-nav-button-icon-height: null !default; $fluent-tab-nav-button-icon-font-size: null !default; -$fluent-tab-nav-button-content-padding: null !default; +$fluent-tab-nav-button-content-padding: 0 4px !default; -$fluent-tab-vertical-nav-button-content-padding: null !default; +$fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-indicator-thickness-primary: 2px !default; $fluent-indicator-thickness-secondary: 3px !default; @@ -32,27 +31,19 @@ $fluent-badge-min-size: null !default; @if $size == "default" { $fluent-tabs-item-padding-top-bottom: 12px !default; $fluent-tabs-item-padding-right-left: 10px !default; - $fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; - $fluent-tab-height: auto !default; - $fluent-base-icon-margin: 8px !default; $fluent-tab-nav-button-icon-width: 20px !default; $fluent-tab-nav-button-icon-height: 20px !default; $fluent-tab-nav-button-icon-font-size: 20px !default; - $fluent-tab-nav-button-content-padding: 0 4px !default; - $fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-badge-min-size: 20px !default; } @else if $size == "compact" { $fluent-tabs-item-padding-top-bottom: 8px !default; $fluent-tabs-item-padding-right-left: 8px !default; - $fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; - $fluent-tab-height: auto !default; - $fluent-base-icon-margin: 8px !default; $fluent-tab-nav-button-icon-width: 16px !default; $fluent-tab-nav-button-icon-height: 16px !default; $fluent-tab-nav-button-icon-font-size: 16px !default; - $fluent-tab-nav-button-content-padding: 0 4px !default; - $fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-badge-min-size: 16px !default; } + +$fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss index 214f072e13de..92052e567b9e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss @@ -1,7 +1,7 @@ @use "../sizes" as *; $fluent-tagbox-tag-content-padding-left: null !default; -$fluent-tagbox-tag-content-padding-right: null !default; +$fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: null !default; $fluent-tagbox-tag-content-font-size: null !default; $fluent-tagbox-tag-container-padding-left: null !default; @@ -17,7 +17,6 @@ $fluent-tagbox-tag-container-gap: null !default; @if $size == "default" { $fluent-tagbox-tag-content-padding-left: 6px !default; - $fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: 2px !default; $fluent-tagbox-tag-content-font-size: 12px !default; $fluent-tagbox-tag-container-padding-left: 12px !default; @@ -31,7 +30,6 @@ $fluent-tagbox-tag-container-gap: null !default; @else if $size == "compact" { $fluent-tagbox-tag-content-padding-left: 4px !default; - $fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: 0 !default; $fluent-tagbox-tag-content-font-size: 10px !default; $fluent-tagbox-tag-container-padding-left: 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss index b711bc2cce9d..40f6e1def3da 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss @@ -87,20 +87,20 @@ $texteditor-input-border-radius: $base-border-radius !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $mode == "light" { + $texteditor-outlined-bg: $base-element-bg !default; + @if $color == "blue" { $texteditor-color: $base-text-color !default; $texteditor-placeholder-color: color.adjust($base-text-color, $lightness: 29.80%, $space: hsl) !default; - $texteditor-outlined-bg: $base-element-bg !default; $texteditor-filled-bg: color.adjust($base-element-bg, $lightness: -3.9%, $space: hsl) !default; } @if $color == "saas" { $texteditor-color: rgba(66, 81, 118, 1) !default; $texteditor-placeholder-color: rgba(144, 158, 192, 1) !default; - $texteditor-outlined-bg: $base-element-bg !default; $texteditor-filled-bg: rgba(243, 245, 248, 1) !default; } @@ -141,8 +141,8 @@ $texteditor-filled-bg-hover: $texteditor-filled-bg !default; */ $texteditor-filled-bg-focused: $texteditor-filled-bg !default; -$texteditor-label-color: $texteditor-placeholder-color; -$texteditor-label-color-focused: $base-accent; +$texteditor-label-color: $texteditor-placeholder-color !default; +$texteditor-label-color-focused: $base-accent !default; :root { --dx-texteditor-color-text: #{$texteditor-color}; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss index 6ef8c31da61a..463672eeeafe 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss @@ -12,10 +12,10 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$fluent-base-standard-texteditor-input-horizontal-padding: 0; -$fluent-texteditor-invalid-badge-size: $fluent-invalid-badge-size + 2 * $fluent-invalid-badge-horizontal-padding; -$fluent-editor-custom-button-margin: 6px; -$fluent-editor-border-width: 1px; +$fluent-base-standard-texteditor-input-horizontal-padding: 0 !default; +$fluent-texteditor-invalid-badge-size: $fluent-invalid-badge-size + 2 * $fluent-invalid-badge-horizontal-padding !default; +$fluent-editor-custom-button-margin: 6px !default; +$fluent-editor-border-width: 1px !default; @mixin texteditor-outlined-styling() { background-color: $texteditor-outlined-bg; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss index eabf7d1cf17a..d49693d85091 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss @@ -4,13 +4,13 @@ $fluent-texteditor-line-height: null !default; $fluent-texteditor-font-size: null !default; $fluent-texteditor-input-height: null !default; -$fluent-texteditor-label-top-offset: null !default; +$fluent-texteditor-label-top-offset: -1px !default; $fluent-texteditor-label-outside-top-offset: null !default; $fluent-filled-texteditor-input-horizontal-padding: null !default; $fluent-filled-texteditor-input-top-padding: null !default; $fluent-filled-texteditor-input-bottom-padding: null !default; $fluent-standard-texteditor-input-top-padding: null !default; -$fluent-underlined-editor-button-height: null !default; +$fluent-underlined-editor-button-height: 22px !default; $fluent-texteditor-label-font-size: null !default; $fluent-texteditor-label-outside-height: null !default; $fluent-standard-texteditor-input-bottom-padding: null !default; @@ -27,7 +27,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-texteditor-label-outside-height: 20px !default; $fluent-texteditor-label-outside-top-offset: 24px !default; $fluent-texteditor-label-font-size: 12px !default; - $fluent-texteditor-label-top-offset: -1px !default; $fluent-filled-texteditor-input-horizontal-padding: 12px !default; $fluent-filled-texteditor-input-top-padding: 5px !default; $fluent-filled-texteditor-input-with-label-top-padding: 8px !default; @@ -37,7 +36,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-standard-texteditor-input-bottom-padding: 5px !default; $fluent-standard-texteditor-with-label-input-top-padding: 9px !default; $fluent-standard-texteditor-with-label-input-bottom-padding: 3px !default; - $fluent-underlined-editor-button-height: 22px !default; } @else if $size == "compact" { @@ -47,7 +45,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-texteditor-label-outside-height: 16px !default; $fluent-texteditor-label-outside-top-offset: 18px !default; $fluent-texteditor-label-font-size: 10px !default; - $fluent-texteditor-label-top-offset: -1px !default; $fluent-filled-texteditor-input-horizontal-padding: 8px !default; $fluent-filled-texteditor-input-top-padding: 3px !default; $fluent-filled-texteditor-input-with-label-top-padding: 6px !default; @@ -57,7 +54,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-standard-texteditor-input-bottom-padding: 3px !default; $fluent-standard-texteditor-with-label-input-top-padding: 6px !default; $fluent-standard-texteditor-with-label-input-bottom-padding: 2px !default; - $fluent-underlined-editor-button-height: 22px !default; } $fluent-filled-texteditor-input-padding: diff --git a/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss index 62ab9b9c048a..80bebbfef54f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss @@ -2,16 +2,14 @@ @use "../sizes" as *; $fluent-tooltip-padding: null !default; -$fluent-tooltip-font-size: null !default; +$fluent-tooltip-font-size: 12px !default; $fluent-tooltip-arrow-border-radius: 2px !default; $fluent-tooltip-shadow: 0 0 2px 0 $base-dropdown-shadow-color1 !default; @if $size == "default" { $fluent-tooltip-padding: 7px 11px !default; - $fluent-tooltip-font-size: 12px; } @else if $size == "compact" { $fluent-tooltip-padding: 3px 5px !default; - $fluent-tooltip-font-size: 12px; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss index 8f2518ae8958..8cb50f05e334 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss @@ -1,18 +1,17 @@ @use "../sizes" as *; $fluent-treeview-toggle-item-width: null !default; -$fluent-treeview-item-height: null !default; $fluent-checkbox-container-size: null !default; -$fluent-treeview-container-with-border-offset: 8px; +$fluent-treeview-container-with-border-offset: 8px !default; $fluent-treeview-item-element-gap: 8px !default; -$fluent-treeview-item-margin: 1px 0; -$fluent-treeview-item-border-radius: 4px; -$fluent-treeview-border-padding: 1px; +$fluent-treeview-item-margin: 1px 0 !default; +$fluent-treeview-item-border-radius: 4px !default; +$fluent-treeview-border-padding: 1px !default; $fluent-treeview-container-padding: $fluent-treeview-border-padding $fluent-treeview-border-padding $fluent-treeview-border-padding - $fluent-treeview-container-with-border-offset; + $fluent-treeview-container-with-border-offset !default; $fluent-treeview-checkbox-offset: null !default; $fluent-select-all-item-padding: null !default; @@ -24,7 +23,6 @@ $fluent-treeview-item-side-padding: 12px !default; $fluent-select-all-item-border-visible-padding: 26px !default; @if $size == "default" { - $fluent-treeview-item-height: 44px !default; $fluent-treeview-toggle-item-width: 20px !default; $fluent-checkbox-container-size: 20px !default; $fluent-treeview-checkbox-offset: 42px !default; @@ -32,11 +30,10 @@ $fluent-select-all-item-border-visible-padding: 26px !default; $fluent-treeview-item-padding: 12px $fluent-treeview-item-side-padding !default; $fluent-treeview-load-indicator-size: 20px !default; $fluent-treeview-searchbox-margin-bottom: 8px !default; - $fluent-treeview-search-editor-height: 48px; + $fluent-treeview-search-editor-height: 48px !default; } @else if $size == "compact" { - $fluent-treeview-item-height: 32px !default; $fluent-treeview-toggle-item-width: 16px !default; $fluent-treeview-checkbox-offset: 36px !default; $fluent-checkbox-container-size: 16px !default; @@ -44,5 +41,5 @@ $fluent-select-all-item-border-visible-padding: 26px !default; $fluent-treeview-item-padding: 10px $fluent-treeview-item-side-padding !default; $fluent-treeview-load-indicator-size: 16px !default; $fluent-treeview-searchbox-margin-bottom: 4px !default; - $fluent-treeview-search-editor-height: 32px; + $fluent-treeview-search-editor-height: 32px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss index 554959097d56..7a73a5756f5e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss @@ -4,7 +4,7 @@ $fluent-xl-font-size: null !default; $fluent-l-font-size: null !default; $fluent-m-font-size: null !default; $fluent-s-font-size: null !default; -$fluent-xs-font-size: null !default; +$fluent-xs-font-size: 12px !default; $fluent-h1-font-size: null !default; $fluent-h2-font-size: null !default; $fluent-h3-font-size: null !default; @@ -17,7 +17,6 @@ $fluent-h6-font-size: null !default; $fluent-l-font-size: 28px !default; $fluent-m-font-size: 20px !default; $fluent-s-font-size: 18px !default; - $fluent-xs-font-size: 12px !default; $fluent-h1-font-size: 40px !default; $fluent-h2-font-size: 36px !default; @@ -32,7 +31,6 @@ $fluent-h6-font-size: null !default; $fluent-l-font-size: 20px !default; $fluent-m-font-size: 16px !default; $fluent-s-font-size: 14px !default; - $fluent-xs-font-size: 12px !default; $fluent-h1-font-size: 32px !default; $fluent-h2-font-size: 28px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss index 408e81922fdd..f46cec6854f2 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss @@ -1,6 +1,5 @@ @use "../sizes" as *; -$fluent-validation-message-padding: null !default; $fluent-validationsummary-item-margin: null !default; $fluent-validation-message-line-height: null !default; $fluent-validation-message-horizontal-padding: null !default; @@ -10,7 +9,6 @@ $fluent-validation-message-vertical-padding: 4px !default; $fluent-validation-message-font-size: 12px !default; @if $size == "default" { - $fluent-validation-message-padding: 6px !default; $fluent-validationsummary-item-margin: 6px !default; $fluent-validation-message-horizontal-padding: 8px !default; @@ -18,7 +16,6 @@ $fluent-validation-message-font-size: 12px !default; } @else if $size == "compact" { - $fluent-validation-message-padding: 4px !default; $fluent-validationsummary-item-margin: 4px !default; $fluent-validation-message-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/_sizes.scss index bfcfe2484d0a..3ad8c9f221f2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/_sizes.scss @@ -69,7 +69,7 @@ $generic-base-inline-borderedwidget-bottom-padding: $generic-base-inline-widget-bottom-padding - $generic-base-border-width !default; -$generic-treeview-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width; +$generic-treeview-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width !default; :root { --dx-component-height: #{$generic-base-inline-widget-height}; diff --git a/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss index 8c648c6e3c96..d8302f0516c3 100644 --- a/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss @@ -4,7 +4,6 @@ $generic-button-text-line-height: null !default; $generic-button-vertical-padding: null !default; $generic-button-horizontal-padding: null !default; $generic-button-icon-horizontal-padding: null !default; -$generic-button-box-shadow-size: null !default; $generic-button-border-weight: $generic-base-border-width !default; $generic-button-content-line-height: $generic-base-line-height !default; @@ -14,7 +13,6 @@ $generic-button-content-line-height: $generic-base-line-height !default; $generic-button-vertical-padding: 8px !default; $generic-button-horizontal-padding: 12px !default; $generic-button-icon-horizontal-padding: 8px !default; - $generic-button-box-shadow-size: 0 1px 3px !default; } @else if $size == "compact" { @@ -22,7 +20,6 @@ $generic-button-content-line-height: $generic-base-line-height !default; $generic-button-vertical-padding: 4px !default; $generic-button-horizontal-padding: 10px !default; $generic-button-icon-horizontal-padding: 5px !default; - $generic-button-box-shadow-size: 0 1px 2px !default; } :root { diff --git a/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss index e1f0ed77f527..561619679484 100644 --- a/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-generic-common-border-radius: null !default; +$cardview-generic-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-common-text-weight: null !default; +$cardview-generic-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-generic-common-padding: null !default; // #region CARD -$cardview-generic-card-min-width: null !default; +$cardview-generic-card-min-width: 250px !default; -$cardview-generic-card-border-width: null !default; +$cardview-generic-card-border-width: 1px !default; /** * $name 202. Border radius @@ -67,7 +67,7 @@ $cardview-generic-card__header__selection-checkbox-padding-right: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card__field-caption-weight: null !default; +$cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: null !default; @@ -90,13 +90,13 @@ $cardview-generic-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card--selected__field-caption-weight: null !default; +$cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; // #endregion // #region HEADER -$cardview-generic-header-gap: null !default; +$cardview-generic-header-gap: 12px !default; // #endregion @@ -110,13 +110,13 @@ $cardview-generic-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-generic-header-panel__item-border-width: null !default; +$cardview-generic-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-generic-header-panel__item-border-radius: null !default; +$cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-vertical: null !default; @@ -129,16 +129,16 @@ $cardview-generic-header-panel__item-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item-text-weight: null !default; +$cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; -$cardview-generic-header-panel__item__icon-size: null !default; +$cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; $cardview-generic-header-item__left-padding: null !default; $cardview-generic-header-item__right-padding: null !default; @@ -152,14 +152,14 @@ $cardview-generic-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--hovered-text-weight: null !default; +$cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // #endregion @@ -170,14 +170,14 @@ $cardview-generic-header-panel__item--hovered__sort-index-text-weight: null !def * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--disabled-text-weight: null !default; +$cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // #endregion @@ -188,10 +188,10 @@ $cardview-generic-header-panel__item--disabled__sort-index-text-weight: null !de * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--dragged-text-weight: null !default; +$cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; -$cardview-generic-header-item__sort-indicator__border-size: null; -$cardview-generic-header-item__sort-indicator__border-radius: null; +$cardview-generic-header-item__sort-indicator__border-size: null !default; +$cardview-generic-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -199,9 +199,9 @@ $cardview-generic-header-item__sort-indicator__border-radius: null; $cardview-generic-header-panel__dropzone-height: null !default; -$cardview-generic-header-panel__dropzone-top: null !default; +$cardview-generic-header-panel__dropzone-top: 0 !default; -$cardview-generic-header-panel__dropzone-border-width: null !default; +$cardview-generic-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -217,7 +217,7 @@ $cardview-generic-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-generic-filter-panel-border-radius: null !default; +$cardview-generic-filter-panel-border-radius: 0 !default; // #endregion @@ -237,16 +237,16 @@ $cardview-generic-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-generic-card__content__field-value--highlighted-border-radius: null !default; +$cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-generic-card__content__field-value--highlighted-border-padding: null !default; +$cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card__content__field-value--highlighted-text-weight: null !default; +$cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -261,21 +261,16 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa @if $size == "default" { // Common - $cardview-generic-common-border-radius: 16px !default; - $cardview-generic-common-text-weight: 400 !default; $cardview-generic-common-cards-row-gap: 16px !default; $cardview-generic-common-cards-column-gap: 16px !default; $cardview-generic-common-gap: 20px !default; $cardview-generic-common-padding: 24px !default; // Card - $cardview-generic-card-min-width: 250px !default; - $cardview-generic-card-border-width: 1px !default; $cardview-generic-card-border-radius: 8px !default; $cardview-generic-card__header-height: 52px !default; $cardview-generic-card__header-text-size: 16px !default; $cardview-generic-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: 180px !default; $cardview-generic-card__cover--noimage__icon-size: 48px !default; $cardview-generic-card__content-padding: 12px !default; @@ -283,60 +278,30 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa $cardview-generic-card__content__cell-padding-vertical: 6px !default; $cardview-generic-card__content__cell-padding-horizontal: 12px !default; - // Card - selected - $cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; - - // Header - $cardview-generic-header-gap: 12px !default; - // Header panel $cardview-generic-header-panel-vertical-margin: 4px !default; $cardview-generic-header-panel--empty-line-height: 36px !default; // Header panel item - default - $cardview-generic-header-panel__item-border-width: 1px !default; - $cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-horizontal: 12px !default; $cardview-generic-header-panel__item-padding-vertical: 8px !default; $cardview-generic-header-item__left-padding: 6px !default; $cardview-generic-header-item__right-padding: 2px !default; $cardview-generic-header-panel__item-line-height: 18px !default; - $cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; - $cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; - - // Header panel item - hovered - $cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; $cardview-generic-header-item__sort-indicator__border-size: 2px !default; $cardview-generic-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-generic-header-panel__dropzone-height: 36px !default; - $cardview-generic-header-panel__dropzone-top: 0 !default; - $cardview-generic-header-panel__dropzone-border-width: 1.5px !default; $cardview-generic-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-generic-filter-panel-border-radius: 0 !default; - // Empty view $cardview-generic-nodata-view__icon-container-size: 60px !default; $cardview-generic-nodata-view__icon-container-margin: 12px !default; $cardview-generic-nodata-view__icon-size: 36px !default; - // Search highlight - $cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-generic-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-generic-column-chooser__select-all-item-horizontal-padding: 10px !default; @@ -344,21 +309,16 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa @else if $size == "compact" { // Common - $cardview-generic-common-border-radius: 16px !default; - $cardview-generic-common-text-weight: 400 !default; $cardview-generic-common-cards-row-gap: 12px !default; $cardview-generic-common-cards-column-gap: 12px !default; $cardview-generic-common-gap: 16px !default; $cardview-generic-common-padding: 16px !default; // Card - $cardview-generic-card-min-width: 250px !default; - $cardview-generic-card-border-width: 1px !default; $cardview-generic-card-border-radius: 6px !default; $cardview-generic-card__header-height: 48px !default; $cardview-generic-card__header-text-size: 14px !default; $cardview-generic-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: 140px !default; $cardview-generic-card__cover--noimage__icon-size: 32px !default; $cardview-generic-card__content-padding: 8px !default; @@ -366,60 +326,30 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa $cardview-generic-card__content__cell-padding-vertical: 4px !default; $cardview-generic-card__content__cell-padding-horizontal: 8px !default; - // Card - selected - $cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; - - // Header - $cardview-generic-header-gap: 12px !default; - // Header panel $cardview-generic-header-panel-vertical-margin: 0 !default; $cardview-generic-header-panel--empty-line-height: 24px !default; // Header panel item - default - $cardview-generic-header-panel__item-border-width: 1px !default; - $cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-horizontal: 8px !default; $cardview-generic-header-panel__item-padding-vertical: 5px !default; $cardview-generic-header-item__left-padding: 3px !default; $cardview-generic-header-item__right-padding: 1px !default; $cardview-generic-header-panel__item-line-height: 14px !default; - $cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; - $cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; - - // Header panel item - hovered - $cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; $cardview-generic-header-item__sort-indicator__border-size: 1px !default; $cardview-generic-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-generic-header-panel__dropzone-height: 24px !default; - $cardview-generic-header-panel__dropzone-top: 0 !default; - $cardview-generic-header-panel__dropzone-border-width: 1.5px !default; $cardview-generic-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-generic-filter-panel-border-radius: 0 !default; - // Empty view $cardview-generic-nodata-view__icon-container-size: 40px !default; $cardview-generic-nodata-view__icon-container-margin: 8px !default; $cardview-generic-nodata-view__icon-size: 24px !default; - // Search highlight - $cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-generic-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-generic-column-chooser__select-all-item-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss index d7d7f7342931..5c6de4412cf1 100644 --- a/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss @@ -59,7 +59,6 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; @@ -119,7 +118,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 12px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 0 !default; $chat-confirmation-popup-toolbar-gap: 8px !default; @@ -160,10 +158,11 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 12) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: $generic-popup-content-padding !default; $chat-confirmation-popup-toolbar-gap: 4px !default; $chat-file-icon-font-size: 14 !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss b/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss index 740205c5095b..9a808cc9f92a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss @@ -4,7 +4,7 @@ @use "../../base/checkBox"; @use "../../base/checkBox/mixins" as *; -$generic-checkbox-disable-opacity: 0.4; +$generic-checkbox-disable-opacity: 0.4 !default; .dx-checkbox { line-height: 0; diff --git a/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss index 4d759dc73135..bda37bf216e9 100644 --- a/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss @@ -5,7 +5,7 @@ $generic-checkbox-icon-font-size: null !default; $generic-checkbox-border-width: 1px !default; -$generic-checkbox-border-radius: null !default; +$generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; $generic-checkbox-arrow-icon-size: 1em !default; $generic-checkbox-arrow-font-size: null !default; @@ -16,12 +16,10 @@ $generic-checkbox-indeterminate-icon-border-radius: 2px !default; $generic-checkbox-icon-font-size: 22px !default; $generic-checkbox-arrow-font-size: 0.727273em !default; $generic-checkbox-indeterminate-icon-size: 0.545455em !default; - $generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; } @else if $size == "compact" { $generic-checkbox-icon-font-size: 16px !default; $generic-checkbox-arrow-font-size: 0.625em !default; $generic-checkbox-indeterminate-icon-size: 0.5em !default; - $generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss index 05284c86677b..d304e8f7c8d0 100644 --- a/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$generic-colorview-alpha-width: $generic-colorview-palette-width + 4; -$generic-colorview-hue-scale-height: $generic-colorview-palette-height; +$generic-colorview-alpha-width: $generic-colorview-palette-width + 4 !default; +$generic-colorview-hue-scale-height: $generic-colorview-palette-height !default; .dx-colorview-container { width: $generic-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss index c4e2389d750a..004e85bb20cb 100644 --- a/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss @@ -6,10 +6,10 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; $generic-dropdownlist-min-height: $generic-base-font-size * $generic-base-line-height + - 2 * $generic-list-item-vertical-padding; + 2 * $generic-list-item-vertical-padding !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss b/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss index 4fe335a1b57b..5dddf82d1e49 100644 --- a/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss @@ -8,7 +8,7 @@ @use "../overlay"; @use "../menuBase" as *; // need as private -$generic-menu-popup-border: 1px solid; +$generic-menu-popup-border: 1px solid !default; .dx-context-menu { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss index f769f5703721..9a546976aa44 100644 --- a/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss @@ -5,7 +5,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$generic-dateview-roller-padding: math.div($generic-dateview-roller-height - $generic-dateview-roller-item-height, 2); +$generic-dateview-roller-padding: math.div($generic-dateview-roller-height - $generic-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss index db120bcabaf2..f226c1eabec4 100644 --- a/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss @@ -14,11 +14,11 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$generic-diagram-toolbar-icon-size: $generic-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; -$diagram-touchbar-item-padding: 5px; +$generic-diagram-toolbar-icon-size: $generic-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; +$diagram-touchbar-item-padding: 5px !default; -$diagram-toolbox-title-size: 12px; +$diagram-toolbox-title-size: 12px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss index e9ba41d0d338..6656681ab031 100644 --- a/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss @@ -8,20 +8,20 @@ @use "../../base/icon_fonts" as *; @use "../../base/dropDownEditor"; -$generic-dropdowneditor-container-button-size: $generic-base-inline-borderedwidget-inner-size; -$generic-dropdowneditor-button-offset: $generic-base-border-width; +$generic-dropdowneditor-container-button-size: $generic-base-inline-borderedwidget-inner-size !default; +$generic-dropdowneditor-button-offset: $generic-base-border-width !default; $generic-dropdowneditor-button-size: $generic-dropdowneditor-container-button-size - - $generic-dropdowneditor-button-offset * 2; + $generic-dropdowneditor-button-offset * 2 !default; $generic-dropdowneditor-invalid-badge-offset: $generic-dropdowneditor-container-button-size + - $generic-invalid-badge-halfhorizontal-padding; + $generic-invalid-badge-halfhorizontal-padding !default; -$generic-dropdowneditor-invalid-badge-with-clear-offset: $generic-dropdowneditor-container-button-size * 2; +$generic-dropdowneditor-invalid-badge-with-clear-offset: $generic-dropdowneditor-container-button-size * 2 !default; -$generic-dropdowneditor-invalid-badge-size: $generic-invalid-badge-size + $generic-invalid-badge-horizontal-padding; +$generic-dropdowneditor-invalid-badge-size: $generic-invalid-badge-size + $generic-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $generic-dropdowneditor-button-size; diff --git a/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss b/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss index 0ea3fc18ff59..a6c5d42e5a62 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss @@ -11,8 +11,8 @@ @use "../slider/sizes" as *; @use "../../base/fieldset"; -$generic-field-min-height: $generic-base-inline-widget-height; -$generic-fieldset-attention-icon-offset: $generic-base-inline-horizontal-padding + $generic-invalid-badge-size; +$generic-field-min-height: $generic-base-inline-widget-height !default; +$generic-fieldset-attention-icon-offset: $generic-base-inline-horizontal-padding + $generic-invalid-badge-size !default; @mixin dx-fieldset-styling() { .dx-field { diff --git a/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss index e7a8071ba5b7..28c2fc9c26b0 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss @@ -14,19 +14,15 @@ $filemanager-file-item-select-bg: color.change($base-accent, $alpha: 0.8) !defau $filemanager-file-item-select-focus-color: $base-bg !default; $filemanager-file-item-select-focus-bg: color.change($base-accent, $alpha: 0.7) !default; $filemanager-file-item-hover-bg: null !default; -$filemanager-file-item-focused-bg: null !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $color == "carmine" { $filemanager-file-toolbar-bg: color.adjust($base-bg, $lightness: -3%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "contrast" { @@ -34,9 +30,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: $base-text-color !default; $filemanager-dirs-tree-item-focus-color: $base-bg !default; $filemanager-file-item-hover-bg: $base-hover-color !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-text-color !default; - $filemanager-progressbox-shadow-color: $base-shadow-color !default; } @if $color == "dark" { @@ -44,9 +38,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } @if $color == "darkmoon" { @@ -54,9 +46,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change(color.adjust($base-shadow-color, $lightness: -100%, $space: hsl), $alpha: 0.3) !default; } @if $color == "darkviolet" { @@ -64,9 +54,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } @if $color == "greenmist" { @@ -74,9 +62,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "light" { @@ -84,9 +70,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "softblue" { @@ -94,7 +78,5 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss index 50a1d5bc883e..ff43e8aea566 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss @@ -4,7 +4,7 @@ $filemanager-toolbar-separator-height: null !default; $filemanager-toolbar-viewmode-width: null !default; $filemanager-toolbar-viewmode-padding: null !default; -$filemanager-toolbar-height: null !default; +$filemanager-toolbar-height: $generic-toolbar-height + 12px !default; $filemanager-breadcrumbs-item-padding-top: null !default; $filemanager-breadcrumbs-item-padding-bottom: null !default; $filemanager-details-view-checkbox-column-width: null !default; @@ -21,7 +21,6 @@ $filemanager-progress-panel-padding-top: 6px !default; $filemanager-toolbar-separator-height: 36px !default; $filemanager-toolbar-viewmode-width: 150px !default; $filemanager-toolbar-viewmode-padding: 8px !default; - $filemanager-toolbar-height: $generic-toolbar-height + 12px; $filemanager-breadcrumbs-item-padding-top: 5px !default; $filemanager-breadcrumbs-item-padding-bottom: 8px !default; $filemanager-details-view-checkbox-column-width: 43px !default; @@ -37,7 +36,6 @@ $filemanager-progress-panel-padding-top: 6px !default; $filemanager-toolbar-separator-height: 26px !default; $filemanager-toolbar-viewmode-width: 130px !default; $filemanager-toolbar-viewmode-padding: 5px !default; - $filemanager-toolbar-height: $generic-toolbar-height + 12px; $filemanager-breadcrumbs-item-padding-top: 3px !default; $filemanager-breadcrumbs-item-padding-bottom: 7px !default; $filemanager-details-view-checkbox-column-width: 39px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss index db8ac6f2827f..d9c1d0c3c4b5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss @@ -6,7 +6,7 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$generic-fileuploader-file-wrapper-border-size: 3px; +$generic-fileuploader-file-wrapper-border-size: 3px !default; .dx-fileuploader-wrapper { padding: $generic-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss index 151208112147..238ce96d88f5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss @@ -67,7 +67,7 @@ $filterbuilder-item-value-focus-color: null !default; * $name 10. Menu icon color * $type color */ -$filterbuilder-menu-icon-color: null !default; +$filterbuilder-menu-icon-color: $base-text-color !default; @if $color == "carmine" { $filterbuilder-text-color: $base-text-color !default; @@ -90,7 +90,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "contrast" { @@ -114,7 +113,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-focus-color: $filterbuilder-item-value-color !default; $filterbuilder-text-color: $base-text-color !default; $filterbuilder-text-focus-color: $base-text-color !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "dark" { @@ -138,13 +136,11 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: rgba(204, 204, 204, 0.8) !default; $filterbuilder-item-value-hover-color: color.adjust($filterbuilder-item-value-color, $alpha: -0.1) !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.2) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "darkmoon" { $filterbuilder-text-color: #000 !default; $filterbuilder-text-focus-color: $filterbuilder-text-color !default; - $filterbuilder-menu-icon-color: $base-text-color !default; $filterbuilder-plus-icon-color: color.change($base-success, $alpha: 0.8) !default; $filterbuilder-plus-icon-hover-color: color.adjust($filterbuilder-plus-icon-color, $alpha: -0.1) !default; $filterbuilder-plus-icon-focus-color: color.adjust($filterbuilder-plus-icon-color, $alpha: 0.2) !default; @@ -186,7 +182,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: rgba(204, 204, 204, 0.8) !default; $filterbuilder-item-value-hover-color: color.adjust($filterbuilder-item-value-color, $alpha: -0.1) !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.2) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "greenmist" { @@ -210,7 +205,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "light" { @@ -234,7 +228,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "softblue" { @@ -258,5 +251,4 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss index aab84e869c3f..5ee45c681393 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss @@ -8,7 +8,7 @@ @use "../toolbar/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss index ea37ef5d4fc7..3a3ba9008d53 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss @@ -26,15 +26,15 @@ @use "../overlay"; @use "../pagination"; -$generic-grid-base-border-hidden: 1px solid transparent; -$generic-grid-base-row-border: 1px solid transparent; -$generic-grid-base-drag-header-border: 1px solid; -$generic-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$generic-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; -$generic-grid-base-checkbox-icon-size: 16px; -$generic-grid-base-checkbox-indeterminate-icon-size: 6px; -$generic-grid-base-group-panel-message-line-height: $generic-button-text-line-height; -$generic-grid-base-cell-input-height: math.round($generic-base-line-height * $generic-base-font-size) + 2 * $generic-grid-base-cell-padding; +$generic-grid-base-border-hidden: 1px solid transparent !default; +$generic-grid-base-row-border: 1px solid transparent !default; +$generic-grid-base-drag-header-border: 1px solid !default; +$generic-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$generic-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; +$generic-grid-base-checkbox-icon-size: 16px !default; +$generic-grid-base-checkbox-indeterminate-icon-size: 6px !default; +$generic-grid-base-group-panel-message-line-height: $generic-button-text-line-height !default; +$generic-grid-base-cell-input-height: math.round($generic-base-line-height * $generic-base-font-size) + 2 * $generic-grid-base-cell-padding !default; @mixin dx-set-checkbox-border-color-as-background() { @if $generic-color-postfix == ".contrast" { diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss index 3d9e0643fe47..9ab006263cd5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss @@ -16,8 +16,8 @@ $generic-grid-base-checkbox-fontsize: null !default; $generic-grid-base-adaptive-text-padding: null !default; $generic-grid-base-treeview-search-margin: null !default; $generic-command-edit-icon-size: null !default; -$generic-command-edit-icon-margin: null !default; -$generic-command-edit-text-margin: null !default; +$generic-command-edit-icon-margin: 5px !default; +$generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: null !default; $generic-grid-base-checkbox-padding-bottom: null !default; $generic-grid-base-treeview-select-all-item-offset: null !default; @@ -26,8 +26,8 @@ $generic-grid-base-command-ai-text-content-padding: null !default; $generic-grid-base-command-ai-header-button-padding: null !default; $generic-grid-base-command-ai-header-button-size: null !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: 0.6; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: 0.6 !default; $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; @if $size == "default" { @@ -46,11 +46,9 @@ $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $generic-grid-base-adaptive-text-padding: 8px !default; $generic-grid-base-treeview-search-margin: 10px !default; $generic-command-edit-icon-size: 18px !default; - $generic-command-edit-icon-margin: 5px !default; - $generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: 17px !default; - $generic-grid-base-checkbox-padding-bottom: 2px; - $generic-grid-base-treeview-select-all-item-offset: 34px; + $generic-grid-base-checkbox-padding-bottom: 2px !default; + $generic-grid-base-treeview-select-all-item-offset: 34px !default; $generic-grid-base-command-ai-chatsparkleoutline-icon-size: 18px !default; $generic-grid-base-command-ai-text-content-padding: $generic-grid-base-command-ai-chatsparkleoutline-icon-size + 8px !default; $generic-grid-base-command-ai-header-button-padding: 6px !default; @@ -73,11 +71,9 @@ $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $generic-grid-base-adaptive-text-padding: 6px !default; $generic-grid-base-treeview-search-margin: 5px !default; $generic-command-edit-icon-size: 14px !default; - $generic-command-edit-icon-margin: 5px !default; - $generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: 14px !default; - $generic-grid-base-checkbox-padding-bottom: 0; - $generic-grid-base-treeview-select-all-item-offset: 28px; + $generic-grid-base-checkbox-padding-bottom: 0 !default; + $generic-grid-base-treeview-select-all-item-offset: 28px !default; $generic-grid-base-command-ai-chatsparkleoutline-icon-size: 14px !default; $generic-grid-base-command-ai-text-content-padding: $generic-grid-base-command-ai-chatsparkleoutline-icon-size + 6px !default; $generic-grid-base-command-ai-header-button-padding: 5px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss index 4d9254181e0a..58d431d63c19 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss @@ -1 +1 @@ -$grid-masterdetail-padding: 20px; +$grid-masterdetail-padding: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss index fa9ad84197d8..0b43b77902d3 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $generic-base-icon-size; +$ai-chat-message-icon-size: $generic-base-icon-size !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss index 797aaf643d03..90e0b2e4232c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss @@ -9,10 +9,6 @@ $htmleditor-filled-border-color: transparent !default; $htmleditor-invalid-border-color: $base-invalid-color !default; $htmleditor-invalid-faded-border-color: $base-invalid-faded-border-color !default; $htmleditor-toolbar-border-color: $base-border-color !default; -$htmleditor-normal-format-active-bg: null !default; -$htmleditor-default-format-active-bg: null !default; -$htmleditor-danger-format-active-bg: null !default; -$htmleditor-success-format-active-bg: null !default; $htmleditor-variable-bg: color.change($base-accent, $alpha: 0.15) !default; $htmleditor-resize-frame-border-color: $base-accent !default; $htmleditor-resize-handle-bg: $base-accent !default; @@ -22,73 +18,41 @@ $htmleditor-table-border-color: $base-border-color !default; $htmleditor-cells-separator-bg: color.change($base-accent, $alpha: 0.5) !default; @if $color == "carmine" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -9%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "contrast" { - $htmleditor-normal-format-active-bg: $base-bg !default; - $htmleditor-default-format-active-bg: $base-default !default; - $htmleditor-danger-format-active-bg: $base-danger !default; - $htmleditor-success-format-active-bg: $base-success !default; $htmleditor-mention-bg: extcolor.difference(#fff, $base-bg) !default; $htmleditor-mention-color: $base-inverted-text-color !default; } @if $color == "dark" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: 13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "darkmoon" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: 13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "darkviolet" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: $base-select-bg !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "greenmist" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: $base-accent !default; $htmleditor-mention-color: color.adjust($base-text-color, $lightness: 100%, $space: hsl) !default; } @if $color == "light" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "softblue" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss index b19b96465f4e..85381b1e0bfc 100644 --- a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss @@ -10,15 +10,15 @@ $generic-aidialog-content-padding: null !default; @if $size == "default" { $generic-toolbar-size-editor-width: 105px !default; $generic-html-editor-add-image-dialog-fileuploader-padding: 60px 20px !default; - $generic-aidialog-selects-gap: 10px; - $generic-aidialog-content-gap: 20px; - $generic-aidialog-content-padding: 20px; + $generic-aidialog-selects-gap: 10px !default; + $generic-aidialog-content-gap: 20px !default; + $generic-aidialog-content-padding: 20px !default; } @else if $size == "compact" { $generic-toolbar-size-editor-width: 80px !default; $generic-html-editor-add-image-dialog-fileuploader-padding: 40px 20px !default; - $generic-aidialog-selects-gap: 5px; - $generic-aidialog-content-gap: 10px; - $generic-aidialog-content-padding: 10px; + $generic-aidialog-selects-gap: 5px !default; + $generic-aidialog-content-gap: 10px !default; + $generic-aidialog-content-padding: 10px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss index 573e09684108..20a972f1246b 100644 --- a/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss @@ -8,57 +8,57 @@ $informer-info-color: null !default; $informer-info-background-color: null !default; @if $color == "carmine" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -26.91%, $lightness: -5.49%); - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -347deg, $saturation: -26.69%, $lightness: 33.14%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -26.91%, $lightness: -5.49%) !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -347deg, $saturation: -26.69%, $lightness: 33.14%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } @if $color == "contrast" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 2deg, $saturation: -16.93%, $lightness: -6.08%); - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 2deg, $saturation: -16.93%, $lightness: -6.08%) !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl) !default; } @if $color == "dark" { - $informer-error-color: $base-danger; - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: -55.69%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -55.69%, $space: hsl) !default; } @if $color == "darkmoon" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -342deg, $saturation: -28.85%, $lightness: -6.67%); - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -342deg, $saturation: -28.85%, $lightness: -6.67%) !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl) !default; } @if $color == "darkviolet" { - $informer-error-color: $base-danger; - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -9.41%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -65.10%); + $informer-error-color: $base-danger !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -9.41%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -65.10%) !default; } @if $color == "greenmist" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -27.05%, $lightness: -4.90%); - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -348deg, $saturation: -26.83%, $lightness: 33.73%); - $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: -3.33%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: 68.82%); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -27.05%, $lightness: -4.90%) !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -348deg, $saturation: -26.83%, $lightness: 33.73%) !default; + $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: -3.33%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: 68.82%) !default; } @if $color == "light" { - $informer-error-color: $base-danger; - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } @if $color == "softblue" { - $informer-error-color: $base-danger; - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/list/_index.scss b/packages/devextreme-scss/scss/widgets/generic/list/_index.scss index c1dcca0e4a38..ed6b8aed869c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/list/_index.scss @@ -13,9 +13,9 @@ @use "../../base/list/mixins" as *; @use "../badge"; -$generic-list-border: $generic-list-border-width solid; -$generic-list-item-border: $generic-list-item-border-width solid; -$generic-list-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width; +$generic-list-border: $generic-list-border-width solid !default; +$generic-list-item-border: $generic-list-item-border-width solid !default; +$generic-list-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width !default; .dx-list-item-chevron { @include dx-icon(chevronnext); diff --git a/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss index a5829b425501..21159495c936 100644 --- a/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss @@ -8,14 +8,13 @@ */ $loadindicator-bg: null !default; $loadindicator-segment-shadow-color: null !default; -$load-indicator-animation-sparkle-bg-color1: null !default; +$load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: null !default; $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "carmine" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -23,7 +22,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "contrast" { $loadindicator-bg: $base-inverted-bg !default; $loadindicator-segment-shadow-color: $base-shadow-color !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -31,7 +29,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "dark" { $loadindicator-bg: color.adjust($base-text-color, $lightness: -17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -39,7 +36,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "darkmoon" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -47,7 +43,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "darkviolet" { $loadindicator-bg: $base-accent-highlight-color !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -55,7 +50,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "greenmist" { $loadindicator-bg: color.adjust($base-text-color, $lightness: 17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -63,7 +57,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "light" { $loadindicator-bg: color.adjust($base-text-color, $lightness: 17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -71,7 +64,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "softblue" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss index c000cb70ef14..fbfba161c754 100644 --- a/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss @@ -8,11 +8,11 @@ @use "../../base/mixins" as *; @use "../../base/menuBase"; -$generic-menu-item-padding-increased: $generic-menu-item-padding-base + 2px; -$generic-menu-item-padding-reduced: $generic-menu-item-padding-base - 2px; -$generic-menu-arrow-image-size: 7px; +$generic-menu-item-padding-increased: $generic-menu-item-padding-base + 2px !default; +$generic-menu-item-padding-reduced: $generic-menu-item-padding-base - 2px !default; +$generic-menu-arrow-image-size: 7px !default; -$generic-menu-popup-border: 1px solid; +$generic-menu-popup-border: 1px solid !default; .dx-menu-item { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss index a6c667cfc55b..f1c91857cdf8 100644 --- a/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss @@ -16,7 +16,7 @@ $pivotgrid-empty-area-text-padding: null !default; $pivotgrid-common-top-bottom-padding: 7px !default; $pivotgrid-field-indicators-offset: 6px !default; $pivotgrid-indicators-line-height: 19px !default; - $pivotgrid-button-top-padding: 12px; + $pivotgrid-button-top-padding: 12px !default; $pivotgrid-empty-area-text-padding: 7px !default; } @@ -27,6 +27,6 @@ $pivotgrid-empty-area-text-padding: null !default; $pivotgrid-common-top-bottom-padding: 5px !default; $pivotgrid-field-indicators-offset: 4px !default; $pivotgrid-indicators-line-height: 16px !default; - $pivotgrid-button-top-padding: 8px; + $pivotgrid-button-top-padding: 8px !default; $pivotgrid-empty-area-text-padding: 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss b/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss index 584ad3d8053d..f7cfb912957a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss @@ -11,8 +11,8 @@ @use "../../base/popup"; @use "../overlay"; -$generic-popup-toolbar-height: $generic-base-inline-widget-height; -$generic-popup-toolbar-label-font-size: $generic-m-font-size; +$generic-popup-toolbar-height: $generic-base-inline-widget-height !default; +$generic-popup-toolbar-label-font-size: $generic-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss b/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss index ed0a16ca0296..0d42ade93bae 100644 --- a/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss @@ -4,11 +4,11 @@ @use "../../base/mixins" as *; @use "../../base/progressBar"; -$generic-bar-height: 6px; -$generic-background-width: 90%; -$generic-progressbar-border-radius: 2px; -$generic-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; -$generic-progress-bar-border: 1px solid; +$generic-bar-height: 6px !default; +$generic-background-width: 90% !default; +$generic-progressbar-border-radius: 2px !default; +$generic-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; +$generic-progress-bar-border: 1px solid !default; .dx-progressbar-container { height: $generic-bar-height; diff --git a/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss b/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss index d437e5903f72..5e412c3672ef 100644 --- a/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss @@ -2,7 +2,7 @@ @use "sizes" as *; @use "../../base/rangeSlider"; -$generic-slider-handle-margin: -7px; +$generic-slider-handle-margin: -7px !default; .dx-rangeslider-start-handle { margin-inline-start: $generic-slider-handle-margin; diff --git a/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss index 8ae754ace0b8..58c2cc65dbce 100644 --- a/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss @@ -3,7 +3,7 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$generic-recurrence-number-box-margin-right: 11px; +$generic-recurrence-number-box-margin-right: 11px !default; .dx-recurrence-editor-container { .dx-recurrence-numberbox-interval { diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss index 998c9d6176d5..71c6890c436a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss @@ -67,32 +67,27 @@ $scheduler-dd-appointment-hover-text-color: null !default; $scheduler-form-icon-color: $base-icon-color !default; $scheduler-workspace-month-text-color: null !default; $scheduler-dropdown-appointment-date-color: null !default; -$scheduler-workspace-active-cell-color: null !default; $scheduler-workspace-hovered-cell-color: null !default; $scheduler-group-header-color: null !default; $scheduler-header-bg: null !default; $scheduler-alldaypanel-bg: null !default; -$scheduler-tab-selected-bg: null !default; -$scheduler-tab-selected-before-bg: null !default; -$scheduler-tab-focused-border-color: null !default; $scheduler-other-month-cell-opacity: null !default; $scheduler-fill-focused-appointment: null !default; $scheduler-is-shadow-color-for-focused-state: true !default; $scheduler-time-indicator-background-color: null !default; $scheduler-first-month-cell-background-color: null !default; -$scheduler-time-panel-background-color: null !default; $scheduler-tooltip-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: null !default; -$month-text-color-light: #757575; -$month-text-color-dark: #9c9c9c; +$month-text-color-light: #757575 !default; +$month-text-color-dark: #9c9c9c !default; // Agenda $agenda-appointment-recurrence-icon-color: null !default; $agenda-appointment-hover-bg: $base-hover-bg !default; $agenda-appointment-active-bg: null !default; -$agenda-appointment-text-color: null !default; +$agenda-appointment-text-color: $base-text-color !default; @if $color == "carmine" { $scheduler-base-border-color: color.change($base-border-color, $alpha: 0.6) !default; @@ -112,14 +107,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: color.adjust($base-bg, $lightness: -4%, $space: hsl) !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $base-bg !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -129,7 +120,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change($base-accent, $alpha: 0.15) !default; } @@ -149,14 +139,10 @@ $agenda-appointment-text-color: null !default; $scheduler-workspace-focused-cell-color: color.adjust($base-accent, $lightness: 5%, $space: hsl) !default; $scheduler-panel-text-color: $base-text-color !default; $scheduler-dropdown-appointment-date-color: $base-text-color !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: extcolor.difference(#fff, $base-text-color) !default; $scheduler-group-header-color: $base-text-color !default; $scheduler-header-bg: $base-bg !default; $scheduler-alldaypanel-bg: $base-bg !default; - $scheduler-tab-selected-bg: $base-inverted-bg !default; - $scheduler-tab-selected-before-bg: transparent !default; - $scheduler-tab-focused-border-color: $base-border-color !default; $scheduler-other-month-cell-opacity: 1 !default; $scheduler-fill-focused-appointment: true !default; $scheduler-time-indicator-background-color: color.change(#3806ff, $alpha: 0.6) !default; @@ -167,7 +153,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-inverted-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: $base-accent !default; } @@ -189,14 +174,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -206,7 +187,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#fff, $alpha: 0.15) !default; } @@ -230,14 +210,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: #fff !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -247,7 +223,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.15) !default; } @@ -269,14 +244,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -286,7 +257,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#9e9ec5, $alpha: 0.35) !default; } @@ -308,15 +278,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-time-panel-background-color: $scheduler-workspace-background-color !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-time-panel-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -326,7 +291,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.15) !default; } @@ -349,13 +313,9 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -365,7 +325,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.08) !default; } @@ -387,14 +346,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -404,8 +359,9 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.08) !default; } -$scheduler-appointment-active-color: $scheduler-appointment-start-color; +$scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; + +$scheduler-appointment-active-color: $scheduler-appointment-start-color !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss index 89156a91ae9a..1d8501d7f8bd 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss @@ -52,21 +52,21 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$generic-scheduler-appointment-reduced-content-padding: 40px; -$generic-scheduler-appointment-empty-transparent-border: 1px solid transparent; +$generic-scheduler-appointment-reduced-content-padding: 40px !default; +$generic-scheduler-appointment-empty-transparent-border: 1px solid transparent !default; -$generic-scheduler-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 1.5; -$generic-scheduler-collapsed-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 0.5; -$generic-scheduler-header-panel-table-cell-height: $generic-scheduler-workspace-date-table-cell-height; +$generic-scheduler-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 1.5 !default; +$generic-scheduler-collapsed-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 0.5 !default; +$generic-scheduler-header-panel-table-cell-height: $generic-scheduler-workspace-date-table-cell-height !default; -$generic-scheduler-current-time-cell-border-size: 2px; +$generic-scheduler-current-time-cell-border-size: 2px !default; -$generic-scheduler-month-date-text-size: $generic-h5-font-size; +$generic-scheduler-month-date-text-size: $generic-h5-font-size !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$generic-scheduler-agenda-time-cell-padding: 10px; -$generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-cell-padding 0 0 10px; +$generic-scheduler-agenda-time-cell-padding: 10px !default; +$generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-cell-padding 0 0 10px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss index 46750f3037f9..cab02338233c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss @@ -7,32 +7,29 @@ $generic-scheduler-timeline-date-table-cell-height: null !default; $generic-scheduler-header-height: null !default; $generic-scheduler-header-offset: null !default; $generic-scheduler-toolbar-item-offset: null !default; -$generic-scheduler-navigator-height: null !default; -$generic-scheduler-navigation-buttons-padding: null !default; $generic-scheduler-appointment-month-text-size: null !default; $generic-scheduler-workspace-info-text-size: null !default; $generic-scheduler-all-day-title-font-size: null !default; $generic-scheduler-appointment-content-padding: null !default; $generic-scheduler-appointment-recurrence-content-padding: null !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: null !default; -$generic-scheduler-agenda-appointment-font-size: null !default; $generic-scheduler-group-header-table-cell-width: null !default; $generic-scheduler-appointment-transparent-left-border: null !default; $generic-scheduler-appointment-transparent-right-border: null !default; $generic-scheduler-dropdown-button-height: null !default; $generic-scheduler-dropdown-button-margin: null !default; $generic-scheduler-dropdown-button-vertical-margin: null !default; -$generic-scheduler-dropdown-button-adaptive-margin: null !default; -$generic-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: null !default; $generic-scheduler-appointment-popup-toolbar-label-size: null !default; $generic-scheduler-appointment-popup-icon-size: null !default; $generic-scheduler-appointment-popup-icon-container-height: null !default; -$generic-scheduler-appointment-popup-icon-padding-right: null !default; +$generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: null !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: null !default; -$generic-scheduler-appointment-popup-item-padding-horizontal: null !default; +$generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: null !default; $generic-scheduler-appointment-popup-all-day-item-height: null !default; $generic-scheduler-appointment-popup-days-of-week-button-size: null !default; @@ -42,22 +39,22 @@ $generic-scheduler-appointment-popup-repeat-end-item-height: null !default; $generic-scheduler-appointment-short-content-padding: null !default; $generic-scheduler-appointment-10min-height: null !default; -$generic-scheduler-appointment-15min-height: null !default; -$generic-scheduler-appointment-20min-height: null !default; -$generic-scheduler-appointment-25min-height: null !default; -$generic-scheduler-appointment-10min-recurrence-padding-right: null !default; -$generic-scheduler-appointment-10min-title-font-size: null !default; -$generic-scheduler-appointment-10min-title-line-height: null !default; -$generic-scheduler-appointment-10min-icon-font-size: null !default; -$generic-scheduler-appointment-10min-icon-right: null !default; +$generic-scheduler-appointment-15min-height: 23px !default; +$generic-scheduler-appointment-20min-height: 32px !default; +$generic-scheduler-appointment-25min-height: 41px !default; +$generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; +$generic-scheduler-appointment-10min-title-font-size: 8px !default; +$generic-scheduler-appointment-10min-title-line-height: 16px !default; +$generic-scheduler-appointment-10min-icon-font-size: 14px !default; +$generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: null !default; -$generic-scheduler-appointment-15min-title-font-size: null !default; +$generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: null !default; $generic-scheduler-appointment-15min-icon-font-size: null !default; -$generic-scheduler-appointment-15min-icon-right: null !default; +$generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: null !default; -$agenda-appointment-title-font-size: null !default; +$agenda-appointment-title-font-size: 16px !default; $scheduler-timeline-cell-height: null !default; $scheduler-left-column-width: 100px !default; @@ -70,8 +67,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-header-height: 36px !default; $generic-scheduler-header-offset: 9px 10px !default; $generic-scheduler-toolbar-item-offset: 2px !default; - $generic-scheduler-navigator-height: 36px !default; - $generic-scheduler-navigation-buttons-padding: 6px !default; $generic-scheduler-appointment-month-text-size: 13px !default; $generic-scheduler-workspace-info-text-size: 14px !default; @@ -81,7 +76,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px !default; - $generic-scheduler-agenda-appointment-font-size: 16px !default; $generic-scheduler-group-header-table-cell-width: 80px !default; $generic-scheduler-appointment-transparent-left-border: 3px solid transparent !default; $generic-scheduler-appointment-transparent-right-border: 2px solid transparent !default; @@ -89,17 +83,13 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-dropdown-button-height: 20px !default; $generic-scheduler-dropdown-button-margin: 3px 2px 3px 3px !default; $generic-scheduler-dropdown-button-vertical-margin: 3px 2px 3px 0 !default; - $generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: 43px !default; $generic-scheduler-appointment-popup-toolbar-label-size: 20px !default; $generic-scheduler-appointment-popup-icon-size: 18px !default; $generic-scheduler-appointment-popup-icon-container-height: 36px !default; - $generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: 22px !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: 7px !default; - $generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: 20px !default; $generic-scheduler-appointment-popup-all-day-item-height: 36px !default; $generic-scheduler-appointment-popup-repeat-end-radio-margin-top: 10px !default; @@ -107,27 +97,14 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-popup-days-of-week-buttons-gap: 10px !default; $generic-scheduler-appointment-popup-repeat-end-item-height: 36px !default; - $agenda-appointment-title-font-size: 16px; - $scheduler-timeline-cell-height: 78px !default; $generic-scheduler-appointment-short-content-padding: 0 7px !default; $generic-scheduler-appointment-10min-height: 23px !default; - $generic-scheduler-appointment-15min-height: 23px !default; - $generic-scheduler-appointment-20min-height: 32px !default; - $generic-scheduler-appointment-25min-height: 41px !default; - - $generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-10min-title-font-size: 8px !default; - $generic-scheduler-appointment-10min-title-line-height: 16px !default; - $generic-scheduler-appointment-10min-icon-font-size: 14px !default; - $generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: 20px !default; - $generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: 24px !default; $generic-scheduler-appointment-15min-icon-font-size: 18px !default; - $generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: 24px !default; } @@ -139,8 +116,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-header-height: 26px !default; $generic-scheduler-header-offset: 6px 7px !default; $generic-scheduler-toolbar-item-offset: 0 !default; - $generic-scheduler-navigator-height: 25px !default; - $generic-scheduler-navigation-buttons-padding: 4px !default; $generic-scheduler-appointment-month-text-size: 11px !default; $generic-scheduler-workspace-info-text-size: 12px !default; @@ -150,7 +125,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-recurrence-content-padding: 2px 25px 2px 5px !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: 2px 5px 2px 25px !default; - $generic-scheduler-agenda-appointment-font-size: 14px !default; $generic-scheduler-group-header-table-cell-width: 60px !default; $generic-scheduler-appointment-transparent-left-border: 2px solid transparent !default; $generic-scheduler-appointment-transparent-right-border: 1px solid transparent !default; @@ -158,17 +132,13 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-dropdown-button-height: 17px !default; $generic-scheduler-dropdown-button-margin: 3px 1px 3px 3px !default; $generic-scheduler-dropdown-button-vertical-margin: 3px 1px 3px 0 !default; - $generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: 39px !default; $generic-scheduler-appointment-popup-toolbar-label-size: 18px !default; $generic-scheduler-appointment-popup-icon-size: 14px !default; $generic-scheduler-appointment-popup-icon-container-height: 26px !default; - $generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: 18px !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: 6px !default; - $generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: 10px !default; $generic-scheduler-appointment-popup-all-day-item-height: 24px !default; $generic-scheduler-appointment-popup-repeat-end-radio-margin-top: 6px !default; @@ -176,27 +146,14 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-popup-days-of-week-buttons-gap: 6px !default; $generic-scheduler-appointment-popup-repeat-end-item-height: 26px !default; - $agenda-appointment-title-font-size: 16px; - $scheduler-timeline-cell-height: 50px !default; $generic-scheduler-appointment-short-content-padding: 0 5px !default; $generic-scheduler-appointment-10min-height: 14px !default; - $generic-scheduler-appointment-15min-height: 23px !default; - $generic-scheduler-appointment-20min-height: 32px !default; - $generic-scheduler-appointment-25min-height: 41px !default; - - $generic-scheduler-appointment-10min-title-font-size: 8px !default; - $generic-scheduler-appointment-10min-title-line-height: 16px !default; - $generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-10min-icon-font-size: 14px !default; - $generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: 16px !default; $generic-scheduler-appointment-15min-icon-font-size: 14px !default; - $generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: 20px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss index 721bcb419a19..7e5c45388a59 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$generic-scrollbar-size-thin: 8px; -$generic-scrollbar-size: 15px; +$generic-scrollbar-size-thin: 8px !default; +$generic-scrollbar-size: 15px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss index 6e7b828ad034..4ffcbc4e0e48 100644 --- a/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss @@ -18,7 +18,6 @@ $slider-handle-bg: null !default; $slider-handle-border-color: null !default; $slider-handle-active-bg: null !default; $slider-handle-hover-bg: null !default; -$slider-handle-focused-bg: null !default; $slider-invalid-handle-accent-bg: null !default; $slider-handle-border-radius: $base-border-radius - 1px !default; @@ -27,7 +26,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -36,7 +34,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: extcolor.difference(#fff, $base-border-color) !default; $slider-handle-active-bg: $base-inverted-bg !default; $slider-handle-hover-bg: $slider-range-bg !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -45,7 +42,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -54,7 +50,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -63,7 +58,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -72,7 +66,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -81,7 +74,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -90,6 +82,7 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } + +$slider-handle-focused-bg: $slider-handle-hover-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss b/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss index b9459bab9b75..c036962f113b 100644 --- a/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss @@ -5,7 +5,7 @@ @use "sizes" as *; @use "../../base/slider"; -$generic-slider-radius: 2px; +$generic-slider-radius: 2px !default; .dx-slider { .dx-tooltip-wrapper { diff --git a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss index 396fe10c61a2..1089f51ba2a1 100644 --- a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss @@ -5,9 +5,9 @@ @use "../button/colors" as *; @use "../overlay"; -$generic-fa-button-size: 30px; -$generic-fa-button-label-padding: 5px 10px; -$generic-fa-button-label-offset: 10px; +$generic-fa-button-size: 30px !default; +$generic-fa-button-label-padding: 5px 10px !default; +$generic-fa-button-label-offset: 10px !default; @include dx-fa-button-mixin( $generic-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss index 650d9654e9bd..5e8f7e17bc8e 100644 --- a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss @@ -7,10 +7,10 @@ $generic-fa-button-label-font-size: 12px !default; @if $size == "default" { $generic-main-fa-button-size: 48px !default; - $generic-main-fa-button-label-padding: 0 25px 0 10px; + $generic-main-fa-button-label-padding: 0 25px 0 10px !default; } @else if $size == "compact" { $generic-main-fa-button-size: 34px !default; - $generic-main-fa-button-label-padding: 0 15px 0 5px; + $generic-main-fa-button-label-padding: 0 15px 0 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss index 839b901f0580..b4d9ec2a53d7 100644 --- a/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 18px; +$splitter-resize-handle-icon-size: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss index a4e5208391da..85340cd00c40 100644 --- a/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss @@ -35,7 +35,6 @@ $switch-active-border-color: null !default; $switch-off-color: null !default; $switch-handle-off-bg: null !default; $switch-readonly-border-color: null !default; -$switch-container-readonly-bg: null !default; $switch-border-radius: null !default; @if $color == "carmine" { @@ -51,7 +50,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -67,7 +65,6 @@ $switch-border-radius: null !default; $switch-off-color: $base-text-color !default; $switch-handle-off-bg: $base-inverted-bg !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -50%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius - 2px !default; } @@ -83,7 +80,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -99,7 +95,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -115,7 +110,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -131,7 +125,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -147,7 +140,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -163,6 +155,7 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } + +$switch-container-readonly-bg: $switch-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss b/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss index 7b3bf528f688..de3aee4096fd 100644 --- a/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss @@ -4,8 +4,8 @@ @use "../../base/mixins" as *; @use "../../base/switch"; -$generic-switch-border-weight: 1px; -$generic-switch-inner-size: $generic-switch-height - $generic-switch-border-weight * 2; +$generic-switch-border-weight: 1px !default; +$generic-switch-inner-size: $generic-switch-height - $generic-switch-border-weight * 2 !default; .dx-switch { width: $generic-switch-width; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss b/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss index 7065d72a2709..89b71d9318f2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss @@ -1,93 +1,4 @@ -@use "../../colors" as *; -@use "../variables/sizes" as *; -@use "../../button/colors" as *; @use "../../tabs/variables/sizes" as *; -@use "../../tabs/variables/colors" as *; -$generic-tabpanel-tab-border-transparent: null !default; -$generic-tabpanel-tab-border-selected-bottom: null !default; -$generic-tabpanel-tab-border-active-bottom: null !default; - -$generic-tabpanel-tab-border-focused-bottom: null !default; -$generic-tabpanel-tab-border-focused-left: null !default; -$generic-tabpanel-tab-border-focused-right: null !default; - -@if $color == "carmine" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "contrast" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "dark" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "darkmoon" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "darkviolet" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "greenmist" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "light" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "softblue" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} +$generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; +$generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss index 63fc74fbffb6..99b07836830a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss @@ -50,8 +50,7 @@ $tabs-focused-disabled-border-color: null !default; $tabs-tab-hover-bg: null !default; $tabs-tab-active-bg: null !default; $tabs-tab-active-border-color: null !default; -$tabs-tab-hover-border-color-horizontal: null !default; -$tabs-tab-hover-border-color-vertical: null !default; +$tabs-tab-hover-border-color-horizontal: $base-border-color !default; @if $color == "carmine" { $tabs-tab-selected-color: $base-text-color !default; @@ -64,8 +63,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-hover-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($base-border-color, $lightness: -2.55%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: color.change(color.adjust($base-accent, $lightness: 33.92%, $space: hsl)) !default; } @if $color == "contrast" { @@ -79,8 +76,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-focused-disabled-border-color: color.adjust($base-border-color, $lightness: -20%, $space: hsl) !default; $tabs-tab-active-bg: $base-inverted-bg !default; $tabs-tab-active-border-color: $base-border-color !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "dark" { @@ -94,8 +89,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($tabs-tab-bg, $lightness: 12.55%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "darkmoon" { @@ -109,8 +102,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: transparent !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "darkviolet" { @@ -124,8 +115,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-hover-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($base-bg, $lightness: 15.49%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: color.change(color.adjust($tabs-tab-bg, $lightness: 16.86%, $space: hsl)) !default; } @if $color == "greenmist" { @@ -139,8 +128,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "light" { @@ -154,8 +141,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "softblue" { @@ -169,6 +154,4 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss index 9aa3bb5dc194..5183306a42b6 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss @@ -3,11 +3,10 @@ $generic-tabs-item-gap: null !default; $generic-tabs-item-padding: null !default; -$generic-tabs-item-padding-disabled-focused: null !default; $generic-tabs-border-width: $generic-base-border-width !default; -$generic-tabs-nav-button-height: null !default; -$generic-tabs-nav-button-width: null !default; +$generic-tabs-nav-button-height: 38px !default; +$generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: null !default; $generic-tabs-icon-margin: null !default; $generic-tabs-icon-size: $generic-base-icon-size !default; @@ -16,10 +15,7 @@ $generic-badge-min-size: null !default; @if $size == "default" { $generic-tabs-item-gap: 4px !default; $generic-tabs-item-padding: 10px 9px !default; - $generic-tabs-item-padding-disabled-focused: 10px !default; - $generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: 1.285715 !default; - $generic-tabs-nav-button-height: 38px !default; $generic-tabs-icon-margin: 8px !default; $generic-badge-min-size: 18px !default; } @@ -27,10 +23,7 @@ $generic-badge-min-size: null !default; @else if $size == "compact" { $generic-tabs-item-gap: 2px !default; $generic-tabs-item-padding: 5px 4px !default; - $generic-tabs-item-padding-disabled-focused: 5px !default; - $generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: 1.75 !default; - $generic-tabs-nav-button-height: 38px !default; $generic-tabs-icon-margin: 7px !default; $generic-badge-min-size: 21px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss index eed206407654..42077dcc6b05 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss @@ -8,243 +8,27 @@ $generic-tabs-border: $generic-tabs-border-width solid !default; $generic-tabs-border-transparent: $generic-tabs-border-width solid transparent !default; -$generic-tabs-border-hover-top: null !default; -$generic-tabs-border-hover-bottom: null !default; -$generic-tabs-border-hover-left: null !default; -$generic-tabs-border-hover-right: null !default; - -$generic-tabs-border-selected-top: null !default; -$generic-tabs-border-selected-bottom: null !default; -$generic-tabs-border-selected-left: null !default; -$generic-tabs-border-selected-right: null !default; - -$generic-tabs-border-focused-top: null !default; -$generic-tabs-border-focused-bottom: null !default; -$generic-tabs-border-focused-left: null !default; -$generic-tabs-border-focused-right: null !default; - -$generic-tabs-border-focused-disabled-top: null !default; -$generic-tabs-border-focused-disabled-bottom: null !default; -$generic-tabs-border-focused-disabled-left: null !default; -$generic-tabs-border-focused-disabled-right: null !default; - -$generic-tabs-border-active-top: null !default; -$generic-tabs-border-active-bottom: null !default; -$generic-tabs-border-active-left: null !default; -$generic-tabs-border-active-right: null !default; - -@if $color == "carmine" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "contrast" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "dark" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkmoon" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkviolet" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "greenmist" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "light" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "softblue" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} +$generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-hover-left: none !default; +$generic-tabs-border-hover-right: none !default; + +$generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-active-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-right: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss index f9d01c04e193..04cc910391e2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss @@ -6,243 +6,27 @@ @use "../../variables/sizes" as *; @use "../../variables/borders/borders-horizontal"; -$generic-tabs-vertical-border-hover-top: null !default; -$generic-tabs-vertical-border-hover-bottom: null !default; -$generic-tabs-vertical-border-hover-left: null !default; -$generic-tabs-vertical-border-hover-right: null !default; - -$generic-tabs-vertical-border-selected-top: null !default; -$generic-tabs-vertical-border-selected-bottom: null !default; -$generic-tabs-vertical-border-selected-left: null !default; -$generic-tabs-vertical-border-selected-right: null !default; - -$generic-tabs-vertical-border-focused-top: null !default; -$generic-tabs-vertical-border-focused-bottom: null !default; -$generic-tabs-vertical-border-focused-left: null !default; -$generic-tabs-vertical-border-focused-right: null !default; - -$generic-tabs-vertical-border-focused-disabled-top: null !default; -$generic-tabs-vertical-border-focused-disabled-bottom: null !default; -$generic-tabs-vertical-border-focused-disabled-left: null !default; -$generic-tabs-vertical-border-focused-disabled-right: null !default; - -$generic-tabs-vertical-border-active-top: null !default; -$generic-tabs-vertical-border-active-bottom: null !default; -$generic-tabs-vertical-border-active-left: null !default; -$generic-tabs-vertical-border-active-right: null !default; - -@if $color == "carmine" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "contrast" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "dark" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkmoon" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkviolet" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "greenmist" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "light" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "softblue" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} +$generic-tabs-vertical-border-hover-top: none !default; +$generic-tabs-vertical-border-hover-bottom: none !default; +$generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss index a342938c89a6..294bb2ffc202 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss @@ -23,7 +23,6 @@ $tagbox-tag-button-remove-bg: null !default; $tagbox-tag-button-remove-active-bg: null !default; $tagbox-tag-focused-bg: null !default; $tagbox-tag-focused-color: null !default; -$tagbox-tag-focused-button-remove-bg: null !default; $tagbox-select-all-border-color: $base-border-color !default; @if $color == "carmine" { @@ -33,7 +32,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "contrast" { @@ -43,7 +41,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: $base-focus-color !default; $tagbox-tag-focused-color: $base-text-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "dark" { @@ -53,7 +50,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "darkmoon" { @@ -63,7 +59,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "darkviolet" { @@ -73,7 +68,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "greenmist" { @@ -83,7 +77,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "light" { @@ -93,7 +86,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "softblue" { @@ -103,5 +95,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } + +$tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss index f6c080b13de3..79a778f4d217 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss @@ -54,7 +54,7 @@ $texteditor-readonly-border-color: $base-border-color !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $color == "carmine" { $texteditor-placeholder-color: $base-label-color !default; @@ -128,7 +128,7 @@ $texteditor-label-transition: $texteditor-filled-invalid-background: color.change($texteditor-invalid-faded-border-color, $alpha: 0.1) !default; } -$texteditor-label-color: $texteditor-placeholder-color; +$texteditor-label-color: $texteditor-placeholder-color !default; :root { --dx-texteditor-color-text: #{$texteditor-color}; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss index 67a56708f324..d3b4dc45bebc 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss @@ -13,21 +13,21 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$generic-texteditor-border-width: $generic-base-border-width; +$generic-texteditor-border-width: $generic-base-border-width !default; $generic-texteditor-input-padding: $generic-base-inline-borderedwidget-top-padding $generic-base-inline-borderedwidget-horizontal-padding - $generic-base-inline-borderedwidget-bottom-padding; + $generic-base-inline-borderedwidget-bottom-padding !default; -$generic-texteditor-input-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding; +$generic-texteditor-input-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding !default; -$generic-texteditor-icon-container-size: $generic-base-inline-borderedwidget-inner-size; -$generic-texteditor-icon-size: $generic-base-icon-size; +$generic-texteditor-icon-container-size: $generic-base-inline-borderedwidget-inner-size !default; +$generic-texteditor-icon-size: $generic-base-icon-size !default; -$generic-texteditor-clear-icon-size: $generic-texteditor-icon-size + $generic-texteditor-clear-icon-oversize; +$generic-texteditor-clear-icon-size: $generic-texteditor-icon-size + $generic-texteditor-clear-icon-oversize !default; -$generic-texteditor-custom-button-margin: 1px; -$generic-texteditor-invalid-badge-size: $generic-invalid-badge-size + 2 * $generic-invalid-badge-horizontal-padding; +$generic-texteditor-custom-button-margin: 1px !default; +$generic-texteditor-invalid-badge-size: $generic-invalid-badge-size + 2 * $generic-invalid-badge-horizontal-padding !default; @mixin dx-texteditor-icon() { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss index ebbe32e60ef1..6520341bb9fe 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss @@ -7,7 +7,7 @@ $generic-texteditor-label-outside-font-size: null !default; $generic-texteditor-label-outside-height: null !default; $generic-texteditor-label-outside-top-offset: null !default; $generic-texteditor-button-icononly-bottom-padding: null !default; -$generic-texteditor-button-icononly-horizontal-padding: null !default; +$generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; $generic-texteditor-button-top-padding: $generic-base-inline-widget-top-padding - $generic-button-border-weight - 2px !default; $generic-texteditor-button-bottom-padding: $generic-base-inline-widget-bottom-padding - $generic-button-border-weight - 2px !default; @@ -26,7 +26,6 @@ $generic-texteditor-underlined-input-with-label-bottom-padding: $generic-base-in $generic-texteditor-label-outside-height: 20px !default; $generic-texteditor-label-outside-top-offset: 24px !default; $generic-texteditor-button-icononly-bottom-padding: $generic-texteditor-button-bottom-padding !default; - $generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; } @else if $size == "compact" { @@ -36,5 +35,4 @@ $generic-texteditor-underlined-input-with-label-bottom-padding: $generic-base-in $generic-texteditor-label-outside-height: 16px !default; $generic-texteditor-label-outside-top-offset: 18px !default; $generic-texteditor-button-icononly-bottom-padding: $generic-texteditor-button-bottom-padding + 1px !default; - $generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss b/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss index 3be2e70bcbaf..8ae3fbf94cd5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss @@ -4,7 +4,7 @@ @use "../../base/toast"; @use "../overlay"; -$generic-toast-shadow: 0 2px 3px 0 $toast-shadow-color; +$generic-toast-shadow: 0 2px 3px 0 $toast-shadow-color !default; .dx-toast-content { color: $toast-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss index 86c52ae07a9f..b46d475d6eeb 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss @@ -2,45 +2,37 @@ @use "../colors" as *; @use "../gridBase/colors" as *; -$treelist-spin-icon-color: null !default; +$treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: null !default; @if $color == "carmine" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: color.adjust($datagrid-spin-icon-color, $lightness: 100%, $space: hsl) !default; } @if $color == "contrast" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-inverted-text-color !default; } @if $color == "dark" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $treelist-spin-icon-color !default; } @if $color == "darkmoon" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-bg !default; } @if $color == "darkviolet" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-icon-color !default; } @if $color == "greenmist" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: color.adjust($treelist-spin-icon-color, $lightness: 100%, $space: hsl) !default; } @if $color == "light" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $treelist-spin-icon-color !default; } @if $color == "softblue" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-icon-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss index 1a618a1088fe..b69ae56ce488 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss @@ -29,7 +29,6 @@ $treeview-border-color: $base-border-color !default; */ $treeview-item-selected-color: null !default; $treeview-item-selected-bg: null !default; -$treeview-item-selected-hover-bg: null !default; $treeview-item-content-disabled: null !default; /** @@ -38,7 +37,7 @@ $treeview-item-content-disabled: null !default; */ $treeview-spin-icon-color: null !default; -$generic-treeview-focused-row-load-indicator-color: #fff; +$generic-treeview-focused-row-load-indicator-color: #fff !default; @if $color == "carmine" { $treeview-color: $base-text-color !default; @@ -48,7 +47,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change($base-accent, $alpha: 0.48) !default; - $treeview-item-selected-hover-bg: color.change($base-accent, $alpha: 0.3) !default; $treeview-spin-icon-color: $base-header-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } @@ -60,7 +58,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-text-color !default; $treeview-item-selected-color: $base-inverted-text-color !default; $treeview-item-selected-bg: $base-inverted-bg !default; - $treeview-item-selected-hover-bg: $base-inverted-bg !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: -48.24%, $space: hsl) !default; } @@ -73,7 +70,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#fff, $alpha: 0.12) !default; - $treeview-item-selected-hover-bg: color.change(#fff, $alpha: 0.09) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.change($base-text-color, $lightness: 51.8%, $space: hsl) !default; } @@ -86,7 +82,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#748fbc, $alpha: 0.42) !default; - $treeview-item-selected-hover-bg: color.change(#748fbc, $alpha: 0.3) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: -48.24%, $space: hsl) !default; } @@ -99,7 +94,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change($base-accent, $alpha: 0.46) !default; - $treeview-item-selected-hover-bg: color.change($base-accent, $alpha: 0.6) !default; $treeview-spin-icon-color: $base-header-color !default; $treeview-item-content-disabled: color.change($base-text-color, $saturation: 0%, $lightness: 51.76%, $space: hsl) !default; } @@ -112,7 +106,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.2) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.change($base-text-color, $saturation: 0%, $lightness: 60%, $space: hsl) !default; } @@ -125,7 +118,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.07) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } @@ -138,7 +130,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.15) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss index e24765ccb446..aa86eaa7255c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss @@ -3,10 +3,8 @@ $generic-treeview-container-with-border-offset: null !default; $generic-treeview-base-container-size: null !default; $generic-treeview-toggle-item-width: null !default; -$generic-treeview-item-height: null !default; $generic-select-all-item-padding: null !default; $generic-treeview-item-padding: null !default; -$generic-treeview-load-indicator-size: null !default; $generic-checkbox-container-size: null !default; $generic-treeview-searchbox-margin-bottom: null !default; $generic-treeview-border-padding: 1px !default; @@ -14,29 +12,27 @@ $generic-treeview-item-element-gap: 8px !default; $generic-select-all-item-border-visible-padding: 20px !default; @if $size == "default" { - $generic-treeview-item-height: 32px; $generic-treeview-base-container-size: 22px !default; $generic-treeview-container-with-border-offset: 7px !default; $generic-checkbox-container-size: 22px !default; $generic-treeview-toggle-item-width: 22px !default; $generic-select-all-item-padding: 9px 40px 11px 40px !default; $generic-treeview-item-padding: 5px 10px !default; - $generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; $generic-treeview-searchbox-margin-bottom: 5px !default; } @else if $size == "compact" { - $generic-treeview-item-height: 28px; $generic-treeview-container-with-border-offset: 6px !default; $generic-treeview-base-container-size: 16px !default; $generic-checkbox-container-size: 16px !default; $generic-treeview-toggle-item-width: 16px !default; $generic-select-all-item-padding: 6px 30px 8px 30px !default; $generic-treeview-item-padding: 4px 6px !default; - $generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; $generic-treeview-searchbox-margin-bottom: 3px !default; } +$generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; + $generic-treeview-container-padding: $generic-treeview-border-padding $generic-treeview-border-padding diff --git a/packages/devextreme-scss/scss/widgets/material/_colors.scss b/packages/devextreme-scss/scss/widgets/material/_colors.scss index 6b8686ed2905..cdf27f7e9c34 100644 --- a/packages/devextreme-scss/scss/widgets/material/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/_colors.scss @@ -31,18 +31,6 @@ $base-text-color: null !default; */ $base-label-color: null !default; -/** -* $name 40. Icon color -* $type color -*/ -$base-icon-color: null !default; - -/** -* $name 50. Arrow icon color -* $type color -*/ -$base-spin-icon-color: null !default; - /** * $name 30. Background color * $type color @@ -55,7 +43,6 @@ $base-bg: null !default; */ $base-border-color: null !default; $base-inverted-bg: null !default; -$base-element-bg: null !default; $base-shadow-color: null !default; /** @@ -76,12 +63,6 @@ $base-warning: null !default; */ $base-danger: null !default; -/** -* $name 80. Hover state text color -* $type color -*/ -$base-hover-color: null !default; - /** * $name 90. Hover state background color * $type color @@ -89,18 +70,11 @@ $base-hover-color: null !default; $base-hover-bg: null !default; $base-inverted-text-color: null !default; -/** -* $name 100. Focused state text color -* $type color -*/ -$base-focus-color: null !default; - /** * $name 110. Focused state background color * $type color */ $base-focus-bg: null !default; -$base-inverted-icon-color: null !default; $base-disabled-opacity: 0.38 !default; $base-disabled-color: null !default; $base-dropdown-shadow-color: null !default; @@ -155,19 +129,13 @@ $scrollview-pulldown-path: null !default; $base-text-color: rgba(0, 0, 0, 0.87) !default; $base-label-color: rgba(0, 0, 0, 0.6) !default; $base-disabled-color: rgba(0, 0, 0, $base-disabled-opacity) !default; - $base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; - $base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; $base-bg: #fff !default; $base-border-color: #e0e0e0 !default; $base-inverted-bg: color.adjust($base-bg, $lightness: -100%, $space: hsl) !default; - $base-element-bg: $base-bg !default; $base-shadow-color: $base-inverted-bg !default; - $base-hover-color: $base-text-color !default; $base-hover-bg: color.change(#000, $alpha: 0.04) !default; $base-inverted-text-color: #fff !default; - $base-focus-color: $base-inverted-text-color !default; $base-focus-bg: color.change(#000, $alpha: 0.04) !default; - $base-inverted-icon-color: $base-inverted-text-color !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; $scrollview-pulldown-path: data-uri("images/widgets/material/color-schemes/light/pulldown.png") !default; @@ -185,24 +153,44 @@ $scrollview-pulldown-path: null !default; $base-text-color: #fff !default; $base-label-color: rgba(255, 255, 255, 0.6) !default; $base-disabled-color: rgba(255, 255, 255, $base-disabled-opacity) !default; - $base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; - $base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; $base-bg: #292929 !default; $base-border-color: #515159 !default; $base-inverted-bg: color.adjust($base-bg, $lightness: 100%, $space: hsl) !default; - $base-element-bg: $base-bg !default; $base-shadow-color: #000 !default; - $base-hover-color: $base-text-color !default; $base-hover-bg: color.change(#fff, $alpha: 0.04) !default; $base-inverted-text-color: rgba(0, 0, 0, 0.87) !default; - $base-focus-color: $base-inverted-text-color !default; $base-focus-bg: color.change(#fff, $alpha: 0.04) !default; - $base-inverted-icon-color: $base-inverted-text-color !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; $scrollview-pulldown-path: data-uri("images/widgets/material/color-schemes/dark/pulldown.png") !default; } +/** +* $name 40. Icon color +* $type color +*/ +$base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; + +/** +* $name 50. Arrow icon color +* $type color +*/ +$base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; +$base-element-bg: $base-bg !default; + +/** +* $name 80. Hover state text color +* $type color +*/ +$base-hover-color: $base-text-color !default; + +/** +* $name 100. Focused state text color +* $type color +*/ +$base-focus-color: $base-inverted-text-color !default; +$base-inverted-icon-color: $base-inverted-text-color !default; + /** * $name 30. Link color * $type color diff --git a/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss index 01d93773d6c3..14a898935fca 100644 --- a/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss @@ -1,2 +1,2 @@ -$material-actionsheet-item-margin-value: 20px; +$material-actionsheet-item-margin-value: 20px !default; $material-actionsheet-item-margin: 0 0 $material-actionsheet-item-margin-value 0 !default; diff --git a/packages/devextreme-scss/scss/widgets/material/button/_colors.scss b/packages/devextreme-scss/scss/widgets/material/button/_colors.scss index d14a977b8059..8e2218b4706a 100644 --- a/packages/devextreme-scss/scss/widgets/material/button/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/button/_colors.scss @@ -13,7 +13,7 @@ $button-shadow-color: null !default; * $type color */ $button-normal-color: $base-text-color !default; -$button-normal-border-color: color.change($button-normal-color, $alpha: 0.24); +$button-normal-border-color: color.change($button-normal-color, $alpha: 0.24) !default; /** * $name 20. Selected state text color diff --git a/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss index 7de9889ee26f..88e97b8ff4a4 100644 --- a/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss @@ -2,7 +2,6 @@ $material-button-horizontal-padding: null !default; $material-button-vertical-padding: null !default; -$material-button-icon-padding: null !default; $material-button-icon-text-padding: null !default; $material-button-min-width: null !default; @@ -19,16 +18,14 @@ $material-button-active-box-shadow-size: 0 4px 6px !default; @if $size == "default" { $material-button-horizontal-padding: 16px !default; $material-button-vertical-padding: 6px !default; - $material-button-icon-padding: 8px !default; - $material-button-icon-text-padding: 12px; + $material-button-icon-text-padding: 12px !default; $material-button-min-width: 64px !default; } @else if $size == "compact" { $material-button-horizontal-padding: 12px !default; $material-button-vertical-padding: 5px !default; - $material-button-icon-padding: 5px !default; - $material-button-icon-text-padding: 8px; + $material-button-icon-text-padding: 8px !default; $material-button-min-width: 48px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss b/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss index f5b547710de0..3f3c19fa0907 100644 --- a/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss @@ -19,19 +19,19 @@ * $name 100. Background color * $type color */ -$cardview-material-common-bg-color: null !default; +$cardview-material-common-bg-color: $base-bg !default; /** * $name 102. Link color * $type color */ -$cardview-material-common-link-color: null !default; +$cardview-material-common-link-color: $base-link-color !default; /** * $name 103. Text color * $type color */ -$cardview-material-common-text-color: null !default; +$cardview-material-common-text-color: $base-text-color !default; // #endregion @@ -41,31 +41,31 @@ $cardview-material-common-text-color: null !default; * $name 200. Background color * $type color */ -$cardview-material-card-bg-color: null !default; +$cardview-material-card-bg-color: $base-bg !default; /** * $name 201. Border color * $type color */ -$cardview-material-card-border-color: null !default; +$cardview-material-card-border-color: $base-border-color !default; /** * $name 203. Divider color * $type color */ -$cardview-material-card-divider-color: null !default; +$cardview-material-card-divider-color: $cardview-material-card-border-color !default; /** * $name 204. Text color * $type color */ -$cardview-material-card-text-color: null !default; +$cardview-material-card-text-color: $cardview-material-common-text-color !default; /** * $name 206. Cover background color * $type color */ -$cardview-material-card__cover-bg-color: null !default; +$cardview-material-card__cover-bg-color: #FFF !default; /** * $name 207. "Empty cover" background color @@ -77,9 +77,9 @@ $cardview-material-card__cover--noimage-bg-color: null !default; * $name 208. "Empty cover" icon color * $type color */ -$cardview-material-card__cover--noimage__icon-color: null !default; +$cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; -$cardview-material-card--focused-outline-color: null !default; +$cardview-material-card--focused-outline-color: $base-accent !default; // #endregion @@ -97,35 +97,23 @@ $cardview-material-card--selected-bg-color: null !default; */ $cardview-material-card--selected-border-color: null !default; -/** -* $name 302. Divider color -* $type color -*/ -$cardview-material-card--selected-divider-color: null !default; - /** * $name 303. Text color * $type color */ -$cardview-material-card--selected-text-color: null !default; +$cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; /** * $name 305. Cover background color * $type color */ -$cardview-material-card--selected__cover-bg-color: null !default; - -/** -* $name 306. "Empty cover" background color -* $type color -*/ -$cardview-material-card--selected__cover--noimage-bg-color: null !default; +$cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; /** * $name 307. "Empty cover" icon color * $type color */ -$cardview-material-card--selected__cover--noimage__icon-color: null !default; +$cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // #endregion @@ -137,37 +125,31 @@ $cardview-material-card--selected__cover--noimage__icon-color: null !default; */ $cardview-material-header-panel__item-bg-color: null !default; -/** -* $name 401. Border color -* $type color -*/ -$cardview-material-header-panel__item-border-color: null !default; - /** * $name 403. Text color * $type color */ -$cardview-material-header-panel__item-text-color: null !default; +$cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; /** * $name 405. Icon color * $type color */ -$cardview-material-header-panel__item__icon-color: null !default; +$cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; /** * $name 406. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; /** * $name 407. Sort index text color * $type color */ -$cardview-material-header-panel__item__sort-index-text-color: null !default; +$cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; -$cardview-material-header-panel__item--focused-outline-color: null !default; +$cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // #endregion @@ -179,103 +161,73 @@ $cardview-material-header-panel__item--focused-outline-color: null !default; */ $cardview-material-header-panel__item--hovered-bg-color: null !default; -/** -* $name 501. Border color -* $type color -*/ -$cardview-material-header-panel__item--hovered-border-color: null !default; - /** * $name 502. Text color * $type color */ -$cardview-material-header-panel__item--hovered-text-color: null !default; +$cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; /** * $name 504. Icon color * $type color */ -$cardview-material-header-panel__item--hovered__icon-color: null !default; +$cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; /** * $name 505. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; /** * $name 506. Sort index text color * $type color */ -$cardview-material-header-panel__item--hovered__sort-index-text-color: null !default; +$cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; // #endregion // #region HEADER PANEL ITEM (DISABLED) -/** -* $name 600. Background color -* $type color -*/ -$cardview-material-header-panel__item--disabled-bg-color: null !default; - -/** -* $name 601. Border color -* $type color -*/ -$cardview-material-header-panel__item--disabled-border-color: null !default; - /** * $name 602. Text color * $type color */ -$cardview-material-header-panel__item--disabled-text-color: null !default; +$cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; /** * $name 604. Icon color * $type color */ -$cardview-material-header-panel__item--disabled__icon-color: null !default; +$cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; /** * $name 605. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; /** * $name 606. Sort index text color * $type color */ -$cardview-material-header-panel__item--disabled__sort-index-text-color: null !default; +$cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // #endregion // #region HEADER PANEL ITEM (ON DRAG) -/** -* $name 700. Background color -* $type color -*/ -$cardview-material-header-panel__item--dragged-bg-color: null !default; - -/** -* $name 701. Border color -* $type color -*/ -$cardview-material-header-panel__item--dragged-border-color: null !default; - /** * $name 702. Text color * $type color */ -$cardview-material-header-panel__item--dragged-text-color: null !default; +$cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; /** * $name 704. "Drag allowed" icon color * $type color */ -$cardview-material-header-panel__item--dragged__allowance-icon-color: null !default; +$cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; /** * $name 705. "Drag blocked" icon color @@ -287,7 +239,7 @@ $cardview-material-header-panel__item--dragged__prohibition-icon-color: null !de * $name 706. Box shadow color * $type color */ -$cardview-material-header-panel__item--dragged-box-shadow-color: null !default; +$cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // #endregion @@ -303,19 +255,19 @@ $cardview-material-header-panel__dropzone-bg-color: null !default; * $name 802. Border color * $type color */ -$cardview-material-header-panel__dropzone-border-color: null !default; +$cardview-material-header-panel__dropzone-border-color: $base-accent !default; /** * $name 803. Text color * $type color */ -$cardview-material-header-panel__dropzone-text-color: null !default; +$cardview-material-header-panel__dropzone-text-color: $base-accent !default; /** * $name 804. Icon color * $type color */ -$cardview-material-header-panel__dropzone__icon-color: null !default; +$cardview-material-header-panel__dropzone__icon-color: $base-accent !default; // #endregion @@ -325,25 +277,25 @@ $cardview-material-header-panel__dropzone__icon-color: null !default; * $name 900. Background color * $type color */ -$cardview-material-filter-panel-bg-color: null !default; +$cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; /** * $name 902. Border color * $type color */ -$cardview-material-filter-panel-border-color: null !default; +$cardview-material-filter-panel-border-color: $base-border-color !default; /** * $name 903. Text color * $type color */ -$cardview-material-filter-panel-text-color: null !default; +$cardview-material-filter-panel-text-color: $base-accent !default; /** * $name 904. Icon color * $type color */ -$cardview-material-filter-panel__icon-color: null !default; +$cardview-material-filter-panel__icon-color: $base-accent !default; // #endregion @@ -353,19 +305,19 @@ $cardview-material-filter-panel__icon-color: null !default; * $name 1000. Background color * $type color */ -$cardview-material-nodata-view-bg-color: null !default; +$cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; /** * $name 1001. Text color * $type color */ -$cardview-material-nodata-view-text-color: null !default; +$cardview-material-nodata-view-text-color: $base-label-color !default; /** * $name 1002. Icon color * $type color */ -$cardview-material-nodata-view__icon-color: null !default; +$cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; /** * $name 1003. Icon background color @@ -393,190 +345,120 @@ $cardview-material-card__content__field-value--highlighted-text-color: null !def * $name 1104. Box shadow color * $type color */ -$cardview-material-card__content__field-value--highlighted-box-shadow-color: null !default; +$cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; // #endregion // #region COLUMN CHOOSER -$cardview-material-columnchooser--empty__message-color: null !default; +$cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; // #endregion @if $mode == "light" { - // Common - $cardview-material-common-bg-color: $base-bg !default; - $cardview-material-common-link-color: $base-link-color !default; - $cardview-material-common-text-color: $base-text-color !default; - // Card - $cardview-material-card-bg-color: $base-bg !default; - $cardview-material-card-border-color: $base-border-color !default; - $cardview-material-card-divider-color: $cardview-material-card-border-color !default; - $cardview-material-card-text-color: $cardview-material-common-text-color !default; - $cardview-material-card__cover-bg-color: #FFF !default; $cardview-material-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; - $cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; - - $cardview-material-card--focused-outline-color: $base-accent !default; // Card - selected $cardview-material-card--selected-bg-color: color.change($base-accent, $alpha: 0.08) !default; $cardview-material-card--selected-border-color: color.change($base-accent, $alpha: 0.32) !default; - $cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; - $cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; - $cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; - $cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; - $cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-material-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; - $cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; - - $cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-material-header-panel__item--hovered-bg-color: color.adjust($cardview-material-header-panel__item-bg-color, $lightness: -4.31%, $space: hsl) !default; - $cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; - $cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; - $cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; - $cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; - $cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // Header panel item - dragged - $cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; - $cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; $cardview-material-header-panel__item--dragged__prohibition-icon-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -4deg, $saturation: 22.8%, $lightness: -9.61%) !default; - $cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-bg-color: color.mix($base-accent, $base-bg, 8%) !default; - $cardview-material-header-panel__dropzone-border-color: $base-accent !default; - $cardview-material-header-panel__dropzone-text-color: $base-accent !default; - $cardview-material-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-filter-panel-border-color: $base-border-color !default; - $cardview-material-filter-panel-text-color: $base-accent !default; - $cardview-material-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-nodata-view-text-color: $base-label-color !default; - $cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; $cardview-material-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; // Search highlight $cardview-material-card__content__field-value--highlighted-text-color: rgba(0, 0, 0, 1) !default; - $cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; } @if $mode == "dark" { - // Common - $cardview-material-common-bg-color: $base-bg !default; - $cardview-material-common-link-color: $base-link-color !default; - $cardview-material-common-text-color: $base-text-color !default; - // Card - $cardview-material-card-bg-color: $base-bg !default; - $cardview-material-card-border-color: $base-border-color !default; - $cardview-material-card-divider-color: $cardview-material-card-border-color !default; - $cardview-material-card-text-color: $cardview-material-common-text-color !default; - $cardview-material-card__cover-bg-color: #FFF !default; $cardview-material-card__cover--noimage-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; - $cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; - - $cardview-material-card--focused-outline-color: $base-accent !default; // Card - selected $cardview-material-card--selected-bg-color: color.change(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $alpha: 0.08) !default; $cardview-material-card--selected-border-color: color.change(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $alpha: 0.32) !default; - $cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; - $cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; - $cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; - $cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; - $cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // Header panel item - default $cardview-material-header-panel__item-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; - $cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; - - $cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-material-header-panel__item--hovered-bg-color: color.adjust($cardview-material-header-panel__item-bg-color, $saturation: -3.47%, $space: hsl, $lightness: 2.16%) !default; - $cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; - $cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; - $cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; - $cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; - $cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // Header panel item - dragged - $cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; - $cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; $cardview-material-header-panel__item--dragged__prohibition-icon-color: color.adjust($base-danger, $saturation: -0.27%, $space: hsl, $lightness: -7.45%) !default; - $cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-bg-color: color.mix(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $base-bg, 8%) !default; - $cardview-material-header-panel__dropzone-border-color: $base-accent !default; - $cardview-material-header-panel__dropzone-text-color: $base-accent !default; - $cardview-material-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-filter-panel-border-color: $base-border-color !default; - $cardview-material-filter-panel-text-color: $base-accent !default; - $cardview-material-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-nodata-view-text-color: $base-label-color !default; - $cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; $cardview-material-nodata-view__icon-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; // Search highlight $cardview-material-card__content__field-value--highlighted-text-color: $base-inverted-text-color !default; - $cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; } +/** +* $name 302. Divider color +* $type color +*/ +$cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; + +/** +* $name 306. "Empty cover" background color +* $type color +*/ +$cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; + +/** +* $name 401. Border color +* $type color +*/ +$cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 501. Border color +* $type color +*/ +$cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; + +/** +* $name 600. Background color +* $type color +*/ +$cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 601. Border color +* $type color +*/ +$cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; + +/** +* $name 700. Background color +* $type color +*/ +$cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 701. Border color +* $type color +*/ +$cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; + $cardview-bg-color: $cardview-material-common-bg-color !default; $cardview-link-color: $cardview-material-common-link-color !default; $cardview-text-color: $cardview-material-common-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss index c7ef0ddf524f..e67175a11df0 100644 --- a/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-material-common-border-radius: null !default; +$cardview-material-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-common-text-weight: null !default; +$cardview-material-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-material-common-padding: null !default; // #region CARD -$cardview-material-card-min-width: null !default; +$cardview-material-card-min-width: 250px !default; -$cardview-material-card-border-width: null !default; +$cardview-material-card-border-width: 1px !default; /** * $name 202. Border radius @@ -73,7 +73,7 @@ $cardview-material-card__content-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card__field-caption-weight: null !default; +$cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: null !default; @@ -85,7 +85,7 @@ $cardview-material-card__content-row-gap: null !default; $cardview-material-card__content__cell-padding-vertical: null !default; -$cardview-material-card__content__cell-padding-horizontal: null !default; +$cardview-material-card__content__cell-padding-horizontal: 12px !default; // #endregion @@ -96,19 +96,19 @@ $cardview-material-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card--selected__field-caption-weight: null !default; +$cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; // #endregion // #region HEADER -$cardview-material-header-gap: null !default; +$cardview-material-header-gap: 4px !default; // #endregion // #region HEADER PANEL -$cardview-material-header-panel-vertical-margin: null !default; +$cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: null !default; @@ -116,13 +116,13 @@ $cardview-material-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-material-header-panel__item-border-width: null !default; +$cardview-material-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-material-header-panel__item-border-radius: null !default; +$cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-vertical: null !default; @@ -135,14 +135,14 @@ $cardview-material-header-panel__item-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item-text-weight: null !default; +$cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item__sort-index-text-weight: null !default; +$cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: null !default; @@ -158,14 +158,14 @@ $cardview-material-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--hovered-text-weight: null !default; +$cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; // #endregion @@ -176,14 +176,14 @@ $cardview-material-header-panel__item--hovered__sort-index-text-weight: null !de * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--disabled-text-weight: null !default; +$cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; // #endregion @@ -194,10 +194,10 @@ $cardview-material-header-panel__item--disabled__sort-index-text-weight: null !d * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--dragged-text-weight: null !default; +$cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; -$cardview-material-header-item__sort-indicator__border-size: null; -$cardview-material-header-item__sort-indicator__border-radius: null; +$cardview-material-header-item__sort-indicator__border-size: null !default; +$cardview-material-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -207,7 +207,7 @@ $cardview-material-header-panel__dropzone-height: null !default; $cardview-material-header-panel__dropzone-top: null !default; -$cardview-material-header-panel__dropzone-border-width: null !default; +$cardview-material-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -223,7 +223,7 @@ $cardview-material-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-material-filter-panel-border-radius: null !default; +$cardview-material-filter-panel-border-radius: 0 !default; // #endregion @@ -243,16 +243,16 @@ $cardview-material-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-material-card__content__field-value--highlighted-border-radius: null !default; +$cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-material-card__content__field-value--highlighted-border-padding: null !default; +$cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card__content__field-value--highlighted-text-weight: null !default; +$cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -267,84 +267,48 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def @if $size == "default" { // Common - $cardview-material-common-border-radius: 16px !default; - $cardview-material-common-text-weight: 400 !default; $cardview-material-common-cards-row-gap: 16px !default; $cardview-material-common-cards-column-gap: 16px !default; $cardview-material-common-gap: 24px !default; $cardview-material-common-padding: 24px !default; // Card - $cardview-material-card-min-width: 250px !default; - $cardview-material-card-border-width: 1px !default; $cardview-material-card-border-radius: 8px !default; $cardview-material-card__header-height: 52px !default; $cardview-material-card__header-text-size: 16px !default; $cardview-material-card__header__toolbar-override-height: 52px !default; $cardview-material-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-material-card__header-toolbar-override-height: 52px !default; - $cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: 180px !default; $cardview-material-card__cover--noimage__icon-size: 48px !default; $cardview-material-card__content-padding: 12px !default; $cardview-material-card__content-row-gap: 4px !default; $cardview-material-card__content__cell-padding-vertical: 6px !default; - $cardview-material-card__content__cell-padding-horizontal: 12px !default; - - // Card - selected - $cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; - - // Header - $cardview-material-header-gap: 4px !default; // Header panel - $cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: 36px !default; // Header panel item - default - $cardview-material-header-panel__item-border-width: 1px !default; - $cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-horizontal: 16px !default; $cardview-material-header-panel__item-padding-vertical: 7px !default; $cardview-material-header-item__left-padding: 6px !default; $cardview-material-header-item__right-padding: 2px !default; $cardview-material-header-panel__item-line-height: 20px !default; - $cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; - $cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: 20px !default; - // Header panel item - hovered - $cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - // Header panel item - dragged - $cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; $cardview-material-header-item__sort-indicator__border-size: 2px !default; $cardview-material-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-height: 44px !default; $cardview-material-header-panel__dropzone-top: -4px !default; - $cardview-material-header-panel__dropzone-border-width: 1.5px !default; $cardview-material-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-material-filter-panel-border-radius: 0 !default; - // Empty view $cardview-material-nodata-view__icon-container-size: 64px !default; $cardview-material-nodata-view__icon-container-margin: 12px !default; $cardview-material-nodata-view__icon-size: 40px !default; - // Search highlight - $cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-material-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-material-column-chooser__select-all-item-horizontal-padding: 19px !default; @@ -353,22 +317,16 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def @else if $size == "compact" { // Common - $cardview-material-common-border-radius: 16px !default; - $cardview-material-common-text-weight: 400 !default; $cardview-material-common-cards-row-gap: 12px !default; $cardview-material-common-cards-column-gap: 12px !default; $cardview-material-common-gap: 16px !default; $cardview-material-common-padding: 16px !default; // Card - $cardview-material-card-min-width: 250px !default; - $cardview-material-card-border-width: 1px !default; $cardview-material-card-border-radius: 6px !default; $cardview-material-card__header-height: 44px !default; $cardview-material-card__header-text-size: 14px !default; $cardview-material-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-material-card__header-toolbar-override-height: 44px !default; - $cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: 140px !default; $cardview-material-card__cover--noimage__icon-size: 32px !default; $cardview-material-card__content-padding: 8px !default; @@ -376,62 +334,32 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def $cardview-material-card__content-font-size: 12px !default; $cardview-material-card__content-line-height: 16px !default; $cardview-material-card__content__cell-padding-vertical: 4px !default; - $cardview-material-card__content__cell-padding-horizontal: 12px !default; - - // Card - selected - $cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; - - // Header - $cardview-material-header-gap: 4px !default; // Header panel - $cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: 32px !default; // Header panel item - default - $cardview-material-header-panel__item-border-width: 1px !default; - $cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-horizontal: 12px !default; $cardview-material-header-panel__item-padding-vertical: 5px !default; $cardview-material-header-item__left-padding: 3px !default; $cardview-material-header-item__right-padding: 1px !default; $cardview-material-header-panel__item-line-height: 16px !default; - $cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; - $cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: 16px !default; - // Header panel item - hovered - $cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - // Header panel item - dragged - $cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; $cardview-material-header-item__sort-indicator__border-size: 1px !default; $cardview-material-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-height: 38px !default; $cardview-material-header-panel__dropzone-top: -3px !default; - $cardview-material-header-panel__dropzone-border-width: 1.5px !default; $cardview-material-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-material-filter-panel-border-radius: 0 !default; - // Empty view $cardview-material-nodata-view__icon-container-size: 48px !default; $cardview-material-nodata-view__icon-container-margin: 8px !default; $cardview-material-nodata-view__icon-size: 32px !default; - // Search highlight - $cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-material-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-material-column-chooser__select-all-item-horizontal-padding: 17px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss index eee7e7d54d74..8bb57067f07c 100644 --- a/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss @@ -56,7 +56,6 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; @@ -115,7 +114,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 11px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 16px !default; $chat-confirmation-popup-toolbar-padding-block: 0 16px !default; @@ -156,7 +154,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 13) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 8px !default; $chat-confirmation-popup-toolbar-padding-block: 0 8px !default; @@ -165,3 +162,5 @@ $chat-bubble-gap: 8px !default; $chat-file-icon-font-size: 16px !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss index 119dfc9588ae..cb1034a3b0dd 100644 --- a/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss @@ -7,7 +7,7 @@ @use "../../base/checkBox"; @use "../../base/checkBox/mixins" as *; -$material-checkbox-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); +$material-checkbox-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; .dx-checkbox { .dx-checkbox-container { diff --git a/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss index 556fe5fe74c5..deb1bfcbfced 100644 --- a/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$material-colorview-alpha-width: $material-colorview-palette-width + 4; -$material-colorview-hue-scale-height: $material-colorview-palette-height; +$material-colorview-alpha-width: $material-colorview-palette-width + 4 !default; +$material-colorview-hue-scale-height: $material-colorview-palette-height !default; .dx-colorview-container { width: $material-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss index 9d81f4600169..a4c30cea256a 100644 --- a/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss @@ -8,7 +8,7 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss index 38c27e283940..902b0493def2 100644 --- a/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss @@ -9,7 +9,7 @@ @use "../../base/icon_fonts" as *; @use "../textEditor" as *; -$material-editor-custom-button-margin: 5px; +$material-editor-custom-button-margin: 5px !default; .dx-daterangebox { @include dx-dropdowneditor-button-icon-styling(daterangepicker); diff --git a/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss index 936652fe63ca..003ec8b587fe 100644 --- a/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss @@ -6,7 +6,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$material-dateview-roller-padding: math.div($material-dateview-roller-height - $material-dateview-roller-item-height, 2); +$material-dateview-roller-padding: math.div($material-dateview-roller-height - $material-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss index eb57e7b9e913..c436fcd6eebe 100644 --- a/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss @@ -15,10 +15,10 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$material-diagram-toolbar-icon-size: $material-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; +$material-diagram-toolbar-icon-size: $material-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; -$diagram-toolbox-title-size: 18px; +$diagram-toolbox-title-size: 18px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss index 57c4be674692..60ff376fd31c 100644 --- a/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss @@ -5,10 +5,10 @@ $material-dropdownbutton-icon-spindown-size: null !default; @if $size == "default" { $material-dropdownbutton-action-horizontal-padding: 12px !default; - $material-dropdownbutton-icon-spindown-size: 24px; + $material-dropdownbutton-icon-spindown-size: 24px !default; } @else if $size == "compact" { $material-dropdownbutton-action-horizontal-padding: 8px !default; - $material-dropdownbutton-icon-spindown-size: 18px; + $material-dropdownbutton-icon-spindown-size: 18px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss index aecf0ce9c787..e71b1065dae8 100644 --- a/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss @@ -8,17 +8,17 @@ @use "../../base/dropDownEditor"; @use "../../base/dropDownEditor/mixins" as *; -$material-dropdowneditor-container-button-size: $material-base-icon-size; +$material-dropdowneditor-container-button-size: $material-base-icon-size !default; $material-dropdowneditor-invalid-badge-offset: $material-dropdowneditor-container-button-size + - $material-invalid-badge-halfhorizontal-padding; + $material-invalid-badge-halfhorizontal-padding !default; $material-dropdowneditor-invalid-badge-with-clear-offset: $material-dropdowneditor-container-button-size * 2 + - $material-invalid-badge-halfhorizontal-padding; + $material-invalid-badge-halfhorizontal-padding !default; -$material-dropdowneditor-invalid-badge-size: $material-invalid-badge-size + $material-invalid-badge-horizontal-padding; +$material-dropdowneditor-invalid-badge-size: $material-invalid-badge-size + $material-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $material-dropdowneditor-container-button-size; diff --git a/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss index ed7f90e27e38..f0a6358c9177 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss @@ -16,14 +16,11 @@ $filemanager-file-item-hover-bg: $base-hover-bg !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; $filemanager-dirs-tree-item-focus-bg: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $mode == "light" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $mode == "dark" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change(color.adjust($base-shadow-color, $lightness: -100%, $space: hsl), $alpha: 0.3) !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss index 8a8635282724..710c1b09d13f 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss @@ -15,7 +15,7 @@ $filemanager-large-icon-size: null !default; $filemanager-progress-panel-title-padding: null !default; $filemanager-progress-box-button-close-margin-top: null !default; -$filemanager-toolbar-height: $material-toolbar-height; +$filemanager-toolbar-height: $material-toolbar-height !default; @if $size == "default" { $filemanager-toolbar-separator-height: 36px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss index bb9fcb08ec53..1d2218ae1772 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss @@ -6,7 +6,7 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$material-fileuploader-file-wrapper-border-size: 3px; +$material-fileuploader-file-wrapper-border-size: 3px !default; .dx-fileuploader-wrapper { padding: $material-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss index 7956eae8fdf4..a0d2d94e429e 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss @@ -3,7 +3,7 @@ $material-fileuploader-file-container-padding: null !default; $material-fileuploader-file-container-text-margin: null !default; $material-fileuploader-file-container-button-margin: null !default; -$material-fileuploader-file-cancel-button-margin-left: 4px; +$material-fileuploader-file-cancel-button-margin-left: 4px !default; $material-fileuploader-file-container-button-size: null !default; $material-fileuploader-file-icon-font-size: null !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss b/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss index de5874276173..fe96fa4a1ac0 100644 --- a/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss @@ -12,8 +12,8 @@ $gantt-task-background-color: null !default; @if $color == "lime" { $gantt-parent-task-background-color: #f44336 !default; $gantt-task-progress-background-color: rgba(0, 0, 0, 0.2) !default; - $gantt-task-color: #fff; - $gantt-task-background-color: #999; + $gantt-task-color: #fff !default; + $gantt-task-background-color: #999 !default; } @if $color == "blue" { @@ -34,8 +34,8 @@ $gantt-task-background-color: null !default; @if $color == "orange" or $color == "purple" or $color == "teal" { $gantt-parent-task-background-color: #8bc34a !default; $gantt-task-progress-background-color: rgba(0, 0, 0, 0.2) !default; - $gantt-task-color: #fff; - $gantt-task-background-color: #999; + $gantt-task-color: #fff !default; + $gantt-task-background-color: #999 !default; } @if $mode == "light" { diff --git a/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss index fcdcfef389c2..4a5b51c944b4 100644 --- a/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss @@ -9,7 +9,7 @@ @use "../form/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss index 3c3662d4e159..ce10df69f8c6 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss @@ -35,7 +35,6 @@ $datagrid-drag-header-border-color: color.change($base-accent, $alpha: 0.5) !def $datagrid-drag-header-shadow-color: null !default; $datagrid-drag-header-second-shadow-color: null !default; $datagrid-header-drag-bg: $datagrid-base-background-color !default; -$header-filter-color: null !default; /** * $name 51. Selected row background color @@ -82,7 +81,6 @@ $datagrid-menu-icon-color: inherit !default; $datagrid-cell-modified-border-color: null !default; $datagrid-cell-removed-border-color: $datagrid-border-color !default; $datagrid-row-removed-bg: $datagrid-border-color !default; -$datagrid-cell-removed-text-color: null !default; /** * $name 45. Invalid data faded color @@ -166,8 +164,8 @@ $datagrid-text-stub-background-image-path: null !default; $datagrid-filter-panel-color: $base-accent !default; $datagrid-draggable-column-text-color: color.change($base-text-color, $alpha: 0.38) !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity !default; @if $mode == "light" { $datagrid-cell-modified-border-color: color.adjust($base-success, $lightness: 51%, $space: hsl) !default; @@ -175,12 +173,10 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: -12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.1) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: $datagrid-columnchooser-bg !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; $datagrid-hover-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; $datagrid-text-stub-background-image-path: data-uri("images/widgets/material/color-schemes/light/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-row-invalid-faded-border-color: color.adjust($base-danger, $lightness: 44%, $space: hsl) !default; } @@ -190,15 +186,16 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: 12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: color.adjust($datagrid-columnchooser-bg, $lightness: 3%, $space: hsl) !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; $datagrid-hover-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; $datagrid-text-stub-background-image-path: data-uri("images/widgets/material/color-schemes/dark/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-row-invalid-faded-border-color: color.adjust($base-danger, $lightness: -31%, $space: hsl) !default; } +$header-filter-color: $datagrid-columnchooser-item-color !default; +$datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; + $header-filter-color-empty: $header-filter-color !default; :root { diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss index 1afc72cc3cfb..7ea5a0c74fb6 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss @@ -28,12 +28,12 @@ @use "../overlay"; @use "../pagination"; -$material-grid-base-border-hidden: 1px solid transparent; -$material-grid-base-row-border: 1px solid transparent; -$material-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$material-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; +$material-grid-base-border-hidden: 1px solid transparent !default; +$material-grid-base-row-border: 1px solid transparent !default; +$material-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$material-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; -$material-grid-base-group-panel-message-line-height: $material-button-text-line-height; +$material-grid-base-group-panel-message-line-height: $material-button-text-line-height !default; @mixin grid-base($widget-name) { @include material-grid-base-cell($widget-name); diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss index 0ea533d4e9aa..928d14b042ab 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss @@ -15,11 +15,10 @@ $material-grid-base-grouppanel-item-margin: null !default; $material-grid-base-command-edit-column-width: null !default; $material-grid-base-command-edit-column-with-icons-width: null !default; $material-grid-base-adaptive-column-height: null !default; -$material-grid-base-treeview-search-margin: null !default; +$material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: null !default; $material-command-edit-icon-size: null !default; $material-filter-row-between-editor-padding: null !default; -$material-command-column-additional-right-margin: null !default; $material-grid-base-treeview-select-all-item-offset: null !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: null !default; $material-grid-base-command-ai-text-content-padding: null !default; @@ -51,12 +50,10 @@ $material-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $material-grid-base-command-edit-column-width: 115px !default; $material-grid-base-command-edit-column-with-icons-width: 93px !default; $material-grid-base-adaptive-column-height: 40px !default; - $material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: 17px !default; $material-command-edit-icon-size: 18px !default; $material-filter-row-between-editor-padding: 12px !default; - $material-command-column-additional-right-margin: 0 !default; $material-grid-base-treeview-select-all-item-offset: 43px !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: 24px !default; @@ -83,12 +80,10 @@ $material-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $material-grid-base-command-edit-column-width: 105px !default; $material-grid-base-command-edit-column-with-icons-width: 80px !default; $material-grid-base-adaptive-column-height: 26px !default; - $material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: 14px !default; $material-command-edit-icon-size: 16px !default; $material-filter-row-between-editor-padding: 8px !default; - $material-command-column-additional-right-margin: 2px !default; $material-grid-base-treeview-select-all-item-offset: 35px !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss index 9ad1220cd023..4014da737971 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss @@ -2,9 +2,9 @@ @use "colors" as *; @use "sizes" as *; -$grid-masterdetail-padding: 16px; -$material-grid-base-border-color: $datagrid-border-color; -$material-grid-base-header-cell-vertical-padding: math.round(math.div($material-grid-base-header-height - $material-grid-base-header-line-height, 2)); -$material-grid-base-command-select-column-width: $material-grid-base-checkbox-size + 2 * $material-grid-base-cell-horizontal-padding; -$material-grid-base-cell-vertical-padding: math.round(math.div($material-grid-base-cell-height - $material-grid-base-cell-line-height, 2)); -$material-grid-base-cell-padding: $material-grid-base-cell-vertical-padding $material-grid-base-cell-horizontal-padding; +$grid-masterdetail-padding: 16px !default; +$material-grid-base-border-color: $datagrid-border-color !default; +$material-grid-base-header-cell-vertical-padding: math.round(math.div($material-grid-base-header-height - $material-grid-base-header-line-height, 2)) !default; +$material-grid-base-command-select-column-width: $material-grid-base-checkbox-size + 2 * $material-grid-base-cell-horizontal-padding !default; +$material-grid-base-cell-vertical-padding: math.round(math.div($material-grid-base-cell-height - $material-grid-base-cell-line-height, 2)) !default; +$material-grid-base-cell-padding: $material-grid-base-cell-vertical-padding $material-grid-base-cell-horizontal-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss index a5cd8a835bab..c8f045a350f9 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $material-base-icon-size - 4px; +$ai-chat-message-icon-size: $material-base-icon-size - 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss index 03c12ee590fa..adbf5ec48cd4 100644 --- a/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss @@ -4,7 +4,7 @@ $material-toolbar-size-editor-width: null !default; $material-htmleditor-toolbar-padding: null !default; $material-html-editor-add-image-dialog-fileuploader-padding: null !default; -$material-aidialog-selects-gap: null !default; +$material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: null !default; $material-aidialog-content-padding: null !default; $material-aidialog-toolbar-padding: null !default; @@ -14,7 +14,6 @@ $material-aidialog-border-radius: $base-border-radius * 2 !default; $material-toolbar-size-editor-width: 120px !default; $material-htmleditor-toolbar-padding: 16px !default; $material-html-editor-add-image-dialog-fileuploader-padding: 50px 40px !default; - $material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: 24px !default; $material-aidialog-content-padding: 24px !default; $material-aidialog-toolbar-padding: 0 24px 16px !default; @@ -24,7 +23,6 @@ $material-aidialog-border-radius: $base-border-radius * 2 !default; $material-toolbar-size-editor-width: 90px !default; $material-htmleditor-toolbar-padding: 11px !default; $material-html-editor-add-image-dialog-fileuploader-padding: 40px 30px !default; - $material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: 16px !default; $material-aidialog-content-padding: 16px !default; $material-aidialog-toolbar-padding: 0 16px 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss index d8059cab008d..fd8e7ba5a2e7 100644 --- a/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss @@ -7,11 +7,11 @@ $informer-info-color: $base-text-color !default; $informer-info-background-color: null !default; @if $mode == "light" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -24.92%, $lightness: 38.24%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -240deg, $saturation: -100.00%, $lightness: 48.63%); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -24.92%, $lightness: 38.24%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -240deg, $saturation: -100.00%, $lightness: 48.63%) !default; } @if $mode == "dark" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -41.80%, $lightness: -53.92%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: 240deg, $saturation: 6.04%, $lightness: -70.78%); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -41.80%, $lightness: -53.92%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: 240deg, $saturation: 6.04%, $lightness: -70.78%) !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/list/_index.scss b/packages/devextreme-scss/scss/widgets/material/list/_index.scss index 0394033ee1eb..1f6f61db64c5 100644 --- a/packages/devextreme-scss/scss/widgets/material/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/list/_index.scss @@ -13,11 +13,11 @@ @use "../radioGroup/colors" as *; @use "../badge"; -$material-list-item-border-width: 1px; -$material-list-border-width: 1px; -$material-list-border: $material-list-border-width solid; -$material-list-item-border: $material-list-item-border-width solid; -$material-list-searchbox-padding-top: $material-list-searchbox-vertical-padding - 2px; +$material-list-item-border-width: 1px !default; +$material-list-border-width: 1px !default; +$material-list-border: $material-list-border-width solid !default; +$material-list-item-border: $material-list-item-border-width solid !default; +$material-list-searchbox-padding-top: $material-list-searchbox-vertical-padding - 2px !default; .dx-list-item-chevron { @include dx-icon(chevronnext); diff --git a/packages/devextreme-scss/scss/widgets/material/menu/_index.scss b/packages/devextreme-scss/scss/widgets/material/menu/_index.scss index 86d8eec7b186..74feb67ec42e 100644 --- a/packages/devextreme-scss/scss/widgets/material/menu/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/menu/_index.scss @@ -9,7 +9,7 @@ @use "../menuBase" as *; @use "../../base/menu"; -$material-menu-font-size: $material-base-font-size; +$material-menu-font-size: $material-base-font-size !default; .dx-context-menu-container-border { background-color: transparent; diff --git a/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss index c58fb28cc838..e9cff178ebc9 100644 --- a/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss @@ -8,7 +8,7 @@ @use "../../base/mixins" as *; @use "../../base/menuBase"; -$material-menu-arrow-image-size: 7px; +$material-menu-arrow-image-size: 7px !default; .dx-menu-item { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss index cbcdc4c94b66..697bd594e2e1 100644 --- a/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss @@ -3,7 +3,7 @@ $material-menu-item-line-height: null !default; $material-menu-item-with-icon-horizontal-padding: null !default; $material-menu-horizontal-padding: null !default; -$material-menu-icon-to-text-offset: 8px; +$material-menu-icon-to-text-offset: 8px !default; @if $size == "default" { $material-menu-item-line-height: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss index e8f0a912e74c..fedacbe636cf 100644 --- a/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss @@ -32,12 +32,12 @@ $pivotgrid-button-top-padding: $pivotgrid-button-top-padding, ); -$material-pivotgrid-headers-font-size: 12px; -$material-pivotgrid-area-font-size: 13px; -$material-pivotgrid-chevron-icon-size: 20px; +$material-pivotgrid-headers-font-size: 12px !default; +$material-pivotgrid-area-font-size: 13px !default; +$material-pivotgrid-chevron-icon-size: 20px !default; -$material-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15); -$material-pivotgridfieldchooser-field-min-height: 36px; +$material-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15) !default; +$material-pivotgridfieldchooser-field-min-height: 36px !default; .dx-pivotgrid-fields-container { &.dx-drag { diff --git a/packages/devextreme-scss/scss/widgets/material/popup/_index.scss b/packages/devextreme-scss/scss/widgets/material/popup/_index.scss index 0082098d5238..bfbb2902eed0 100644 --- a/packages/devextreme-scss/scss/widgets/material/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/popup/_index.scss @@ -8,7 +8,7 @@ @use "../../base/popup"; @use "../overlay"; -$material-popup-toolbar-label-font-size: $material-m-font-size; +$material-popup-toolbar-label-font-size: $material-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss b/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss index 58b93ad7bfc8..66c0e933c0fa 100644 --- a/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss @@ -4,9 +4,9 @@ @use "../../base/mixins" as *; @use "../../base/progressBar"; -$material-bar-height: 4px; -$material-background-width: 90%; -$material-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; +$material-bar-height: 4px !default; +$material-background-width: 90% !default; +$material-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; .dx-progressbar-container { height: $material-bar-height; diff --git a/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss b/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss index 960a46991c2a..f42753264aab 100644 --- a/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss @@ -6,7 +6,7 @@ @use "../radioGroup/colors" as *; @use "../../base/radioButton"; -$material-radiobutton-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); +$material-radiobutton-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; @include dx-radiobutton-icon-mixin( $material-radiobutton-size, diff --git a/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss b/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss index 583170ede72f..d73e48a91ceb 100644 --- a/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss @@ -2,7 +2,7 @@ @use "sizes" as *; @use "../../base/rangeSlider"; -$material-slider-handle-margin: -14px; +$material-slider-handle-margin: -14px !default; .dx-rangeslider-start-handle { margin-inline-start: $material-slider-handle-margin; diff --git a/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss index 2ca39d753187..48e26a618efb 100644 --- a/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss @@ -4,9 +4,9 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$material-recurrence-number-box-margin: 11px; -$material-recurrence-button-group-padding: 6px; -$material-recurrence-radio-group-margin: 15px; +$material-recurrence-number-box-margin: 11px !default; +$material-recurrence-button-group-padding: 6px !default; +$material-recurrence-radio-group-margin: 15px !default; .dx-recurrence-numberbox-interval { margin-right: $material-recurrence-number-box-margin; diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss index 1d3349f02d9d..1468a9d68cfb 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss @@ -35,7 +35,7 @@ $scheduler-appointment-base-color: $base-accent !default; * $type color */ $scheduler-appointment-start-color: color.change($base-shadow-color, $alpha: 0.3) !default; -$scheduler-appointment-active-color: $scheduler-appointment-start-color; +$scheduler-appointment-active-color: $scheduler-appointment-start-color !default; /** * $name 60. Focused cell color @@ -74,8 +74,8 @@ $scheduler-fill-focused-appointment: false !default; $scheduler-is-shadow-color-for-focused-state: true !default; $scheduler-time-indicator-background-color: rgba(0, 0, 0, 0.03) !default; $scheduler-first-month-cell-background-color: color.adjust($scheduler-appointment-base-color, $alpha: -0.85) !default; -$material-scheduler-toolbar-color: $base-accent; -$material-scheduler-toolbar-active-color: color.change($base-accent, $alpha: 0.08); +$material-scheduler-toolbar-color: $base-accent !default; +$material-scheduler-toolbar-active-color: color.change($base-accent, $alpha: 0.08) !default; $scheduler-tooltip-appointment-text-color: $base-text-color !default; $scheduler-appointment-overlay-bg: $base-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss index e655da5b8b04..7b42c6fb901a 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss @@ -53,17 +53,17 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px; -$scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px; +$scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px !default; +$scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px !default; -$material-scheduler-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 1.5; -$material-scheduler-collapsed-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 0.5; +$material-scheduler-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 1.5 !default; +$material-scheduler-collapsed-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 0.5 !default; -$material-scheduler-month-date-text-size: 14px; +$material-scheduler-month-date-text-size: 14px !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$material-scheduler-agenda-time-panel-cell-padding: 8px; +$material-scheduler-agenda-time-panel-cell-padding: 8px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss index 26815ff6d53b..d8178c42da4d 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss @@ -4,45 +4,43 @@ $material-scheduler-workspace-date-table-cell-height: null !default; $material-scheduler-toolbar-item-offset: null !default; -$material-scheduler-navigator-height: null !default; $material-scheduler-header-panel-day-font-size: null !default; $material-scheduler-appointment-tooltip-title-font-size: null !default; $material-scheduler-appointment-tooltip-date-font-size: null !default; $material-scheduler-workspace-month-cell-left-offset: null !default; $material-scheduler-header-panel-table-cell-height: null !default; $material-scheduler-appointment-content-padding: null !default; -$material-scheduler-dropdown-appointment-margin-top: null !default; $material-scheduler-dropdown-button-height: null !default; $material-scheduler-dropdown-button-padding: null !default; $material-scheduler-dropdown-button-margin: null !default; $material-scheduler-dropdown-button-vertical-margin: null !default; -$material-scheduler-dropdown-button-adaptive-margin: null !default; -$material-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: null !default; $material-tooltip-marker-dimension: null !default; $material-scheduler-appointment-tooltip-title-margin-top: null !default; $material-scheduler-appointment-tooltip-remove-padding-top: null !default; -$material-scheduler-appointment-short-content-padding: null !default; -$material-scheduler-appointment-15min-height: null !default; -$material-scheduler-appointment-20min-height: null !default; -$material-scheduler-appointment-25min-height: null !default; -$material-scheduler-appointment-10min-recurrence-padding-right: null !default; -$material-scheduler-appointment-10min-title-font-size: null !default; -$material-scheduler-appointment-10min-title-line-height: null !default; -$material-scheduler-appointment-10min-icon-font-size: null !default; -$material-scheduler-appointment-10min-icon-right: null !default; +$material-scheduler-appointment-short-content-padding: 0 7px !default; +$material-scheduler-appointment-15min-height: 17px !default; +$material-scheduler-appointment-20min-height: 24px !default; +$material-scheduler-appointment-25min-height: 32px !default; +$material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; +$material-scheduler-appointment-10min-title-font-size: 8px !default; +$material-scheduler-appointment-10min-title-line-height: 12px !default; +$material-scheduler-appointment-10min-icon-font-size: 12px !default; +$material-scheduler-appointment-10min-icon-right: 2px !default; $material-scheduler-appointment-15min-recurrence-padding-right: null !default; -$material-scheduler-appointment-15min-title-font-size: null !default; +$material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: null !default; $material-scheduler-appointment-15min-icon-font-size: null !default; -$material-scheduler-appointment-15min-icon-right: null !default; -$material-scheduler-appointment-20min-recurrence-padding-right: null !default; -$material-scheduler-appointment-20min-icon-right: null !default; +$material-scheduler-appointment-15min-icon-right: 2px !default; +$material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; +$material-scheduler-appointment-20min-icon-right: 4px !default; $scheduler-left-column-width: 65px !default; $material-scheduler-timeline-date-table-cell-height: 50px !default; -$material-scheduler-navigator-icon-font-size: $material-base-icon-size; +$material-scheduler-navigator-icon-font-size: $material-base-icon-size !default; $material-scheduler-appointment-month-text-size: 12px !default; $material-scheduler-workspace-info-text-size: 16px !default; $material-scheduler-all-day-title-font-size: 12px !default; @@ -80,7 +78,6 @@ $scheduler-timeline-cell-height: 50px !default; @if $size == "default" { $material-scheduler-workspace-date-table-cell-height: 38px !default; - $material-scheduler-navigator-height: 36px !default; $material-scheduler-header-panel-day-font-size: 30px !default; $material-scheduler-header-height: 54px !default; $material-scheduler-header-offset: 2px 16px !default; @@ -92,13 +89,10 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-header-panel-table-cell-height: 56px !default; $material-scheduler-appointment-content-padding: 5px 7px !default; - $material-scheduler-dropdown-appointment-margin-top: 7px !default; $material-scheduler-dropdown-button-height: 22px !default; $material-scheduler-dropdown-button-padding: 4px !default; $material-scheduler-dropdown-button-margin: 5px 1px 3px 3px !default; $material-scheduler-dropdown-button-vertical-margin: 5px 2px 3px 1px !default; - $material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: 10px !default; $material-tooltip-marker-dimension: 24px !default; @@ -120,33 +114,16 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-appointment-popup-days-of-week-buttons-gap: 8px !default; $material-scheduler-appointment-popup-repeat-end-item-height: 48px !default; - $agenda-appointment-title-font-size: 14px; - - $material-scheduler-appointment-short-content-padding: 0 7px !default; - $material-scheduler-appointment-15min-height: 17px !default; - $material-scheduler-appointment-20min-height: 24px !default; - $material-scheduler-appointment-25min-height: 32px !default; - - $material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-10min-title-font-size: 8px !default; - $material-scheduler-appointment-10min-title-line-height: 12px !default; - $material-scheduler-appointment-10min-icon-font-size: 12px !default; - $material-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 14px !default; $material-scheduler-appointment-15min-recurrence-padding-right: 18px !default; - $material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: 18px !default; $material-scheduler-appointment-15min-icon-font-size: 16px !default; - $material-scheduler-appointment-15min-icon-right: 2px !default; - - $material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; - $material-scheduler-appointment-20min-icon-right: 4px !default; } @else if $size == "compact" { $material-scheduler-workspace-date-table-cell-height: 28px !default; - $material-scheduler-navigator-height: 28px !default; $material-scheduler-header-panel-day-font-size: 20px !default; $material-scheduler-header-height: 38px !default; $material-scheduler-header-offset: 0 8px !default; @@ -158,13 +135,10 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-header-panel-table-cell-height: 40px !default; $material-scheduler-appointment-content-padding: 2px 4px !default; - $material-scheduler-dropdown-appointment-margin-top: 3px !default; $material-scheduler-dropdown-button-height: 18px !default; $material-scheduler-dropdown-button-padding: 2px !default; $material-scheduler-dropdown-button-margin: 2px 1px 2px 2px !default; $material-scheduler-dropdown-button-vertical-margin: 2px 1px 2px 1px !default; - $material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: 8px !default; $material-tooltip-marker-dimension: 18px !default; @@ -186,25 +160,9 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-appointment-popup-days-of-week-buttons-gap: 4px !default; $material-scheduler-appointment-popup-repeat-end-item-height: 32px !default; - $agenda-appointment-title-font-size: 13px; - - $material-scheduler-appointment-short-content-padding: 0 7px !default; - $material-scheduler-appointment-15min-height: 17px !default; - $material-scheduler-appointment-20min-height: 24px !default; - $material-scheduler-appointment-25min-height: 32px !default; - - $material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-10min-title-font-size: 8px !default; - $material-scheduler-appointment-10min-title-line-height: 12px !default; - $material-scheduler-appointment-10min-icon-font-size: 12px !default; - $material-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 13px !default; $material-scheduler-appointment-15min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: 12px !default; $material-scheduler-appointment-15min-icon-font-size: 12px !default; - $material-scheduler-appointment-15min-icon-right: 2px !default; - - $material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; - $material-scheduler-appointment-20min-icon-right: 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss b/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss index b91875142119..ea4f3b80cd11 100644 --- a/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss @@ -6,7 +6,7 @@ @use "../scrollable"; @use "../../base/scrollView"; -$scrollview-loading-indicator-size: 42px; +$scrollview-loading-indicator-size: 42px !default; .dx-scrollview-scrollbottom-text, .dx-scrollview-pull-down-text { diff --git a/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss index 28567ffc4f8a..08d5cbb4b37b 100644 --- a/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$material-scrollbar-size-thin: 6px; -$material-scrollbar-size: 13px; +$material-scrollbar-size-thin: 6px !default; +$material-scrollbar-size: 13px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/slider/_index.scss b/packages/devextreme-scss/scss/widgets/material/slider/_index.scss index 36bf203145da..9c819f8ec0b2 100644 --- a/packages/devextreme-scss/scss/widgets/material/slider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/slider/_index.scss @@ -6,11 +6,11 @@ @use "../../material/tooltip/colors" as *; @use "../../base/slider"; -$material-slider-radius: 50%; -$material-slider-handle-active-border-width: 2px; -$material-slider-handle-inner-size: 12px; -$material-slider-handle-inner-border-width: 1px; -$material-slider-handle-inner-disabled-size: 2px; +$material-slider-radius: 50% !default; +$material-slider-handle-active-border-width: 2px !default; +$material-slider-handle-inner-size: 12px !default; +$material-slider-handle-inner-border-width: 1px !default; +$material-slider-handle-inner-disabled-size: 2px !default; .dx-slider-wrapper { height: $material-slider-wrapper-height; diff --git a/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss index 6a7fa38a0fdd..9b1763a9f246 100644 --- a/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss @@ -3,7 +3,6 @@ $material-slider-handle-size: null !default; $material-slider-wrapper-height: null !default; $material-slider-tooltip-width: null !default; -$material-slider-top-tooltip-margin: null !default; $material-slider-tooltip-popup-content-padding: null !default; $material-slider-track-height: 2px !default; @@ -13,7 +12,6 @@ $material-slider-height: 28px !default; $material-slider-handle-size: 32px !default; $material-slider-wrapper-height: 32px !default; $material-slider-tooltip-width: 34px !default; - $material-slider-top-tooltip-margin: -5px !default; $material-slider-tooltip-popup-content-padding: 5px 7px !default; } @@ -21,6 +19,5 @@ $material-slider-height: 28px !default; $material-slider-handle-size: 26px !default; $material-slider-wrapper-height: 26px !default; $material-slider-tooltip-width: 28px !default; - $material-slider-top-tooltip-margin: -2px !default; $material-slider-tooltip-popup-content-padding: 3px 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss index ac08160d72be..615c8df0cf78 100644 --- a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss @@ -5,9 +5,9 @@ @use "../button/colors" as *; @use "../overlay"; -$material-fa-button-size: 40px; -$material-fa-button-label-offset: 16px; -$material-fa-button-label-padding: 4px 8px; +$material-fa-button-size: 40px !default; +$material-fa-button-label-offset: 16px !default; +$material-fa-button-label-padding: 4px 8px !default; @include dx-fa-button-mixin( $material-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss index bc68610c12b7..ffdd306ceace 100644 --- a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss @@ -3,16 +3,12 @@ $material-main-fa-button-label-padding: null !default; $material-main-fa-button-size: null !default; -$material-fa-button-label-font-size: null !default; - @if $size == "default" { $material-main-fa-button-size: 56px !default; - $material-main-fa-button-label-padding: 0 24px 0 8px; - $material-fa-button-label-font-size: 14px; + $material-main-fa-button-label-padding: 0 24px 0 8px !default; } @else if $size == "compact" { $material-main-fa-button-size: 42px !default; - $material-main-fa-button-label-padding: 0 20px 0 8px; - $material-fa-button-label-font-size: 12px; + $material-main-fa-button-label-padding: 0 20px 0 8px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss index 3af001d1096d..a97016b18725 100644 --- a/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 24px; +$splitter-resize-handle-icon-size: 24px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/switch/_index.scss b/packages/devextreme-scss/scss/widgets/material/switch/_index.scss index 9b7f631c40ef..59f2347d69ed 100644 --- a/packages/devextreme-scss/scss/widgets/material/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/switch/_index.scss @@ -6,10 +6,10 @@ @use "../../base/switch"; @use "../../base/switch/mixins" as *; -$material-switch-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); -$material-switch-handle-on-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1); -$material-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($base-text-color, $alpha: 0.1); -$material-invalid-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-invalid-handle-bg, $alpha: 0.1); +$material-switch-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; +$material-switch-handle-on-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1) !default; +$material-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($base-text-color, $alpha: 0.1) !default; +$material-invalid-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-invalid-handle-bg, $alpha: 0.1) !default; @include dx-switch( $material-switch-width, diff --git a/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss index edc230fb821e..5688f2501bf0 100644 --- a/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss @@ -3,28 +3,25 @@ $material-switch-width: null !default; $switch-padding: 10px !default; $switch-container-vertical-margin: null !default; -$switch-container-horizontal-padding: null !default; $material-switch-height: null !default; -$switch-handle-height: null !default; $switch-container-height: 14px !default; $switch-handle-margin: 0 !default; $material-switch-shadow-blur-radius: 10px !default; $material-switch-handle-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2) !default; $material-switch-container-adjustment: 6px !default; +$switch-container-horizontal-padding: $switch-handle-margin !default; @if $size == "default" { $material-switch-width: 36px !default; $switch-container-vertical-margin: 3px !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $material-switch-height: 20px !default; - $switch-handle-height: $material-switch-height !default; } @else if $size == "compact" { $material-switch-width: 32px !default; $switch-container-vertical-margin: 2px !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $material-switch-height: 18px !default; - $switch-handle-height: $material-switch-height !default; } + +$switch-handle-height: $material-switch-height !default; diff --git a/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss index 97c92422d3a9..c50cef3909cf 100644 --- a/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss @@ -1,21 +1,20 @@ @use "../../sizes" as *; $material-tabs-item-gap: 4px !default; -$material-tabs-item-padding: null !default; $material-tabs-item-padding-horizontal: null !default; $material-tabs-item-padding-vertical: null !default; -$material-tab-height: null !default; +$material-tab-height: auto !default; $material-tab-min-width: null !default; $material-tab-text-line-height: 1.714286 !default; $material-base-icon-margin: null !default; -$material-tab-nav-button-icon-width: null !default; -$material-tab-nav-button-icon-height: null !default; -$material-tab-nav-button-icon-font-size: null !default; -$material-tab-nav-button-content-padding: null !default; +$material-tab-nav-button-icon-width: 24px !default; +$material-tab-nav-button-icon-height: 24px !default; +$material-tab-nav-button-icon-font-size: 24px !default; +$material-tab-nav-button-content-padding: 0 4px !default; -$material-tab-vertical-nav-button-content-padding: null !default; +$material-tab-vertical-nav-button-content-padding: 4px 0 !default; $material-indicator-thickness-primary: 2px !default; $material-indicator-thickness-secondary: 3px !default; @@ -30,27 +29,15 @@ $material-tabs-icon-size: $material-base-icon-size !default; @if $size == "default" { $material-tabs-item-padding-horizontal: 12px !default; $material-tabs-item-padding-vertical: 16px !default; - $material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; - $material-tab-height: auto !default; $material-tab-min-width: 90px !default; $material-base-icon-margin: 8px !default; - $material-tab-nav-button-icon-width: 24px !default; - $material-tab-nav-button-icon-height: 24px !default; - $material-tab-nav-button-icon-font-size: 24px !default; - $material-tab-nav-button-content-padding: 0 4px !default; - $material-tab-vertical-nav-button-content-padding: 4px 0 !default; } @else if $size == "compact" { $material-tabs-item-padding-horizontal: 6px !default; $material-tabs-item-padding-vertical: 12px !default; - $material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; - $material-tab-height: auto !default; $material-tab-min-width: 82px !default; $material-base-icon-margin: 7px !default; - $material-tab-nav-button-icon-width: 24px !default; - $material-tab-nav-button-icon-height: 24px !default; - $material-tab-nav-button-icon-font-size: 24px !default; - $material-tab-nav-button-content-padding: 0 4px !default; - $material-tab-vertical-nav-button-content-padding: 4px 0 !default; } + +$material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; diff --git a/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss index af20f502f158..0a55098aa46e 100644 --- a/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss @@ -4,7 +4,6 @@ $material-tagbox-tag-content-padding-left: null !default; $material-tagbox-tag-content-padding-right: null !default; $material-tagbox-tag-content-vertical-padding: null !default; $material-tagbox-tag-content-font-size: null !default; -$material-tagbox-tag-container-padding: null !default; $material-tagbox-tag-container-padding-left: null !default; $material-tagbox-tag-container-vertical-padding: null !default; $material-tagbox-tag-height: null !default; @@ -15,21 +14,19 @@ $material-tagbox-select-all-padding-bottom: 12px !default; $material-tagbox-select-all-margin-bottom: 3px !default; $tagbox-tag-border-radius: 2em !default; $material-tagbox-filled-with-label-top-padding: null !default; -$material-tagbox-filled-with-label-bottom-padding: null !default; +$material-tagbox-filled-with-label-bottom-padding: 4px !default; @if $size == "default" { $material-tagbox-tag-content-padding-left: 8px !default; $material-tagbox-tag-content-padding-right: 30px !default; $material-tagbox-tag-content-vertical-padding: 4px !default; $material-tagbox-tag-content-font-size: 14px !default; - $material-tagbox-tag-container-padding: 0 12px 11px 16px !default; $material-tagbox-tag-container-padding-left: 16px !default; $material-tagbox-tag-container-vertical-padding: 11px !default; $material-tagbox-tag-height: 26px !default; $material-tagbox-remove-button-width: 26px !default; $material-tagbox-remove-button-icon-size: 16px !default; $material-tagbox-filled-with-label-top-padding: 18px !default; - $material-tagbox-filled-with-label-bottom-padding: 4px !default; } @else if $size == "compact" { @@ -37,12 +34,10 @@ $material-tagbox-filled-with-label-bottom-padding: null !default; $material-tagbox-tag-content-padding-right: 20px !default; $material-tagbox-tag-content-vertical-padding: 2px !default; $material-tagbox-tag-content-font-size: 12px !default; - $material-tagbox-tag-container-padding: 0 8px 3px 11px !default; $material-tagbox-tag-container-padding-left: 12px !default; $material-tagbox-tag-container-vertical-padding: 7px !default; $material-tagbox-tag-height: 18px !default; $material-tagbox-remove-button-width: 18px !default; $material-tagbox-remove-button-icon-size: 12px !default; $material-tagbox-filled-with-label-top-padding: 10px !default; - $material-tagbox-filled-with-label-bottom-padding: 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss index dacb0d90e04e..da0905c94c67 100644 --- a/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss @@ -8,15 +8,15 @@ @use "../../base/textBox"; @use "../textEditor" as *; -$textbox-search-icon-right-padding: math.div($material-filled-texteditor-input-horizontal-padding, 2); +$textbox-search-icon-right-padding: math.div($material-filled-texteditor-input-horizontal-padding, 2) !default; $textbox-standard-search-icon-size: $material-texteditor-icon-container-size + $material-base-standard-texteditor-input-horizontal-padding + - $textbox-search-icon-right-padding; + $textbox-search-icon-right-padding !default; $textbox-filled-search-icon-size: $material-texteditor-icon-container-size + $material-filled-texteditor-input-horizontal-padding + - $textbox-search-icon-right-padding; + $textbox-search-icon-right-padding !default; @mixin dx-texteditor-search-icon($texteditor-icon-size, $texteditor-padding) { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss index e05f1ab8f71e..e7495f66bedd 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss @@ -13,7 +13,7 @@ $texteditor-color: $base-text-color !default; * $type color */ $texteditor-placeholder-color: $base-label-color !default; -$texteditor-label-color: $texteditor-placeholder-color; +$texteditor-label-color: $texteditor-placeholder-color !default; /** * $name 30. Background color @@ -59,7 +59,7 @@ $texteditor-input-border-radius: $base-border-radius !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $mode == "light" { $texteditor-disabled-color: rgba(0, 0, 0, 0.6) !default; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss index ae152575c305..c9e40f7baa5b 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss @@ -13,10 +13,10 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$material-base-standard-texteditor-input-horizontal-padding: 0; -$material-texteditor-icon-container-size: $material-base-icon-size - 2px; -$material-texteditor-invalid-badge-size: $material-invalid-badge-size + 2 * $material-invalid-badge-horizontal-padding; -$material-editor-custom-button-margin: 5px; +$material-base-standard-texteditor-input-horizontal-padding: 0 !default; +$material-texteditor-icon-container-size: $material-base-icon-size - 2px !default; +$material-texteditor-invalid-badge-size: $material-invalid-badge-size + 2 * $material-invalid-badge-horizontal-padding !default; +$material-editor-custom-button-margin: 5px !default; @mixin dx-texteditor-icon() { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss index 8082b872260d..1e206df56fc9 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss @@ -28,9 +28,9 @@ $material-filled-texteditor-input-with-label-bottom-padding: null !default; $material-texteditor-label-outside-top-offset: 24px !default; $material-filled-texteditor-input-horizontal-padding: 16px !default; $material-filled-texteditor-input-top-padding: 15px !default; - $material-filled-texteditor-input-with-label-top-padding: 21px; + $material-filled-texteditor-input-with-label-top-padding: 21px !default; $material-filled-texteditor-input-bottom-padding: 14px !default; - $material-filled-texteditor-input-with-label-bottom-padding: 8px; + $material-filled-texteditor-input-with-label-bottom-padding: 8px !default; $material-standard-texteditor-input-top-padding: 15px !default; $material-standard-texteditor-input-bottom-padding: 14px !default; $material-standard-texteditor-with-label-input-top-padding: 21px !default; @@ -47,10 +47,10 @@ $material-filled-texteditor-input-with-label-bottom-padding: null !default; $material-texteditor-label-outside-height: 16px !default; $material-texteditor-label-outside-top-offset: 18px !default; $material-filled-texteditor-input-horizontal-padding: 12px !default; - $material-filled-texteditor-input-top-padding: 9px; - $material-filled-texteditor-input-with-label-top-padding: 15px; - $material-filled-texteditor-input-bottom-padding: 9px; - $material-filled-texteditor-input-with-label-bottom-padding: 3px; + $material-filled-texteditor-input-top-padding: 9px !default; + $material-filled-texteditor-input-with-label-top-padding: 15px !default; + $material-filled-texteditor-input-bottom-padding: 9px !default; + $material-filled-texteditor-input-with-label-bottom-padding: 3px !default; $material-standard-texteditor-input-top-padding: 9px !default; $material-standard-texteditor-input-bottom-padding: 9px !default; $material-standard-texteditor-with-label-input-top-padding: 14px !default; @@ -68,5 +68,5 @@ $material-standard-texteditor-input-padding: $material-base-standard-texteditor-input-horizontal-padding $material-standard-texteditor-input-bottom-padding !default; -$material-filled-texteditor-input-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 4px; -$material-filled-texteditor-spin-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 8px; +$material-filled-texteditor-input-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 4px !default; +$material-filled-texteditor-spin-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss index a9a01c5e3df1..b1bdbb525bb6 100644 --- a/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss @@ -4,11 +4,10 @@ $material-treeview-toggle-item-width: null !default; $material-treeview-checkbox-offset: null !default; $material-treeview-item-padding: null !default; $material-select-all-item-padding: null !default; -$material-treeview-item-height: null !default; $material-checkbox-container-size: null !default; $material-treeview-load-indicator-size: null !default; -$material-treeview-item-element-gap: null !default; +$material-treeview-item-element-gap: 12px !default; $material-treeview-searchbox-margin-bottom: null !default; $material-treeview-search-editor-height: null !default; @@ -20,30 +19,26 @@ $material-treeview-container-padding: $material-treeview-border-padding $material-treeview-border-padding $material-treeview-border-padding - $material-treeview-container-with-border-offset; + $material-treeview-container-with-border-offset !default; @if $size == "default" { - $material-treeview-item-element-gap: 12px !default; $material-treeview-toggle-item-width: 24px !default; $material-treeview-checkbox-offset: 55px !default; $material-select-all-item-padding: 15px $material-treeview-checkbox-offset !default; $material-checkbox-container-size: 24px !default; $material-treeview-item-padding: 12px $material-treeview-item-side-padding !default; - $material-treeview-item-height: 48px !default; $material-treeview-load-indicator-size: 24px !default; $material-treeview-searchbox-margin-bottom: 8px !default; - $material-treeview-search-editor-height: 48px; + $material-treeview-search-editor-height: 48px !default; } @else if $size == "compact" { - $material-treeview-item-element-gap: 12px !default; $material-treeview-toggle-item-width: 18px !default; $material-treeview-checkbox-offset: 47px !default; $material-select-all-item-padding: 10px $material-treeview-checkbox-offset !default; $material-treeview-item-padding: 9px $material-treeview-item-side-padding !default; - $material-treeview-item-height: 36px !default; $material-treeview-load-indicator-size: 18px !default; - $material-checkbox-container-size: 18px; + $material-checkbox-container-size: 18px !default; $material-treeview-searchbox-margin-bottom: 4px !default; - $material-treeview-search-editor-height: 32px; + $material-treeview-search-editor-height: 32px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss index c9ab6f8f1224..8cf4574b658d 100644 --- a/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss @@ -4,7 +4,7 @@ $material-xl-font-size: null !default; $material-l-font-size: null !default; $material-m-font-size: null !default; $material-s-font-size: null !default; -$material-xs-font-size: null !default; +$material-xs-font-size: 12px !default; $material-h1-font-size: null !default; $material-h2-font-size: null !default; $material-h3-font-size: null !default; @@ -17,7 +17,6 @@ $material-h6-font-size: null !default; $material-l-font-size: 28px !default; $material-m-font-size: 20px !default; $material-s-font-size: 18px !default; - $material-xs-font-size: 12px !default; $material-h1-font-size: 96px !default; $material-h2-font-size: 60px !default; @@ -32,7 +31,6 @@ $material-h6-font-size: null !default; $material-l-font-size: 20px !default; $material-m-font-size: 16px !default; $material-s-font-size: 14px !default; - $material-xs-font-size: 12px !default; $material-h1-font-size: 64px !default; $material-h2-font-size: 42px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss index ba97716f3337..5fec857c6d92 100644 --- a/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss @@ -1,6 +1,5 @@ @use "../sizes" as *; -$material-validation-message-padding: null !default; $material-validationsummary-item-margin: null !default; $material-validation-message-line-height: null !default; @@ -10,7 +9,6 @@ $material-validation-overlay-border-radius: 4px !default; $material-validation-message-font-size: 12px !default; @if $size == "default" { - $material-validation-message-padding: 6px !default; $material-validationsummary-item-margin: 10px !default; $material-validation-message-horizontal-padding: 8px !default; @@ -18,7 +16,6 @@ $material-validation-message-font-size: 12px !default; } @else if $size == "compact" { - $material-validation-message-padding: 4px !default; $material-validationsummary-item-margin: 6px !default; $material-validation-message-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/tests/module-variable-default.test.ts b/packages/devextreme-scss/tests/module-variable-default.test.ts new file mode 100644 index 000000000000..92edb88505f2 --- /dev/null +++ b/packages/devextreme-scss/tests/module-variable-default.test.ts @@ -0,0 +1,90 @@ +import { createRunner, scss } from './stylelint-rule'; + +const ruleName = 'dx/module-variable-default'; +const { lint, fix } = createRunner('module-variable-default.mjs', ruleName); + +test('a module-level variable without the flag is reported, at the top level and inside @if branches', () => { + const { warnings } = lint('missing.scss', scss( + '$top: 7px;', + '$flagged: 8px !default;', + '$global: 9px !global;', + '', + '@if $size == "default" {', + ' $branch: 1px;', + '}', + '', + '@else {', + ' $branch: 2px;', + '}', + )); + + expect(warnings).toEqual([ + { line: 1, rule: ruleName, text: `Expected the !default flag on "$top": without it a value passed through @use … with() is ignored (${ruleName})` }, + { line: 6, rule: ruleName, text: `Expected the !default flag on "$branch": without it a value passed through @use … with() is ignored (${ruleName})` }, + { line: 10, rule: ruleName, text: `Expected the !default flag on "$branch": without it a value passed through @use … with() is ignored (${ruleName})` }, + ]); +}); + +test('variables local to a mixin, function or rule are not module variables', () => { + const { warnings } = lint('local.scss', scss( + '@mixin size($base) {', + ' $double: $base * 2;', + ' width: $double;', + '}', + '', + '@function half($value) {', + ' $result: $value / 2;', + ' @return $result;', + '}', + '', + '.dx-widget {', + ' $gap: 4px;', + ' margin: $gap;', + '', + ' @if $size == "compact" {', + ' $gap: 2px;', + ' }', + '}', + )); + + expect(warnings).toEqual([]); +}); + +test('reassigning a variable that already holds a value is not a default', () => { + const { warnings } = lint('reassign.scss', scss( + '$offset: 4px !default;', + '$offset: $offset + 1px;', + '', + '$late: null !default;', + '$late: 2px;', + )); + + expect(warnings.map((warning) => warning.line)).toEqual([5]); +}); + +test('--fix appends the flag, also to a multi-line value', () => { + const { warnings, output } = fix('fix.scss', scss( + '$top: 7px;', + '$map: (', + ' "a": 1,', + ' "b": 2,', + ');', + '', + '@if $size == "default" {', + ' $branch: 1px;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$top: 7px !default;', + '$map: (', + ' "a": 1,', + ' "b": 2,', + ') !default;', + '', + '@if $size == "default" {', + ' $branch: 1px !default;', + '}', + )); +}); diff --git a/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts b/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts new file mode 100644 index 000000000000..1c70a11fe2f9 --- /dev/null +++ b/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts @@ -0,0 +1,392 @@ +import { createRunner, scss } from './stylelint-rule'; + +const ruleName = 'dx/no-identical-branch-declarations'; +const { lint, fix } = createRunner('no-identical-branch-declarations.mjs', ruleName); + +test('a variable set to one value in every @else branch is reported once, on the first branch', () => { + const { warnings } = lint('else-chain.scss', scss( + '$same: null !default;', + '$differs: null !default;', + '', + '@if $size == "default" {', + ' $same: 8px !default;', + ' $differs: 8px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $same: 8px !default;', + ' $differs: 4px !default;', + '}', + )); + + expect(warnings).toEqual([{ + line: 5, + rule: ruleName, + text: `"$same" has the same value in all 2 branches of the @if chain; declare it once outside the chain (${ruleName})`, + }]); +}); + +test('consecutive @if blocks testing one variable against different constants form a chain', () => { + const { warnings } = lint('if-if.scss', scss( + '@if $size == "default" {', + ' $same: 8px !default;', + '}', + '', + '@if $size == "compact" {', + ' $same: 8px !default;', + '}', + '', + '@if $mode == "dark" {', + ' $same: 8px !default;', + '}', + )); + + expect(warnings.map((warning) => warning.line)).toEqual([2]); +}); + +test('nothing is reported for a value that differs, a variable missing from a branch, or a repeated constant', () => { + const { warnings } = lint('clean.scss', scss( + '@if $size == "default" {', + ' $a: 8px !default;', + ' $only-here: 1px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $a: 4px !default;', + '}', + '', + '@if $size == "default" {', + ' $b: 8px !default;', + '}', + '', + '@if $size == "default" {', + ' $b: 8px !default;', + '}', + )); + + expect(warnings).toEqual([]); +}); + +test('--fix replaces the null pre-declaration with a literal and removes the emptied chain', () => { + const { warnings, output } = fix('fix-literal.scss', scss( + '$x: null !default;', + '$keep: 1px !default;', + '', + '@if $size == "default" {', + ' $x: ds.$spacing-480 !default;', + '}', + '', + '@else if $size == "compact" {', + ' $x: ds.$spacing-480 !default;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$x: ds.$spacing-480 !default;', + '$keep: 1px !default;', + )); +}); + +test('--fix puts a value that reads a branch-bound variable after the chain', () => { + const { output } = fix('fix-after.scss', scss( + '$height: null !default;', + '$handle: null !default;', + '', + '@if $size == "default" {', + ' $height: 20px !default;', + ' $handle: $height !default;', + '}', + '', + '@else if $size == "compact" {', + ' $height: 18px !default;', + ' $handle: $height !default;', + '}', + )); + + expect(output).toBe(scss( + '$height: null !default;', + '', + '@if $size == "default" {', + ' $height: 20px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $height: 18px !default;', + '}', + '', + '$handle: $height !default;', + )); +}); + +test('--fix leaves no blank first line when the removed declaration opened its group', () => { + const { output } = fix('fix-first-line.scss', scss( + '$same: null !default;', + '', + '@if $size == "default" {', + ' $same: 1px !default;', + '', + ' $width: 36px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $same: 1px !default;', + '', + ' $width: 32px !default;', + '}', + )); + + expect(output).toBe(scss( + '$same: 1px !default;', + '', + '@if $size == "default" {', + ' $width: 36px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $width: 32px !default;', + '}', + )); +}); + +test('--fix moves a value that reads a variable declared below its pre-declaration to just before the chain', () => { + const { output } = fix('fix-before.scss', scss( + '$padding: null !default;', + '$margin: 0 !default;', + '', + '@if $size == "default" {', + ' $padding: $margin !default;', + ' $width: 36px !default;', + '}', + '', + '@if $size == "compact" {', + ' $padding: $margin !default;', + ' $width: 32px !default;', + '}', + )); + + expect(output).toBe(scss( + '$margin: 0 !default;', + '$padding: $margin !default;', + '', + '@if $size == "default" {', + ' $width: 36px !default;', + '}', + '', + '@if $size == "compact" {', + ' $width: 32px !default;', + '}', + )); +}); + +test('--fix only removes branch declarations that a non-null default above the chain already overrides', () => { + const { output } = fix('fix-dead.scss', scss( + '$x: 10px !default;', + '', + '@if $size == "default" {', + ' $x: 12px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $x: 12px !default;', + '}', + )); + + expect(output).toBe(scss('$x: 10px !default;')); +}); + +test('--fix settles a duplicate that only appears in the outer chain after the inner one is hoisted', () => { + const { warnings, output } = fix('fix-nested.scss', scss( + '$focused: null !default;', + '', + '@if $mode == "light" {', + ' @if $color == "blue" {', + ' $hover: #00f !default;', + ' $focused: $hover !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #0ff !default;', + ' $focused: $hover !default;', + ' }', + '}', + '', + '@if $mode == "dark" {', + ' @if $color == "blue" {', + ' $hover: #008 !default;', + ' $focused: $hover !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #088 !default;', + ' $focused: $hover !default;', + ' }', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '@if $mode == "light" {', + ' @if $color == "blue" {', + ' $hover: #00f !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #0ff !default;', + ' }', + '}', + '', + '@if $mode == "dark" {', + ' @if $color == "blue" {', + ' $hover: #008 !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #088 !default;', + ' }', + '}', + '', + '$focused: $hover !default;', + )); +}); + +test('--fix keeps a declaration behind the chain ahead of the declarations that read it', () => { + const { warnings, output } = fix('fix-order.scss', scss( + '$item-bg: null !default;', + '$disabled-bg: null !default;', + '$disabled-border: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + ' $disabled-bg: $item-bg !default;', + ' $disabled-border: $disabled-bg !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + ' $disabled-bg: $item-bg !default;', + ' $disabled-border: $disabled-bg !default;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$item-bg: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + '}', + '', + '$disabled-bg: $item-bg !default;', + '$disabled-border: $disabled-bg !default;', + )); +}); + +test('a duplicate that a branch-bound declaration still reads is reported but not moved', () => { + const source = scss( + '$item-bg: null !default;', + '$disabled-bg: null !default;', + '$hovered-bg: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + ' $disabled-bg: $item-bg !default;', + ' $hovered-bg: color.adjust($disabled-bg, $lightness: -4%) !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + ' $disabled-bg: $item-bg !default;', + ' $hovered-bg: color.adjust($disabled-bg, $lightness: 4%) !default;', + '}', + ); + + const { warnings, output } = fix('fix-blocked.scss', source); + + expect(warnings.map((warning) => warning.line)).toEqual([7]); + expect(output).toBe(source); +}); + +test('--fix keeps a declaration under its doc comment when what it reads is not reassigned in between', () => { + const { output } = fix('fix-doc-in-place.scss', scss( + '@use "../colors" as *;', + '', + '/**', + '* $name 100. Background color', + '* $type color', + '*/', + '$common-bg: null !default;', + '', + '@if $mode == "light" {', + ' $common-bg: $base-bg !default;', + ' $accent: #00f !default;', + '}', + '', + '@if $mode == "dark" {', + ' $common-bg: $base-bg !default;', + ' $accent: #0ff !default;', + '}', + )); + + expect(output).toBe(scss( + '@use "../colors" as *;', + '', + '/**', + '* $name 100. Background color', + '* $type color', + '*/', + '$common-bg: $base-bg !default;', + '', + '@if $mode == "light" {', + ' $accent: #00f !default;', + '}', + '', + '@if $mode == "dark" {', + ' $accent: #0ff !default;', + '}', + )); +}); + +test('--fix takes the doc comment along when the declaration has to move', () => { + const { output } = fix('fix-doc-moves.scss', scss( + '/**', + '* $name 302. Divider color', + '* $type color', + '*/', + '$divider: null !default;', + '', + '$border: null !default;', + '', + '@if $mode == "light" {', + ' $border: #ccc !default;', + ' $divider: $border !default;', + '}', + '', + '@if $mode == "dark" {', + ' $border: #444 !default;', + ' $divider: $border !default;', + '}', + )); + + expect(output).toBe(scss( + '$border: null !default;', + '', + '@if $mode == "light" {', + ' $border: #ccc !default;', + '}', + '', + '@if $mode == "dark" {', + ' $border: #444 !default;', + '}', + '', + '/**', + '* $name 302. Divider color', + '* $type color', + '*/', + '$divider: $border !default;', + )); +}); diff --git a/packages/devextreme-scss/tests/stylelint-rule.ts b/packages/devextreme-scss/tests/stylelint-rule.ts new file mode 100644 index 000000000000..c8e8d5bafbf7 --- /dev/null +++ b/packages/devextreme-scss/tests/stylelint-rule.ts @@ -0,0 +1,49 @@ +import { execFileSync } from 'child_process'; +import { mkdtempSync, readFileSync, writeFileSync } from 'fs'; +import { tmpdir } from 'os'; +import { basename, join } from 'path'; + +export type Warning = { line: number; rule: string; text: string }; +export type Result = { warnings: Warning[]; output: string }; +export type Runner = { + lint: (name: string, source: string) => Result; + fix: (name: string, source: string) => Result; +}; + +const packageRoot = process.cwd(); +const stylelintBin = join(packageRoot, 'node_modules', '.bin', 'stylelint'); + +export const scss = (...rows: string[]): string => `${rows.join('\n')}\n`; + +export const createRunner = (pluginFile: string, ruleName: string): Runner => { + const fixture = mkdtempSync(join(tmpdir(), `${basename(pluginFile, '.mjs')}-`)); + const configPath = join(fixture, 'config.json'); + writeFileSync(configPath, JSON.stringify({ + customSyntax: require.resolve('postcss-scss', { paths: [require.resolve('stylelint-scss')] }), + plugins: [join(packageRoot, 'tools', 'stylelint', pluginFile)], + rules: { [ruleName]: true }, + })); + + const run = (name: string, source: string, fix: boolean): Result => { + const file = join(fixture, name); + const reportFile = join(fixture, `${name}.report.json`); + writeFileSync(file, source); + writeFileSync(reportFile, ''); + const args = [file, '--config', configPath, '--formatter', 'json', '--output-file', reportFile, ...(fix ? ['--fix'] : [])]; + try { + execFileSync(stylelintBin, args, { stdio: 'ignore' }); + } catch (error) { + if ((error as { status?: number }).status !== 2) throw error; + } + const [{ warnings }] = JSON.parse(readFileSync(reportFile, 'utf8') || '[{"warnings":[]}]'); + return { + warnings: warnings.map(({ line, rule, text }: Warning) => ({ line, rule, text })), + output: readFileSync(file, 'utf8'), + }; + }; + + return { + lint: (name, source) => run(name, source, false), + fix: (name, source) => run(name, source, true), + }; +}; diff --git a/packages/devextreme-scss/tests/unused-elements.test.ts b/packages/devextreme-scss/tests/unused-elements.test.ts index 559096b709b3..52180125fabf 100644 --- a/packages/devextreme-scss/tests/unused-elements.test.ts +++ b/packages/devextreme-scss/tests/unused-elements.test.ts @@ -47,24 +47,41 @@ const removeAllCommentsFromContent = (content: string): string => content .filter((_, index) => index % 2 === 0) .join(''); -const extractVariables = (filePath: string): string[] => { +const variableRegex = new RegExp(`\\$[${VAR_NAME_CHARS}]+`, 'y'); + +type VariableUsage = { declared: Set; read: Set }; + +const collectVariableUsage = (filePath: string, usage: VariableUsage): void => { const content = removeAllCommentsFromContent(readFileSync(filePath, 'utf8')); - const regex = new RegExp(`\\$[${VAR_NAME_CHARS}]+`, 'g'); - return content.match(regex) ?? []; + let depth = 0; + let index = 0; + while (index < content.length) { + const char = content[index]; + if (char === '(') depth += 1; + if (char === ')') depth -= 1; + if (char === '$') { + variableRegex.lastIndex = index; + const [variable] = variableRegex.exec(content) ?? []; + if (!variable) { + index += 1; + continue; + } + const assigned = /^\s*:/.test(content.slice(index + variable.length)); + if (assigned && depth === 0) usage.declared.add(variable); + if (!assigned) usage.read.add(variable); + index += variable.length; + continue; + } + index += 1; + } }; -const findUniqueVariables = (variables: string[]): string[] => { - const variableCounts = new Map(); - variables.forEach((variable) => { - variableCounts.set(variable, (variableCounts.get(variable) ?? 0) + 1); - }); - return Array.from(variableCounts.entries()) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // @ts-ignore - .filter(([variable, count]) => count === 1) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // @ts-ignore - .map(([variable, count]) => variable); +const variableUsageOf = (directories: string[]): VariableUsage => { + const usage: VariableUsage = { declared: new Set(), read: new Set() }; + directories + .flatMap((directory) => getFilesFromDirectory(join('scss', 'widgets', directory), ['.scss'])) + .forEach((filePath) => collectVariableUsage(filePath, usage)); + return usage; }; test('There are no unused images in repository', () => { @@ -85,29 +102,14 @@ test('There are no unused images in repository', () => { expect(fullImagesFileList).toEqual(usedImagesFileList); }); -['generic', 'material', 'fluent'].forEach((themeName) => { - test(`There are no unused variables in ${themeName} SCSS files`, () => { - const baseScssFiles = getFilesFromDirectory(join('scss', 'widgets', 'base'), ['.scss']) - .map((fileName) => resolve(fileName)); - - const genericScssFiles = getFilesFromDirectory(join('scss', 'widgets', themeName), ['.scss']) - .map((fileName) => resolve(fileName)); - - const scssFiles = [...baseScssFiles, ...genericScssFiles]; - - let variables: string[] = []; - scssFiles.forEach((filePath) => { - variables = variables.concat(extractVariables(getFilePath(filePath.substring(filePath.indexOf('/scss'))))); - }); - - const uniqueVariables = findUniqueVariables(variables); +const themes = ['generic', 'material', 'fluent']; +const readAnywhere = variableUsageOf(['base', ...themes]).read; - const exclusions: { generic: string[]; material: string[]; fluent: string[] } = { - generic: [], - material: [], - fluent: [], - }; +themes.forEach((themeName) => { + test(`There are no unused variables in ${themeName} SCSS files`, () => { + const { declared } = variableUsageOf(['base', themeName]); + const unused = [...declared].filter((variable) => !readAnywhere.has(variable)).sort(); - expect(uniqueVariables).toEqual(exclusions[themeName]); + expect(unused).toEqual([]); }); }); diff --git a/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs b/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs new file mode 100644 index 000000000000..1bf97bfbf8ce --- /dev/null +++ b/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs @@ -0,0 +1,55 @@ +import stylelint from 'stylelint'; + +const { + createPlugin, + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = 'dx/module-variable-default'; + +const messages = ruleMessages(ruleName, { + expected: (name) => `Expected the !default flag on "${name}": without it a value passed through @use … with() is ignored`, +}); + +const meta = { fixable: true }; + +const isConditional = (node) => node.type === 'atrule' && ['if', 'else'].includes(node.name); +const isModuleLevel = (decl) => { + let node = decl.parent; + while (isConditional(node)) node = node.parent; + return node.type === 'root'; +}; +const hasFlag = (decl, flag) => new RegExp(`!${flag}\\b`, 'i').test(decl.value); +const isNull = (decl) => /^null(\s+!default)?$/.test(decl.value.trim()); + +const ruleFunction = (primary) => (root, result) => { + if (!validateOptions(result, ruleName, { actual: primary, possible: [true] })) return; + + const assigned = new Set(); + + root.walkDecls((decl) => { + if (!decl.prop.startsWith('$') || !isModuleLevel(decl)) return; + + const reassignment = assigned.has(decl.prop); + if (decl.parent.type === 'root' && !isNull(decl)) assigned.add(decl.prop); + if (reassignment || hasFlag(decl, 'default') || hasFlag(decl, 'global')) return; + + report({ + message: messages.expected, + messageArgs: [decl.prop], + node: decl, + result, + ruleName, + fix: () => { + decl.value = `${decl.value} !default`; + if (decl.raws.value) decl.raws.value = { value: decl.value, raw: `${decl.raws.value.raw} !default` }; + }, + }); + }); +}; + +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; +ruleFunction.meta = meta; + +export default createPlugin(ruleName, ruleFunction); diff --git a/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs b/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs new file mode 100644 index 000000000000..088b41e742bd --- /dev/null +++ b/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs @@ -0,0 +1,238 @@ +import stylelint from 'stylelint'; + +const { + createPlugin, + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = 'dx/no-identical-branch-declarations'; + +const messages = ruleMessages(ruleName, { + rejected: (name, count) => `"${name}" has the same value in all ${count} branches of the @if chain; declare it once outside the chain`, +}); + +const meta = { fixable: true }; + +const isElse = (node) => node.type === 'atrule' && node.name === 'else'; +const isIf = (node) => node.type === 'atrule' && node.name === 'if'; +const isVariable = (node) => node.type === 'decl' && node.prop.startsWith('$'); +const isEmpty = (block) => (block.nodes ?? []).length === 0; +const normalise = (value) => value.replace(/\s+/g, ' ').trim(); +const isNull = (value) => /^null(\s+!default)?$/.test(normalise(value)); +const hasDefaultFlag = (value) => /!default\b/.test(value); +const indentOf = (node) => (node.raws.before ?? '').match(/[ \t]*$/)[0]; + +const localReferences = (value) => [...value.matchAll(/(^|[^\w.-])(\$[\w-]+)/g)].map((match) => match[2]); + +const equalityTest = (atRule) => { + const match = atRule.params.trim().match(/^(\$[\w-]+)\s*==\s*("[^"]*"|'[^']*'|[\w.-]+)$/); + return match && { variable: match[1], literal: match[2] }; +}; + +const chainOf = (ifRule) => { + const branches = [ifRule]; + let next = ifRule.next(); + + while (next && (next.type === 'comment' || isElse(next))) { + if (isElse(next)) branches.push(next); + next = next.next(); + } + if (branches.length > 1) return { branches, independent: false }; + + const test = equalityTest(ifRule); + const literals = new Set([test?.literal]); + + while (test && next && (next.type === 'comment' || isIf(next))) { + if (isIf(next)) { + const other = equalityTest(next); + if (other?.variable !== test.variable || literals.has(other.literal)) break; + literals.add(other.literal); + branches.push(next); + } + next = next.next(); + } + return { branches, independent: true }; +}; + +const collectChains = (root) => { + const chains = []; + const seen = new WeakSet(); + + root.walkAtRules('if', (ifRule) => { + if (seen.has(ifRule)) return; + const chain = chainOf(ifRule); + chain.branches.forEach((branch) => seen.add(branch)); + if (chain.branches.length > 1) chains.push(chain); + }); + return chains.reverse(); +}; + +const directDeclarations = (block) => { + const byName = new Map(); + (block.nodes ?? []).filter(isVariable).forEach((decl) => byName.set(decl.prop, decl)); + return byName; +}; + +const identicalDeclarations = (branches) => { + const [first, ...rest] = branches.map(directDeclarations); + const identical = new Map(); + + for (const [name, decl] of first) { + const declarations = [decl, ...rest.map((byName) => byName.get(name))]; + const value = normalise(decl.value); + if (declarations.every((other) => other && normalise(other.value) === value)) identical.set(name, declarations); + } + return identical; +}; + +const walkFinds = (branches, predicate) => branches.some((branch) => { + let found = false; + branch.walkDecls((decl) => { + if (!predicate(decl, branch)) return undefined; + found = true; + return false; + }); + return found; +}); + +const assignedInside = (branches, name) => walkFinds(branches, (decl) => decl.prop === name); + +const planMoves = (branches, identical) => { + const movable = new Set(identical.keys()); + const placement = new Map(); + + const settle = () => { + placement.clear(); + for (const name of movable) { + const behind = localReferences(identical.get(name)[0].value).some((ref) => ( + movable.has(ref) ? placement.get(ref) === 'after' : assignedInside(branches, ref) + )); + placement.set(name, behind ? 'after' : 'before'); + } + }; + const readByStaying = (name) => walkFinds(branches, (decl, branch) => ( + !(movable.has(decl.prop) && decl.parent === branch) && localReferences(decl.value).includes(name) + )); + + for (let changed = true; changed;) { + settle(); + changed = false; + for (const name of movable) { + if (placement.get(name) === 'after' && readByStaying(name)) { + movable.delete(name); + changed = true; + } + } + } + return { movable, placement }; +}; + +const earlierDeclaration = (parent, ifRule, name) => { + let found; + for (const sibling of parent.nodes) { + if (sibling === ifRule) break; + if (isVariable(sibling) && sibling.prop === name) found = sibling; + } + return found; +}; + +const declaredBetween = (parent, after, before, name) => parent.nodes + .slice(parent.index(after) + 1, parent.index(before)) + .some((node) => isVariable(node) && node.prop === name); + +const attachedComment = (decl) => { + const previous = decl.prev(); + const attached = previous?.type === 'comment' && !/\n[ \t]*\n/.test(decl.raws.before ?? ''); + return attached ? previous : undefined; +}; + +const dropEmptyBranches = (branches, independent) => { + if (independent) { + branches.filter(isEmpty).forEach((branch) => branch.remove()); + return; + } + while (branches.length && isEmpty(branches[branches.length - 1])) { + branches.pop().remove(); + } +}; + +const ruleFunction = (primary) => (root, result) => { + if (!validateOptions(result, ruleName, { actual: primary, possible: [true] })) return; + + const lastHoisted = new WeakMap(); + + for (const { branches, independent } of collectChains(root)) { + const ifRule = branches[0]; + const { parent } = ifRule; + const identical = identicalDeclarations(branches); + const { movable, placement } = planMoves(branches, identical); + + for (const [name, declarations] of identical) { + const [first] = declarations; + const earlier = earlierDeclaration(parent, ifRule, name); + const overridden = earlier !== undefined && !isNull(earlier.value); + const fixable = overridden ? declarations.every((decl) => hasDefaultFlag(decl.value)) : movable.has(name); + + const fix = () => { + const live = branches.filter((branch) => branch.parent); + const tidy = () => { + dropEmptyBranches(live, independent); + live.forEach((branch) => { + if (branch.parent && branch.first?.type === 'decl') branch.first.raws.before = branch.first.raws.before.replace(/^\s*\n/, '\n'); + }); + parent.raws.semicolon = true; + if (parent.type === 'root' && parent.first) parent.first.raws.before = ''; + }; + + if (overridden) { + declarations.forEach((decl) => decl.remove()); + tidy(); + return; + } + + const hoisted = first.clone(); + const references = localReferences(first.value); + const comment = earlier && attachedComment(earlier); + declarations.forEach((decl) => decl.remove()); + + const relocate = (insert, gap) => { + const indent = indentOf(ifRule); + const moved = comment?.clone({ raws: { ...comment.raws, before: `${gap}${indent}` } }); + hoisted.raws.before = moved ? `\n${indent}` : `${gap}${indent}`; + if (moved) insert(moved); + insert(hoisted, moved); + comment?.remove(); + earlier?.remove(); + }; + + if (placement.get(name) === 'after') { + const anchor = lastHoisted.get(ifRule) ?? live[live.length - 1]; + relocate((node, previous) => parent.insertAfter(previous ?? anchor, node), lastHoisted.has(ifRule) ? '\n' : '\n\n'); + lastHoisted.set(ifRule, hoisted); + } else if (earlier && !references.some((ref) => declaredBetween(parent, earlier, ifRule, ref))) { + hoisted.raws.before = earlier.raws.before; + earlier.replaceWith(hoisted); + } else { + relocate((node, previous) => (previous ? parent.insertAfter(previous, node) : parent.insertBefore(ifRule, node)), '\n'); + } + + tidy(); + }; + + report({ + message: messages.rejected, + messageArgs: [name, branches.length], + node: first, + result, + ruleName, + ...(fixable && { fix: { apply: fix, node: parent } }), + }); + } + } +}; + +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; +ruleFunction.meta = meta; + +export default createPlugin(ruleName, ruleFunction); From 5a13cc52dd46092398708af09a0c13cf29bc7816 Mon Sep 17 00:00:00 2001 From: Arman Jivanyan Date: Mon, 7 Sep 2026 11:53:45 +0000 Subject: [PATCH 14/45] fix (demos): Workaround the broken pivotgrid demo (#35044) --- .../Angular/app/app.component.ts | 121 +++++++++++++++--- apps/demos/testing/skipped-tests.js | 2 - ...onflict-informer (material.blue.light).png | Bin 58678 -> 58592 bytes ...ct-informer (material.blue.light)_mask.png | Bin 0 -> 3484 bytes apps/demos/utils/server/csp-bundle-angular.js | 6 - apps/demos/utils/server/csp-check.js | 6 - 6 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png diff --git a/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts b/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts index 9b0bcfb6a7c5..1079a7780bf7 100644 --- a/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts +++ b/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts @@ -1,20 +1,111 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import { Component } from '@angular/core'; - -// TODO: restore the original demo (see git history for this file, app.component.html, -// app.component.css, app.service.ts). Disabled because combining DxPivotGridModule and -// DxPivotGridFieldChooserModule here reproducibly triggers, in CI only (not locally, likely a -// devextreme-angular artifacts staleness difference between environments): -// NG8023: Multiple components match node with tagname dxo-pivot-grid-field-chooser-texts: -// 'DxoPivotGridFieldChooserTextsComponent', 'DxoPivotGridFieldChooserTextsComponent'. -// Both modules generate their own nested-options component for that tag name, and combining -// them in one standalone component's imports puts both in scope at once. Needs a real fix in -// devextreme-angular's generated wrappers (or dropping one of the two imports if one turns out -// to be redundant here) rather than a demo-side workaround. +import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; +import { + DxRadioGroupComponent, + DxButtonComponent, + DxSelectBoxComponent, +} from 'devextreme-angular'; +import PivotGridDataSource from 'devextreme/ui/pivot_grid/data_source'; +import { + DxPivotGridComponent, + DxoPivotGridFieldChooserComponent, + DxPivotGridTypes, +} from 'devextreme-angular/ui/pivot-grid'; +import { + DxPivotGridFieldChooserComponent, + DxoPivotGridFieldChooserTextsComponent, +} from 'devextreme-angular/ui/pivot-grid-field-chooser'; +import { Service, Layout, Sale } from './app.service'; + +if (!/localhost/.test(document.location.host)) { + enableProdMode(); +} + @Component({ selector: 'demo-app', - template: '

This demo is temporarily disabled. See the TODO in app.component.ts.

', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], + providers: [Service], + preserveWhitespaces: true, + imports: [ + DxPivotGridComponent, + DxoPivotGridFieldChooserComponent, + DxRadioGroupComponent, + DxPivotGridFieldChooserComponent, + DxoPivotGridFieldChooserTextsComponent, + DxSelectBoxComponent, + DxButtonComponent, + ], }) -export class AppComponent {} +export class AppComponent { + pivotGridDataSource: PivotGridDataSource; + + layouts: Layout[]; + + layout = 0; + + applyChangesModes: DxPivotGridTypes.ApplyChangesMode[] = ['instantly', 'onDemand']; + + applyChangesMode = this.applyChangesModes[0]; + + state: Record; + + constructor(service: Service) { + this.pivotGridDataSource = new PivotGridDataSource({ + fields: [{ + caption: 'Region', + width: 120, + dataField: 'region', + area: 'row', + headerFilter: { + search: { + enabled: true, + }, + }, + }, { + caption: 'City', + dataField: 'city', + width: 150, + area: 'row', + headerFilter: { + search: { + enabled: true, + }, + }, + selector(data: Sale) { + return `${data.city} (${data.country})`; + }, + }, { + dataField: 'date', + dataType: 'date', + area: 'column', + }, { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + format: 'currency', + area: 'data', + }], + store: service.getSales(), + }); + + this.state = this.pivotGridDataSource.state(); + + this.layouts = service.getLayouts(); + } + + applyClick() { + this.pivotGridDataSource.state(this.state); + } + + cancelClick() { + this.state = this.pivotGridDataSource.state(); + } +} -bootstrapApplication(AppComponent); +bootstrapApplication(AppComponent, { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), + ], +}); diff --git a/apps/demos/testing/skipped-tests.js b/apps/demos/testing/skipped-tests.js index 52bd9f86578d..cdd9e5ce7dcf 100644 --- a/apps/demos/testing/skipped-tests.js +++ b/apps/demos/testing/skipped-tests.js @@ -6,8 +6,6 @@ export const skippedTests = { Angular: { // WebGL initialization error at the provider. There is no point in adding a mask Map: ['ProvidersAndTypes', 'Markers', 'Routes'], - // Demo is stubbed out (see the TODO in its app.component.ts) pending a devextreme-angular fix - PivotGrid: ['StandaloneFieldChooser'], }, React: { // WebGL initialization error at the provider. There is no point in adding a mask diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png index ef926159c9c3e865688d5f7c1216cbd6a5925342..410371c30851f7ca2b946e6b350d948012317381 100644 GIT binary patch delta 20523 zcmch;2|Sf++cv&;xW!^$WA#=vnKq8qcl|o6$-5^7z7M3AXGNnS4jHQwx zW2s1{WNJc4X8y;u^gPdg-tT$e@A=;M{r&ANYq-~SU)MPt=W!0}VfpM2FJ|A9xbXYE z`;r$Puu-ceQWlL*4fpjq*t%Erp5Qng(I$0aQ`xmY%)QSnFcLq^P?y|!FVx)cnApy< z*Gsxz_-MT!tl8;**zL{r!R+v^R;8yU;nTr)Qr}12nD#hi@M>B8q8oRg@b=o;%s;*Q zok?g!^ypeDBls>?=)OxItGkEZFUve&8TwFQ%b8vD0Dn>H!Jm94r}Z@BXuP_A8ZeGt zP|>F!Oxl(a!WYr8%TiOyqh*%YN>8nz3DYBI1FZE$jy;lJm2_su#eklze36k_rx|i= zqi!+ITm1dj;;-hG=&l1z(QQr9aZS;}E~U*WjxM~t0r|^Mx-><5=ePH&HLby`J~;|A zFA6i3FYMJzFcULQp`CD1Q&ZEMC>Gnx@ec1X( zS@$5f-jkDtH&Sh87tdW3AtTvQuX0`7w0giv%A=UK_~0xR^E=Pe`9`wl@GnVIbJ#O~ z|Ni~XrydN9ykB+JxXRk7K$yNhOtki4t}k!;r=A;GcD%Vq*;vUB-E>vAoZy44+jS&M z6(~ocXDX}buk`fTrd-4~!eA@heQ-|ktRB(8*TEMgA}s4Be9!1Rc`o}jOPjx}WcryDJJX07?|+B)5B8PD%r-Ep;;n&ZP)D5RTl;IreZBz8mI*Scb(R+}Fj z7igH*z<-#{c|CJ^ql%9cX;(O^O{Uzd+X`2jnw+P&s&vG8!{%TOOHIwIw0Wkc3ZITu z(}kU1zBlA`Jw4mw+RmS~@9TCPx12X{%sC`z(;un}HN;XM}Eq^k3gH&pb+wHKn+yX%+Vo!Y&qSmolqQQlA)uWe;_ugJfzOKXd% z#F?>n>!9met3pfZu7;etP?^^;N4=Z$wOo9_@-stlG5c zNA12N>w*`iERgTXR&scOq(?9HyT4eHg z){L^Quk4<)Y@@NYT=+F>;mc&V-rf_vb7vlC?p(O+*JUf#{nCWcHrN+_ju>5-x#&&8 zZ!?=q(fWPn8l&J@GuI?+r2f}d!a@ZBYu&zY-<@vmPwrgM|Iyo<`&nX`dHG}m0pzd<@TMNAGJCh zw{vqNUVTf;@BP>rW?|b|&LB(s?LMidwq!$Om1A#iw^Y-I59=&O%lC`~D~~SUe7vd5 zSNw5V5Wh~if{WO$*|TRWPJAyaz*+@8#~iHqE?>Uv)+u7qR;wd!GdV^LRfbDmF7{o$ z+;yVA)b(eBU4*37Jm1ygwoN%FSNQIDJoYx-v6Hc9;KytI*DWp89ClGpBkW>c80>p^ zwC$A!PvYapG5BQXU zVf#z;-3OK*e>AikLR!m9?lIlZGyU}D4qtY5_76OGGRi|uw^QFA4?hYldoxRCeMsZ)3NWe1QGDgJn=syO-M z^+bn&avYVC;`8+B_O!=iN0xMM;2Hg6&;C;@=|f-ZoocoY-#a^WBuPJ3uVbJ>yt{Df zYhli}%nEO}jtxAPqXV64T{W>aYh0S1zTIQ7Gh=T@-q*DDu1E*lhD;l^<2h~fcBh&a zi)J@H3)WY9L5xs9c~pycwfUL#)Myy>Vrp`H^t+cbrMqX(YVpDeDW1ts8pKUFQi;`; z_;E;|X>4p!e;XoX%>E@ISnuW$ymqwlp}(e%&LZDcqBpT|O~ti#U6m0b6*lSvq1gpj zZWHNO#wy4D{CWC)W2295jADd8m#kxhO&Dv0ywk>AVP=jCEXHcXh=~-A6pOzrq$uU! zjXMuE=bci+`bAcOTh*;`HdtQWU6Z1&s>p_1Cjt87$4XzmekI#WnM*9cy~(MjvxsL( zLZiIdXT`#~C4oFO8DDyOY)S*x+%?M$ij#_9nJ<0vqZ;q|^XGs3_+gMj+nejiuUZu$ z?N#<#U%5)A-mJ*AJG%4@m14|Xb_bRX2-XVTm|-7)QPUTZ&UJ{oWCOUlY(DD>uBE~8?$o*RK7ydWokdw#gN^vRRo z;G#{=uXk?Rd+&no#v zM&0lym*y^#Gio>*s!xr*c57Q?@ga2I@%5Q!`VW$;Z^uwmu6pvFD|j8K@kU_HV--F) z>f})UU^X?L-Qg(Dd*8O|W9;;3tj|Pq>*Si)u{X(k9~@$(uN2w6jOg&Q^L3H5oJ%6< z1wjq>?FL8M1B3OFSy?4uamT)$YaNeq`h!U@c8XSX5B`k;sMUV{bAEd-OLs3zXD=;R zp;wO)Ja%_2MOXxHr(=Y6;-!2iDD%6mgq&QNvHsR#2#Q_yIJbsmuG}klp{ed9#sNSOmbah@s6NbM}i+rJr=Q+~qTh)dt6o9Si5cNeNZ1U&q3Qc+qZ36wSoM-5Wy-bUCAPiC*j~T&nqRQ zd2KzJFZy{$7~0^~UA83#wk0QQJ-N-4Vg-WqlIgED#MVFt@*#4(yrm}4+pC*9xbO70 zmpTzpCsL9QQsaGUVi#Skc|9_c<3(kJG_h5VD(YjIW#a3@1EY#kV;zI8OG=tKs+&2= z2@4~nY>kV+$KD(T))@xII`gbo?|E|yO_&DI6lQ>oiTV~FjB;sl_2ZIV!piDub;`xX zYUV`o!hg)F#I`N5YgTe3wZEN=Rl2B>y}D9@lD=K(7gy=mR>>m&z)dOeO{kpX-v1Fj z{0log(hx3bmGd<%e;F%X*4)Z!gRT0qInNJD@{+$~M@5y?jCd{od1u~4$eu*nEcoBf zw(M!=peyfZXHJf-kd)W*{~kg3SNwn?F3!s<^Z!OQ{?$tVy@i;h4Pobh%zpl@Ek(5_ zWFMOynZJ;BWqD*oY{fDuibijF=>Oos>FrV#cjwAqH(sAE{4yi%N?pJ#YsbnOnP9B} z(_-`Yrn0YheF%Hjp=K1p(x7{Fnc(TH%nXt3PH7ReGW!)Hy)3v9v0_FDSppw& z1Yf?*qX)FGc^Q2>bBRa!)pp)UtKCUqp>ORE^>nV>k!Md4aG`rzS%+E+E=D+iA~xI3jZtlYAl_0uu?HXn_;?`JeF)(f7_ul&p=vm zLNnIQwhneabvH3JTT^@E@&QiEpsp+3_autNSLU1MP*w9D)>H)XZoj#HyMz7wM^*Ah z1-IX2(0hvKaX!1Yfv%VQqIBJO3KicYDT zQNyOH2tI=ZE2~AKi?u?dmm3K({#d@4#?N)*rEx;IkijN?t&NoHF7_anxa=pES1g10 z3PZ2QY-o6`z0V=wijcYC4XblYUv$|9?dK0r18_y_ZHU*H0kD5Q$?bn6et$=wlgy-$ zWP{NJsh10dRe;Vltw9d9eEUB11-pGLw7%Of(B?MtX-Qj>mzVeH%c0KxEYTlH*Iy)@p?2}AitS4{=Cpc; z`K!k5_BCpb4Sc_ha#MBxGFy{Ox4FBDKilV0J?dM!><%q~z}RL@vIKwFqdj5j!@6S8 zgKR}T{aMefJxvD1KW?q!P`R;4kg+b=6MS=5u2eN6 ztYvDRQOY$+GhCN-;{Z#Tu8Kp(91k^Z{y&1(TdH1?_`R4dU3itys^xq^6G;-qml7*X z!VjwX=t}2y1U9_Pi?-&n4olE_d1c0`MHk7fJ=?fQdoEgO7aKeEWS559J9GWYrEYVS?(nsQ~idvroH63 z_ct!V*9}e=6xP0bI_+*PP&nKmkken&HLzMIi5Z0il72&uH}d|v#rGEu1=IzOiSN^{ zT+_~{0$h!`*e!U9SSk;=o%eHn%{f9%e-WMd(6{>IlhUd4($iB00;L~G>Q;PpWsZVs zv-@~Y0r;dk*{AzCY# zrsR8yi;PqMVShEwF9+Sar-1>`kW0j)m zM;kJ)Xz-j585=vhO59v`b7W#-;uX8t^PzXrA78&MV_#<=xMJ@^>L>?Ybl=18pW*|J zEG%k17W=-iy|1?R{13yt{CqrAoEm9&`+k{+Z*AZPY**_29Av;B?DqZqUg>+@%=NgZ zQ@7rYFVqwz-rqvCJdqavnc*_M?%eRw0lYs^+*9LNI=hD^TD|i_;HpH|gI~?vTONkR zpZc5_=2&N9*T0mS{=U?&qvc#*C&{x$yJNfQ8f)7!E5xkYOZ@%TD1_EfQ-jW@P8pu6 z5Y2x1`nBqRb}e(yAl2$=kA9l2b?smU(;_$$Z+OAG?2kH!MxBoKu$r4kdIlf4wT(Bj zzvwQ!&+ytz4H*pixK#0Gx_>nfC?8jJ}iB>mai^#UuoW{uk6ycERma!-8@1ehlH3{5m$e;DFC8^ zf&w)&#nj)v7XNutj;nF}7N_h+^$xqnpN@Ca^1cZ77EZe>4Mx;RP*ZM|ewzCFOBvc% zz{#cFM9DjO6Nl(RCY+R<>@}aq?HVz*|8Na~l@xyDN9qP*3KadS>VXNP{m%|4dv#Q& z%cTp4MO)d}y7l{7kjy*z{{7(Fx*Jvhk@uMy8!soBMDBQRzH8f+)n~dDTDACsUm(rQ znf%$Pm1sl|`OR%OIqRly$xu!GIJLJzuS*@}7TNkG-p)@~>v$7kHF^w$-Yd}k2Nkb4 zSSd}5cI}F&jFb(uPk$7OlEA$A^G&jjtaNEQIe%A0sAzkI#gr7upHWFrt5zJ;MQ&W0 zRc~H_;t%pgW;bGsN3ZC=J_rXFSvGFd7?;}kFI$F*}LIN-{PF9v9Ap| zCpX{{GqKYBF3rVPR-2P^YKo$cL+)3?mfM!!I8vh@v%TYz@kYmHhEvYexkkY~QE>+^ z_K%9#IZf>|rZtaD9#hkcmJf~Zzsxgnq3vToB{$sc!lNo`mUGRE$2~kb=Ilc0cBEwM z)&?3gbT_&p9b!M;_UsC&V<`^3F%Cx&>g2rsGf`zYzb|tKsJ}>EQdW%jX$b!6`t8cr zi8HFQeS2SOc1-74ZwZTPdA??8!Sg^_=U{IRofav2;HK!OQ7?v=_!j6uHRw1c#pvkS ze>|hb#};*}&V)O4Ch;PXRWtP}SzOZR0yX~KEwp9CY3k!oyM_VfuxodHStF!#WA?R1 zTJfEqUq1164c8y-CH2ag7bJa{HAndjKo$41OB`BC(?_3e*vIM%teaD;EqWD8Bnt=~B*=?nWF zp4ndKXWMK!x8y|WY=x25z;D*y!r4jaxa001Owh*hAU5ncCvEIeirJrq^rh43JKfro zqsgzIb$`K%>4B%&Qv55)rMv=ZNIoeI!N$F%lT_igZt7J{eB?Pu(0r|ZvnsD`)cJuq zEisW#8oQIiWmA8Lb+O9#7vmpohlsi-`;uh7Px&S>Yt#F*lZ-`u*&1X)=UKUn*`+5}96G~7sm^W{pU|5BBlqM=6 zC@JGisF!)c$myJWHmzcnr?2$g@sC$hXv^g3yM$N%dWD$HKNsIA5_u9f{u?{}%SQjX zjPF=kmu^urHuP0KDt3CJ`>sXdS};W|@R;}?mC`jY>*`7Z)+mrlqiqR*mee0fiR!^? zJrtjDe?2!+@-tA1?Y6fV=WKj<%qoX46Y^X3t>M1w^mwcOmS}avM-&!25lH7wPEOKN zEec~cWVlbBMIa5|e2g=Nwh(0sBiwbvz0*CWk0*N^oIX7H9TBYv1-}YZPsvqu61$yG zpB{K;6_|0Xar1GC)MxMReTo{Smv`BrK`^?L_*lR$)I)oEdJ>F;)YMuune{tTGmLDV z*yy8ni4@RK+h>=yx@9J&Ar{s?F<8@masJv5kH>r4zgkRhK_DCEwf9DTRsNdc9L5*lqyh1Bv$@8f8?OMTiyf+N z-=;;o`;Lm)PR(`SG7^z1+HqMwX~gQpf{uBf@?D{MoX*I+~Pd z#lkp_<1+G5@VT+^artmW!Lw)2zC-nJ>(;HL$}A-z@g!}3{-?e^4ag@7TUY(*m$ z5x;lSfClwLBg(l}i#x^SzHNV+%Ol@|8D-!}CtLzbQnOpiNpaE%t&AY;#BfU5ZV4$w zf9mb+Lh*4ey{9pI&|&(g18S+H@_qsl>HfLl<7zE`pd|PpD=WjUW-UtE4G#`4N5$2O z5ABb*!ot`#xI=1c>d}@0)FPifC6(Bg7QJP2)Xd#SZDQQV4&yVQdwXra*8RFhWtW44 zIM-(C=z-zhW|)~*Riy0TM_=)ey|hOY0~Mq%@E|+;b#rqSSW-iQt2BsB9BxAFv&eI? zv$L~-FoRUalHs-+4%ypV;hoAT`C!n+Xzo-U@kb$qX{GMVu7v~O2J&p@etv875s3M4 z-9F9omIMnaN(WL~f3j0!$y7O9Bxk5@A8hrU-1xWqUU7P2@LS8HNarm(6DYh@Qlf5K6SFhHK=7XHpe>wL2eb-4hVx6?*KOLk z^SW9>dU_yQJu%JsE~u0eB;D-LqT7+`a9-qvb&%=9L$7!PiowpEP*b4Gni7_c?Rgip zujzYIDQ9Y10*a!UkecehAqzIe`#Nc-RnF1P6&2!Cq3*5}+Q$He-VaW(v zTU+(f-NGAcKYJQAmd*K7M#fO)^tZieS#YqkufzK_DC&bK-xQ9}4bt7%knci?)8P-S zbAHFCX#PkskRbtLZ+-dlPh{guY|+0^+o&H?nQ>jpW+DApB?mK{`O>r;HtFCjlak|w-EajgL9qfq2V2C!|oIL_>ieh+Dpv8?o5Uwn{W zhN^pnqN|iDLI^+tZI1XO^=bP_I|*nqHa6y6+K#$)fCLNul~qfDD@i`+mDgn!v2v`L z6%c%|e%@~C>)PGQueA5Xl|5ad5c8o_-}zcuS2*KyqRkMe={Z+w2R$uR=I$|3dUa>n zD%niip?2q$uBk>#Q$=Ty)se-z9c3AxSqvCE?B;-!?>a<0RcSgQhj~dSiCaZ>@!~ai9Toz=Q$x z-Rgd}a*9D;qd4GmfeXu6ew z@XueueE;uMt`-wy5Hd22cN7|4?A(I z!%jhQSN9+5po%s%(0qEh@-)TV=Ib~Lty{Ea`1Sj@I%#oFN*?m&@>lG=_IsvQ-j=%B zx_x}X3tasrv<9C!?G67Jc6GmKQ=#qb#%>!e`np%wZe(X&Zt3C{GT-&>UD?lrvlm;I z(KVA_qbp(k10~OYrb|VngTo|D)XZE6C=z)}N{5&Xxg|VPAB?WAKtCK^;`Y5c3Us5C z=X6eGcSX{Zo*Ww@^qe#?V%!F_TE<(3c^!EpGq6xhL}{x!qf*3__S>9 z|7jPrjZF6Zw+4Ht|GmkczqD=wvN@z}NeRIG>}P(`oz5Wii;fph{HgRaBe1_^_;FZL z(#EL~DsVa+f#v%Zakq=`2=EC|NK$GE*4yHP`auNofY-_Tq?h@;{p4-X0YE9(GyOI$ zhrttgSiE>Kw>H$rgyi}69a@zzN};zolAocCCJ?tj5H{od_-Wu(3hgRBr@|Mlw|vg; z^t`++93*75JUT~z#lJSt>FM0nU`?w}$o)G*Nm1=L3Jq?^wif-YRB?=0Yg(^ZT{0SW z|LFFsUB02OD@CsEU*Wsp57$(-!k`cJY$0+vY>)P@abCIH;7ej^;+2*+DpEp%16S2N z%^qG3t=eCyETH;srsljT_`&HRph~19Wi=0lk&kFPADo&P<@QA{av2%R&}av-?I1)i zcJiCuDB$_r$9_3d6mV%hFYBW=i@tp4mgtSqdd7k)0FeY#hlYk|J@2!85o}?DyWl3) zAXzGL+wAQ{=m1~dec8ut5ZHAiq^=yy&;&Vw)T!_$o@4j5 z1^-^*och<5^Ro41R;8?Hsd6RH#8aMSfu-`Ch!$k%K+U!?{OK!GG@O+&Eb=bXW)b?$ z3zlz`c!P3Y4N4^(N8aAvROjW~qhvmp}~5Prh$$+Ab7aRwD1b^sCpz&giT$|vGgeSIlF}7grHeY0Z55B_*eO|Hat2fi*RO{0U+&;6xzYs1T8f%S1{%Et9)O*dGq}7`EhnIAhtQbW2rbW zK*T@dhrP}DWPNGdN@SZYZXda+`6;^6FBUg`7XI^f)TY|TP%ocBtt;CyG$mOUXwl2J z>CAN4t}HQC(B`W-eqmYW3v<)PyfV?sZIAr}jDqG`mSt#3ipbJKW$ZtfM^$x3T)Vf3 zRo)Mduk`)m(i83evICeNxprmIYAGQ@;=IAZ&Bf zDxy?i*s?jl?*h3cMsG!qj~zPD;Sva9;5CDth>zhuH|Jy$ursr~_V# zcB0TrE|WuXF4w8sNSJ|1s{=457}xDziKrYKF{FWx7labQ0?=Qns=x$RpS;(#nr>2J ziKKWRJ(}b*5(@IWyZ2LK_M2@RR}Pr&Sor8s2&YtnN{ht*)~G>RZWpJy_)_HwiPp(p z7kSLDIdxUGH96_l*Z}l&BRBgaXdu*sA1MBOX9blSqCatUrO27_#@2u`pELu(b&h|= zod0P;c!7fz4oYTrHlS!J9|Q9!J%V|?;QxBc6{9Xo_^VH@4AQ>Bt@qdVk^<7D2xUqM zGa3RU#ca-F&@pF24EUOMA}aR&7!9k2#vMpLUE^Jw00a?lbXePh#qIp@WUuNQf}C_6GL!Ycn56@&kx@+7cA8nq$oKc%G6^FtAx`SY)) zJmh!>xGGs(G}Ml0{Hwy*Xiqo???B`%wT_)nE0@vR<0&&SX$gsKxn}AZhCrbMR0heB z?Lk|_N>y(8yGV(ZVl~ubQDo6%&Q$*ztHxLlW~h-fs5~bR$V5-dZ89dn>XQ7|p@D&X z!@m{b5QO9n|4KCp)1*?(sm9s@b;q$j| zWOe2%+|)_64i9TvGNv)mMG`U%jrqHbll2noA~;ZYU$~5Z8McG6qB>Ax&6+i|Q_jvW z8ybA5f(&AQIlr;GHivU)lvm0zElrOb$byoNgc;VsLqA*Fk~rr3%jos`7rdjERr+0) zBt>Gy_?tQA!K!Q zVvVq_Z0QM>0?BzF*GZiG(94p|)x!3pgnjm!*NZpqTC}`@UGB~Py)QF%`tOaTMQ+}y zn4xujk<+Qi>B_NO7E>1P(@u`nT6$vFAM-2?&U(I;YkBO*ocT-FTDQm+j_xEwyR};} z30nZ%wTAMTVX%Mk+U4kW4yNdzziP=&+Hx=ED}Dbx_;E3Z_|v8C z*IFm4TCLgVP5pG34i%?HE6n`Sk{o=gy0l+^`iFicm*>Udg6^%Ppx7QfuS83SeAvCG z-hDCS?X8Ki^yw|sL@A{^OscpK9z17^O>`^QAaaw@>4;%O`~DsWDpGm;zH<9yAEgY2 z+-@8@wt{E;U8!L3^%8aJxq#ABkd8B@3N26XSWF(N`zn*}x|rEYJkd5Q78Ai*&(!x9 zoS~QUP*F?Ie^Pb(tYh)<)2nzt5+M6!sXZP9oz>aNzsr1em6#y zomY#QeXyA7v>5qXIAu?!w=)Y0bX`8z@+(dCFAXHM>8UoJsf*JifzvmFkC)n0XTR=i zEuXzq5xjK%8PLRrTjJ+G>r~oGyLY>|d&Q1M#I_B_PAhVq`uWYI1!0mo0hyS{KlM?3 z>e5oT53`*sDk|K5#Pf6#(rGdBY_**8r)7y|K8se1eOI0yR3MIBrhkzBwPhj9HA=k?CJ=6tUcbLidD@*L4BW3 zJrSR*74Ij7W>UKCJ>PeZr_Z(KoM5MRR^6HAujUv(R**aOWd+u-d{iB?wF2Z?lccL; z0(MX@RtW>hb_NYdtccwjuNF7&pfFQzmUH4_@XgX^y$6W%GvY)*{* zIBaT9YWXMxN^Zezz$V&z^ZRVS)F$c>4e*iSj%Wp!%u`=?t1F!T_5nlDc83r9jQ17B zW-1X?%5>Mn+Qrj0t8{9oK#TQ)iATN5Z4FGs8G4DvpCPJ}tRKx3dJDZP)6dM^^M#S_ z#@`H|Z}29==o2tt5#k!3TYf&wJ~{H;^6}JA`h8Z5$uE0c+qqiHWFPl0e*7~lz5k7| z{>i>a!viP_`@Yhjc&=|y{x-bI_S?IR%l=!={t#r6yYpjzk`utnxwklbr;s+!?D5q=+XIoeVLvoNX=;qdV+e@)TsVgh#3J z({rEYig0^3u)!`=nm;_dvM6fnxlq*71Nz*$6-dE5O2H+d7kYDpmw{|8C-0-def9GE zrLe7LpXHRm8T7Z$V46bSsf)#~CZ=<~^2qOKL2$>z1c=*{ql@j&!-oYhFRu($TRVSx zxt!uktrq63+2 zNL4?|vs7)0vv73z*_5O67)IqKm4b;)Cxo@wQ~25(p?^^H*s0I4Nm(UXC%%8!o=21` zGKBH(pxzHpFCTs0tu!o8_NJjUz^YEZ-$7b&qq(u;@^xQ{bo!q!C+Ot2)_uehR8?I_ zwiX^h03uLv;E(*%JvXU+W@gsYQNxEUBa9-FjkI$g1NJsEwApsY2YE1V z@mVGWdFb&k5Z3yn7bW*}xxV64alo&mXf%Ae(V;L}gznLcv8kia@hyT9Qsgur&a0O^ zJ9NjQYW3sqbCrI?EhUw(IxB!2na3ibrBZ>KQcP^31a)+DjWj4kd*%9dwQGyy>a&+%x^WT%@bnTQI2C1%aIRBk+Xp z|F{;5m_PkQAP8`P$sA8ITXLMB43e2wfJJ%t2}&-W_!-pgfn~GIHEASR_ z71_qCj~{uxmhzydD*X+mEVLyhBdfx%SJ^rX=tYq}+Jm|?=tf3lRFwBzQ^7K zmUF)+c+E>rirE)JT1ZU~Q#DX82Z_zpR5#V-YcavwceXx%?~9|G+kwx1?&H}A{iDIk zZ6ppkoP8ZRGBsRSLz<3*^guTcAf-{czhm38xet#wmlvQx=LiGILsi?c@mRLxTT?4y zt>TYIc$<%9HsTxDs;aFj?o& zPw3b4<@(>b3j4X3-%J~J?;HMPYCkbW^-U|`(_P>-A&&76>h_wO+qMAB;Er{iMH2U} z*KsyWu$V0s77kPz6C5N1aA&jXG@l)riChG%TnQ-cxA(VX4|kcK9Cm_%^|$j(hXYmy zN!%a?hux|dNxT%@w8RUX=#OOOD%@rRwQ?nhU#0P9nzm4VtDjN14a5&=MDFBY z5Um~N;VKN|BqAK9rd>!0K!2)3-;sQA3k5^KUCG;b`7Ywkdmaz0bjR1n7zn%SB-W?9 z58ZJ-H{5IUNNJ*N>HX`n3a(vTydy6VU->HNJKxv- zy{$;``;*z^*O?p)bUGn~%Mu<1CglOhU$tu0r=ahEbw_+Z=;blEHJF;G<&7+LLIIbo zRVoVeteejP{I`y`U-;UI7uWT zM|Z}4Xtwt`Y|aOr=>NEba3kVn_hC%#;Ii?xDYx!YYCCKa97Ul4p+vJ7Qu8jd*(55O zk&{za(NJ5<=IiSlvab{mnt>2s=^n0Hn$Ku#>RRkDR2o~*H4m``TAzYm@~>Y|Bq71+ zdGR>=JWtDYcyi5ykEQ5|1Pe>iHa1Vo1MKr~EgzpH`@B3$c1;yomf0d&Sg4K)^7#50 zkohD==d5y@+L7cM@n8= zTS(SJo2`zinXVLE4BNM4Ka`5Y3S%`sXJMQfFs3fNAHl+LYgQdRCuEawMLwUs?(eL1 zC{>bWcCj8NQq9xMM!ow?9<_2Ax13CG@juNC8Fu6fmT6P^Ll;dcB zDD_oVonW>n^J(TNZ42Y=gSv1c4SZY}3S7)cgQ-9bS#<^+DYyKHJ#2v}sw~xoIe`U(mf!KE5_}1IR06-(_v4 zKFHlVzd+M)Cn^0Mi7?aJlm~SN!~{+4Z{5P@ASvOncqt=}l)LISkb`3<0svKq+ZhXM z?e!<}kTBAMT-1fNa=00=yLdh*IW#QnFl>>}8D3AEDUT@+;!E0zI+x06P@CNRGtE}{-gB+iLA00(5Fd0KAU0Yf0M5`@bnkk3ry z?~cio$dF+!8^8}321gK*S27^Fn zOoHFGFb{;fJifcgFM=c(9(=m|mXCrAAiFVRWZyz7!Sw1l!Rd(C5uYQcgUE3+B5gwU zw%`&Y3i;sPBE;6jkZB9t@tTJrTNiG!pN*JTxV8#gkOcn8;{*8J&`}8^elNTr9$X6# zb@}5$jLLPwuy%O?F)JYwfMA1?1pnY+a54BE0#GIH3J5~Rb6j1><~1ZGgJ=ezp$xR1 zY1)L4)MpUx6{mCQsx}Gh1du+H>Y<{_Nm0A`X5J`mHOdz=hD zKN#6vH4+%`lGco5-T(@a#XD;maPV)piKh|m+YF{46ds`^!NzAGUj+H4iT~Adbt0=@ zpuhPFT0C^*@X@bBU{Fd6Cj<|@3|15k1K1w8%K+qH# zLt%j^7{bQ^-U*WAE8I{!R~Q(IS{}k$9H32;8Db1bYyrqH87Pte94Lr_8PL>!-80N1 z3nWE5SBbVeUMdm)F_GaPcd3M^n9+-0I5FgoG(fA08LTS|KL<%LAzOri1@k6M+3;;A zA*z5G%5K3!5;9q}1mWH8!h5utV53LbXd_@I5|ZRc+x;32vcpC+RR$rUnuG*nvRs`i zQ}7TI;v-6wa~OtX&z~R_7&0UQvxSNwF-Lh4NQDwZs^S17gK#khJf7(UU?C(wCI8(G zLx_Mhi4{>GLO39(YUeEy*9e9&iP0@s6M`!NUzia5DUT1bG|^&90$?YaQ3&NX9p!-czynYMx^vt~ z;t6wOz)6BofFmXsM&%B6{Y;VI+f|4kh>acX4ubbEF=krdW-Bet1A>hK6p-8lE0b+A zQ3oKL<`2$c53Y5`kow!59*LhwiKE5xuN!xsx33Jd?0&^`Q z0E9iE&i5z?<%+?D@G%sHV5}tIAP5FgHxM3xsfHZTxhbuV2oVs}U?v<1ST`k6><$o= zBZWjTY%7+-UXB#iLhcZ`1C%!+R3-(Tkk<-F_Bn)53GZymqtWW_Ab8*`WUF@&ZPf{6 zxs3N@JCTg99tccan~$>OCv1zH5?3K1sUz7eq9ZJ2#$l@TaT->_8qhEyE=i1K1B_&d z7>OzqHD=_Bl#WDI@IC$o@q8pD>U0dcXGqK~Oq$`3FN))HuEMB5CKKq0hG0r4g4V|g zvOzPrE2N68gdq=sofS4G;w0PdFpg1g!zYG?|b5 zuv|pg97Yz?9Q_HY3X9J)C-LI0*RRjY;IqvsI6nZFeI7t7UPlF67X#mm(0+!q2HikB z?(DjeX(-HvSa37bJXL1*O>AWfY0L3H5D4v=jtU(?Vg$j43;>VB)rC3}w}TQB(SJqN zO$MUpvVwzp0++PGGS-uuV1I^en<;IZ4NThf-Z;08>C<0vE8>Ix!*Ed77ld51A_^guBwo}+ zOA95sJO=6V>*MYsahc}ila7OiP?ok(Hd%PzRfNgSk@!GJ*nH?Ml_(=L31~wQi`Xer zIQ#=z#8h-07@SZ%J}4FEm>6V3DD!(1pF*l6;z3V+$U4~2=R^2+=Ap@v|A&WTg>l^r W=k$(0SNIJ-T08XA(o_yy`u_m>>lXq5 delta 20530 zcmcJ%2_Tm1+BUpat5#`7rijduS&@h`Pf=JEi6v7-WGGbJt3t-~7(%9G$|@Ae7^Tcp zv?3v8Oo|W^@gLV??fvfk{qOt#@BjVZYi)Vl?)w_f>pYM1IEQ<`tNrCs-7oj0{`fgR zPx_CCOce8nU2En>XL=T8Wy8cmqaXBhNalw2+r}Ty+Z7{NWvjEs=}Bs!$XPbkxHKK- zm;Lcw*Ha%m=l#UHZsoQj$(<+PPCwt_TPnO={6*JK&b@orviELf?R;IKU8P;q_B!10 zq>Y!R>|#Ch?!W`3!s|6B=Pt_mi-dCi1U-CZOXZvQt(sw;*b|Of1;ARMc>2+9JHMQJ^H4E7V*< zYj=WX)iwMSNHB1d8f`i;+N3>d;8)T&YEr1k=uzDrSg1H^@Vwi_gKzX2uBvx!S-Lpj zN^y7y8;M43i>@zJ&fprWJ4Ti=-{QgKm!C5ylBldUNN-~9N>P5L*k z_S$WhcuW3gD;c$y6~SH}ZvGoT{2HWDV-v^Fewy#FHqFslFv(QkIyWvA6l(GC}8sEQ9eBE=O^{tO|$ENb@`l^=6t9;diG*(6MS{=JA^|K#e zj>x8Mo0tT`=l_(dygjhpG`{ybQ>dn143LsR@Sme}j}IhO?TC+kqbpL^$0W?qCB#<0}O*yIME z{OA+DvaCauH%ZvBEQY0#cVp|rU3zoH`CTu~wkB;jY5pkQF{qDKPJ&96ezjk@Q-3vm zD8@jy+k$ywmQe#cc^lbsZpSX(a2)I!XcTLjX*qn4KVo&}D%oJuGFRT&GUcGN97eq5 zq8$wrzcq5{4t7P|>n-==xRGUEDQUx+=gP9Q_20HP>1@?cuQ?;dTWj*`(j>EuYc~G9 z@=es%zwZ;-OFs$qKaKtfpnsX9{j_v@rtZ&6_ZUSjTe>Gzbj8v=+P|&-=Scs115y7v z(yaLT56M?O-(6ZsMy1JHa52)J6@JRCf1PW)CZ*f6FLZu5X+fm=<5iV;-ZZ1Yf%<4S zna(nA#g*&1et&wIK2*qWF6!#n-hI5AGniX1VVycSXjhe6GFlv*py-i%e(Dt2LDiqT z`1rJ5ynbEdMGYlXpLq7{*~Qs<%fYb4Z($t|PclnB{>oH;L)>~{e%55|x^?3<{9#`v z>Ln-J3d*9?IJmh7>n#^#qK}>9;FNRxz3rZ<*s=V8GuU%W;q%^7Dw0lHM^Ub5voMC&W*{S^eeDZizm8R%=u3b;U zsCnyipI+~ii?bbWO%q^ZD4U4N`O@XqEG;GVIlIm0^_m z{B%Kg)#I7B^%Z_=0xOSqoq4wI;`f(HgOA6&B?nw%H(Y%8i@Uq~nOecy~TVB1Y zT)}4k1N&W7&Rat4Hly=z~nyz5N2 zN8XwDs*jv|nk*LvEM>gEyBqA8GFh0iT#U+VR*rjhmpaT3 zQm@HxpX^oiDlHeMEZqj84l`wDW?KFDK1HS)cz=~=FxEcteYMJh^tyHH)^6C~*0{6% z%r2gVFIS7cf4m#$`1<}OvgDQAo`1y3+A|gP25tYAv-n+7qI}+!kKATjTf^o` zRd4Y)gQpK{+f+DOJW@D27-#z6_zD`QQ{knZ4sB=Z#S6atWo+ouVH-VOYv>bRqrrCU zx#fJN<$Y?=NBL{=hL-N`#-;?t*%~e7%P@+XBP?3#{j2O6Z&n`d`F=<=eSch)c1FDG zYL+~^#@kt0q1t+SYxHi2B_}6e(pKWOr=~x3MCuBz9}su;TEn6;TWffv@C@&kEn9G4 z8)J8$6^)ZS-3#3lmbozPC@S&zt5MmvO37U7>h%_011!^HJZ+d zg)x(@`KeyYcWBQ&J96mU=QkQQzyJQnn>S`e^Z(ojlj7esRyz6Z#Z~3!Ev}RY-|WT2 zKxSdr((m8DzkXo7^6udryHtIlXHWGLm11k2Jo)MIo8nxYqngk~rWShUV zg!x2HHmf8Jzs#VYUntHmeyef1zExjUC|3?1e|Yj`;YfA~yLeKw?3;SYa($bRe`S8^ zDNk}OYJeUah(;ZMs5n+iMXZr5y*`pto>+sEpPiu=J?QJy`6Hl)A=j}(#>~v@NS@ug z(I!mS{A`0tL(BR0lD8TT4%#RtuemV_lI^ZH-CS~Q#6Uq_XJcR5ZZKrxz_T@bINU%5lI+{P~ZW>TjnX6^su^QnA`%tbN}{a;PEa zg+b>UyFB%qH*bC$q89A=sJQ~FK0|p*?{0irsF+fu$X}$$0_tJ> zp3Xgldq@AlODIKLW_IxaBf(Xib60|Prpaw{iN4W`UsHGn8h`injLAsyxd{flQmDu@u`omB2>z^pC&}d zFIGA{R{CZv@5<=iwawUcS5%%eegK2S-AJ{*{u21jK@bHQ>h*L9^Zg zj&?^%R#Qe+Q&zVB3y#3(SZ@vEfUCkkXc^zI`^_tu(#=ED)k73g#|r4|aOXeh;Xe;! zi8G22W=MmSip;CJGwonrec2_Ws_N=*J*TFoh^(mpT2@>zqyKSdPW=zif`4TNgoV)l z-1J}a3+jJ@skj(iFPjc^{M=;BXy9aLC8t)Sz2f^D`Igo<>uNP`vf7?B@GBKE9I9e9 zQ=}%Z*XBxf>I>D5uzWw^+^y@8n6xG$LdRBShnV^BPlCErfeIbRC>8 z4{9`~F7c*J<)4Ms6Gz+jyHB$y#H$WP%zvmgUHdv2WsBc#mDq}pm<6Zi znQmSacVzAfvpbvTm~^*m&MNxx{a--N|O|vLlnN14k&$*`Ff5Eu!EI-qalN%A^jvGA_3A8=p+Qt zqapnGr`7|4M0L}c23$Ejv|xKSFaBR`kNSTyzW7ysO0&!QJCB|ij@SS50c}v9Y1@mU zOurk&0^6I`)v^Wq2JRLOKYM*l{6I~=$fgK^h*F`oI{q)BhXN|3biyrl8ymtuolbi! zS>L-Ur|xCZ<7gW<+1gP)E&k|wnn4Eb0e`esJ)=&>ORkU-*_$P@;RIs~0k;1P_sGuZ z`&@!t5(63#TMmTVuN#mVy#~@yd#kLBJ7QawD7E_E5aitwh=+!3DKfvt&#=oXXw%B% z10QT&z8Q0&^yF8l-juYct#gz0^;_nrx=hs~r$$oXTiSAs_1W5%9>U$--R>TbCY~3w zem82gF)9_J@pc}$DKuZm16NAfN@N@yxuAHXU`;Zzh}Uvila!#(gl4 zNml6JX{Ehtt&Hwv%WV^S8tapE&JmRiDIMgm^HCGWkY9c1!r@lnO$zTcL@IODS$4br7Ms;8%&R`2C)H?Wu1 z5n8LDEOIk9V)%mVmd{qqYh-wNmx7yD;!T0qO{p4%MhDZm$2k+~%Yv+A>u*R3FteVp zys$w$Fxd2gn3_;YO!}y>*Sk3JO%q-fgTbaPT+4X(2j@M^IaaVY!{jJvSM$W0h12RL|HY|(c4fh=fM16SkVxsPDq*>qJBmaEmV!K`9+qcap z7Ke(s7n|pR(mU{CaB`;@R2+GDL{%KWF$WB{GW;*tGWntgbE%Req9^)lRUg4aJ?#fh|<7WDf zToE5CT+Ye%_A~VPv7+JK%EjiRvl$M#Vlfug&)=}y{r)SngSA-9!Rp1iXy^BH6ji{! zQh9m|MDS|KE^ZQH-9405(akYD-RV4ZZ0tN68FgX=@vB0}bl1kDx#?hm<5iOd3C&02 zgwF`*4I1>%Qf9h*b{7Yac6y$i*;w6|q4Gln>_C{lp5xCM85MkUpILg_GOJa}%=k>` zf2{Z;Xk}u@v&Naz_7|v`jkNTJUCL8Ub`lDY6Sa@+4{=`XvHy}Cmo)fnaaw7{`;@dD zSHw$0m4r@2PI?kWz6dZ}w{<=`-S?oHJLS=%8)7bn%}%~skb$GlI!j}FG|f#Z{!0bHgZr(rA5&}hNXFdHU4ZfE=o?NTiKK7vaf>Kr4zacub<7&PAY&+dLcOw@UWmD?{uf!+u0_MT!diYLc5*H zX6?$p_(^_Alk^VUo>Ll*!0o`n!-uP>g(1qd{g?-%Ki$Z!4zy20lOiK$a{B0N84X1p$^R7fy{i1nUjc_Koy2VRqQ zqIS)Rjo-%m=to;mBHoJTH*~|JFf2Y=22MR*{N5D-4=h$ zOb#1THxa8Iw^KiV;R5*JJ&VUTX@gA(di}&A8uqimmL+itNJP)y2id)SVg|8Mt7l@*n4n zGB>5{+_B@!qk_kwC1YiAY!;rEJWBhvsLRBzMOJ|XKqjJoWc++$<-QtuRm{VUY?-^x z4tid*X}LFU@^$T|K%ksb`L!aqp$S^pMPK_Q?;An?Wkh!CI8_iXXcK_?5b2XR z2Q(y@)n|>>x^DYrrSAN|7EXq~s&?hl0GB{|noyx=IrO>ewyBQtq?X&ZD65ugp1#a! zpKCU1oyrDt->f6+z7*XZ%>7++3mu3jh)OQ!`Tipo>VNTjoaDqxOV6OY6y>p-r`Sg5 zrSVSvJE51<77qkwzx}0a{N4J6nUoTC6^Sa9V-W)%dlJB!;IQI}5&7N4Lu``whSU}v zWGxqU*vR+QT=#uJ)a;WnDey&PoHnz+ONb3v8xf8<&bH>SNj z2?8iHjdcF{s&0$QG+l{u1u-is)S2}wuxJ#(M(hEx!sYBYHfBZ!XZMob z1;g$lp;eYYJ{~^l;9&i5DDlEI@?C@MiD%E;TT;wI!sO$~NMF=p3#&6{t_NYpGdtuQ zU+<-k9or&n*RVB8mzQrsGYCU%Le{RjplnK7uW2*9O94Im;Cs5G1N~<6E*5py9)=!*$w8eQ5yQ<_Ej{0cHA#fVO ziG;C>SIs4=AAE=Rt=rjd{;V!)yB7u6E}U#ql$Omd8r&RIiGm%chkZ|(_op}c!QCYu zd0-^#?N6Ktz&xS|?&hXHTeoK)`#L>MQjf`Stl#J6h}j}%{j#&OKKob+SXLU6W^bTg zgl*t%l026}NdiyhIL4(V_EzM3`^s4nXz71;x{+Pd6m)i6i`k>u$Y7NeE$WWIn8Gup#zY%j*&kNkipX z0iGWpv^Yf8s@)JjS_Q>yN}8`tx(|zSdOUS#v}lOCNnwcVkyCda!iJNhk7q1c#m#(c zoa^5{z5szyym5^(F1_ji1<0m!exjZvbwk>EdJHp7j!*R07AoSpevH(m^mL(ykmC<( zNY)xch*fYCN}2ll^?;?Nq0N%MBN-YaW4oGm>C(@S96Kr!_gv7uN@4j;7O8=yn zX6NGz@~FhbaDW#g=lpf}rL-(xp!#JSnc8>nwx+CGzg|X4YOk8wFSLaPpUYcsq4*Ir z-dAmW^k_ZQn)dYRJ`|LQGzbmCL&z})e4UwLUj8#iH3{KSNkshuw@`OJjE<%s5zGDk z=`t)tMs7c96whL@5Iz~X=L*TI`%B#1*&=qxrLW@1+G{x{WM)>v+oApPwpe54_4fYZ zuGgv#_S`b2tafp=0$z_*c|KnB^Yh0ql+Rq?S|i){I~4bbSd~adysE&eWkitB zHoo%xuwoEP%|ElU1WB-hC9^CU_QZ{XQa_XctiOL94mtPk$(gJ% z3aFgVjZn9~k*?1pzTsY4e%N7_4^F1_#ch1UjyI|*gxZw9EbpdPSmeCcbI{wKaAn>c z36=U^dioZGcZ>Yw{aIXT_JdjChla`Beok`PN9!ZRv%a!!gxWbc@FO0s8NUgyKzsB^ z?phm>p1X&KteHf#^_zz$xvrJ)Pjqhb=bf7wvrly`(oe;fT44`&va-^$v#+(B8||t^ zSXa@OMI8lr5@n@zCG5E{4QpzC?|$|yXMFS-pR98;x#vze=M3J-SdY07n6eZseC?&RlD{-e?M#uqMq%4HF&`zchXSh2n3 z?cuEv3|PHMKT?BjT2tnhfk3VxC_5 zWu@nET6DnjeNVI@2Ld8jIM~@yqb8M1(7k;drCNbeSzOOl#0zGX^UoBVac=Z#L;-LP@q{@L4)j# z&ao+MM#|NF^X{eGt2WASF!GkJ1ZduBH1A}diFi=9g(3-DgH#jIgM=6xJT%R-BZ0n! z&#W!0gn7+4si)F#(M3#o`a#%u;!NpPfB#<>K7=g?0 zIKb9duQY7nwFm@;RcFH4+JIA_GibdXz*v2?-!fo7@2X{X#!c}$8gP?z>|Oa14jJXl zla7u8QM%ywT){wF)ZPtHmdrE>3W8~&K?$HryLof7kYQp2VD|b0L4>96{n%W|x4?g= zq!iEmyHrw!WPAN|!|~i$3$u3Rr|Eu&?;k%Hgbt(DhP$h~H?3w^<@K1a{QA3SrcDZk zeBRv&?rcm4KBYWP^DF!7xQxiQFMq20J6a2iA1yKUbn?HS6Vdjz)`m?OR;8xC{&X94 z&(!g4X>7EmPb?|YE0K-}8)SmGf=TWm$NRIx#X;Gm_hSpAV*%BD=I4jgy1Zw)0k!G| zaxiB1-TRf3gt^0vw`~f5jv76`lZ?c% zHvLsQ1b;0mx82^|;a2O5astDpeVRI7)@b--*6?ncaDS+@&v{vejeoiy+vMorTHY7y zHU(d=m1EBCRL{Q7*YQ?YIgg5|vXSQu4ydtexVbYJ?I%hVBzFg8P+$eyOPcQ&Rwpe6 z56n;iCCP;jw3A@tC`3!Qk~|bxImgMyn>zzaX>dC5${@IbsPd!atp;^{fc?XctF__3 ziJ)gEhh$H-3XsAJymrtY>cX^?&%9@al+8nM7*yz=JP%}!-E%=MMKD6AAq12K#80ad zBm@d$vME@@!t?zKA%plD@HvhwKok`0*I$2)*>RGIg+N1Bq08Sw;*H(fvQlGf|8KRs z*9*n^mZ>tX3fvUDQAn+#c)8WG)fyJY+YW3KptJ>Mf3phGs6U*$tCCkxrqL!s<+%08 zx$w^~3dQzcc2e*EVxaQF9gcSR)2IHjxzWDvF(3KrUVI{46er|X#bJM@6nnf{Q=4rr z5z#zE(nny|g^FlmNV{E75RKr%mw}l*6E220sV~HE`Hzz?(_6r65ky4@8zM5>L+CHy0oh4sWfy}#Owwcft3JCA#Ze~>SU)&4 zKkfHRRlRlK)<%EdRBhW^Y55UlgJPK%g|iYg!iWCaw<@$&*{rjqR-$Qdc0$wfa1E=P zah;aP1pWFqXWi|a-UsPxzYY!H+6QhhMlH@2jaPBf6P3t6oji4__XJ@u2&Ax&Z|_#B zXtVnVgxErz9lMLEC@kIEdk=b6_*N|wCDMRhtdzBH;sQbSgtG~Z;Rp>4)sK@4)keDn z_-Ac|uUuS{yP$iosC9qZvPHP`RQ442qS)_VC!8K=!4M}|L*Xo%Q9SJ(1Z(HQ) zCCj_w4bS7NH=bACJ92|J;+VeiOazDx>*`r0tI5iHC)QuOo?dDCrafyQv2EGxMeV-a z0UKkkg8MGKp_xt-9dzLN)>IS*xC14?+|~C`je; z5INWud4l%5&t+{J|q2#s7BxW|B1V^xV+m_VUG+6}fhu z`2%TM*SB6n(&*D7;x95!zk4?rKA)SKcfl!UW@Xu;AEos3X=k;5o9N~tMg>+<w!M<*9WEE>$E)X_E4vwQlyW zv!X~&khJjH&_8%tFGnAZ?b1a?1sRgO3#bWX`7c{7NT-EDW()^XN!GMjUOwUI|7;?X z_fki@0VB=Q$_@dj(H>YfkkKfU@~x@mV-1Gd!z^l$IowFI#B9#1?fe7c+5eM-n^B-V zv#gr>Tdw~l8(K+Hq5sT=lKMI`A_H~$2U2eb*9jp#1>k2a7xorA8}y8$h~===;%t8fVQSbIpF6 zLlV&nv9H?Ot?jTR`ihqC`-8L?A*!h$Syp<#5g*7h!fTRa1!MjL$yECP)+#*wukxui zoJ|cRr71)~&ZXo3l4-3uC@qV_>hHJ5!M8AMr1fkj1iSC|hL9^{4u6Gu^i<(`+JfIc%^jI_nGu?clm$!ZlB?ylvReJ4Cy>I&N0kO*2zNZhiHO^<$JllR|B+#><&L-n7V(jDxyGB6seo z+v=u88r3xp%nl_D6%8d??AG5oQEHO_Y2RrpGa>cmXzJ3r98nHv8$5%=+`t! zdIPH!yJxlH+oy9qi#|SRc#PHJ@s1>|(QZQqBu0BXKPW$WX~V@odcJ4%9B2O(413|4 zr1!9%c_?b2lPGqMH!sdNH*or2B8|Qmr|wz~L_Da}(W4Lex!q@7)T=x5-*XmsQxn3J z>db5Fum=wxK9Bm|HKbe(R_ZlBUL8Awe&+Gd&QzSr;-t!u_t*sPaJ=pBsF>T)8+N zY}x;zB_*T`J;LJ!=%RF|7H6ntQr%p@f^ItdOvaI>MAB&32b6bblQ4fr8jNS-%4a!A zpY{FHQhkr%RzU=78^TVPD)ehA6(4KGWxIN$fOp@KG&oyd#V^SNr zxbc$mxsv&Y=J~Zrb6rEP?;yUI$o6;~9Om;iMNC|LJQ}^FrHentGLBkU4D$2Lt{U^1 zDVd!gQfW?BySi;a*Szq|rNtjTivw^(?@-ddZGJ(Au&jzcQ37$tKh`?0K$9GUWqV&feSCtXV_+dmm9m>Z=x|K0~_S zNuui_)ytgpZqJz4hTz~}ca{0~8x&moSCNj`4+|f27J{tUNzXR207t()Q<+!u8IJY| zR9Wa#Aw<`G45#chQ_{0vM<&F-Fc#e=%% zap4NdH5c|>Q;aG09Ot7l3=(?L;@)+x^K_R>^%lpDT)$D&)5Rnu-Ozn5v)lXo4%~8_ z%vT-8WAEh^3(a<&=+{98DX>q(Nv&gPkl%1Dj|*lcK)#21~k*b}889aVVbq^gEPRKS9CGAJF#F%zCIP zfpGp7Orzj2%H`4HEpHqxag3aRfEA?jsjDagNm`jV$O#nm_$cb>>2*}nPMH6&AKE0OCP8*}3H10W`mD>xAaxM;RA70O` zRHHfnK})5#XW?^?XJ_YI%^C8F!n3OzCP`r=^jqEGvI@?gZ<=KbQ#q5ba(tplR-E*I zID3yA4e{v}_PK17UF9fejiy}3zgkkXX3hy+s~nK+H9B!rJQspK=kaGe3)g`WZtp8r6hn-YDLg*{G9eqcZ(po5g$?500U7%` zdluv*OTR25wuk6P2~Ys;+4G(tC(1|U+=d7dC-;-TPyJ=grQ~_tiv5E@JhhAdf<=U( zaxI!}c3qejQ~CCEgC~xXU0hOf5^p<%Ve4)%V1xKwPtj3Ajygr{>iY5i2t#si*LYN# z$mR22x21s85zJ#{Wu-q%?tqk`3SLNrkp2x)dN3wBA#9kK>Ua&c1d3*mW;|@tre`Y) zM3=ME6dCY0)~V1IWQfN3FE0F;|7y6nBWq{2-<6dcTDx($=9!0qEAA^daNF=J&vi7r z5^#ax$QJ*Y?wlo%*!-ib`GE!5ZJ==CyWxx^yRRsIkq#H#A{s2<834i)Rj1ea4s29+ zZ!aH7l0wx;z9WA3*&u$cYhS*%mnWi#^Ky%dPdsKOZ(_K1o-P=_!ZZH|4rv0QE&T>N z(z?d={^?2{o5%iE?`T@w@0%3IQlZD}WLN~c$poBY@m>X@XzQ`JkKK_^9;_8k>QB5d z`ksIiRpLI(ltcX(=_o$Cy&GOg!DoKg2Bj}QbwhKCfXdFE&p1zHwd|Q}kR=C8vX2NE z&wTxGklW{*)o9tAQ!zQ94=;F;kZp97>%$3?gls0%#SIYf_FI*FE;@~he_6TyOn(jI zXz8SZCerQ$Q5m4l_8-$q? z3&%OgzQI}LoPTwf-TxAD4K6?!Zm)jE^wIJ~9|?=n8chNdBw+DErHzLuqU(6ChQ)Z$ zVX0!&7CXwk6;c18OS}_UL25t`z30wdx+1Octsci{|)l&}oD zhlPbTFir)x!L=l?X-(4ufTaVlx{>{PjSNNBPI4B5r}3nKd9iySX=8I8p!-a;h_#xf zK*KdfjhAq!$PRk$x5Qb}!YSYUI|5dz;l5tPS2a3V&Y7{Yv2{3Jm>IQ&_H*ra50Ec? zzs@`mP5=H*(4c$m?d1z+30{!;b)On;Wiwg=rifjgP2>SVL zui-7bgC)zfJ)r&4cmYNt+Yb=`(z}nd=l8dyYAI-?E69+thE3uO{SH(e@%oLL$HKf9 zoKAIn+4RmqLMfcse>HQ^$E#AuNP>m{2x``D?Iy=O9$-+sjZtah5b6Z?;yNI4toXUT z@+1d(*CYlr^)v5Xg;IJEZ!Db>>DrwsuuqV|SXAdopl(LEFUF;79oo_oNkvw}Ht$nJ zXwzmYOl4sgp(iATWQ^SIdypeVa*^PxX|3Yh@%273-30N(;aF~%82Al%deW4keLLSoy=;R)7A$L9eeZTCoK% zvbMYVnoNi2j*Chlo~WS&pfqgeu=d%L(z&tnB=R;5&*HagB4y$?$nVHn%WU?)DH#CL*+S}C)JcE~3awB=eeT(c%!Sl=3w60ZvyLUA- zelUi-`}g-o`;rQ=;wV*k#R;SeOig<2FIhW_;musWyxT_%7Fwg}5DG6wx+*la!ng;p zs88J`(QTWbNlEQo`^(|upaBBO`Bm4pC=ySCai5(3{QAKaAfMC}Aq)UAi@7)Sx3NG6 zc^+UOGlviyj_Hvjw>t4=V>Bgk?D$7ezpV_Uga@wBu+5WrB5XYc>q&;0Y#WL2 z4Q7*c1vi2QEnFpp95jtR%0RQ}O0`5B6s?oj@3_8%37OA!F*xlM+$S(o{ zR_wp}Tm8=dYdEevlfu0MybRBzUaWhzm#Nr_{fG`mx_?8!+>kiRPGH|a+Zk}K;$tP} z4mz#-=f%3rdm6U7wt`tZcSp5tKIX8Fg@pwlvUZx7Q4SB=PGq^ve{(URb1gIc{sA&F zbGUN{{3l2^j?1oI6ltM|MMqQQb{!myy_4xRjwFgTp~O6~TS&X4dS#Vijg7h|>K0 zR`^@pf~n(QNFdr5XJ4_6VbVlXr|_-G?kHD>1H+U_UDm!dW0AvJ1Fiu7NfTT@hy$XZ zQz(6Ia$Hd~xZLw3+)YIckrtWXxGk+~lS%lkaNq3wD(H;pF$5k&{LaMLGcQH zjiLS-w@MIRvR{#nT?F)N6pmSN5DID6FtcTDU7GNfd`C)-h>nNOiC@}^zhsegX zT-GQ9+f;~{Y`C2$0W^Rk6SOOnuKr9>p$C9Nx~AnEX_0r+!^y;O^H2v&up0+JH_I0U z73jVg1`I$o;-K3jpdZo;za_Kw7ib$3agTZx6iiFty&3+nWQ!1qLE62rx+u&8*aMhl z>O+69HTcuE&Bnwqh(glWM!;;bjCxuq*nUNY7amlc5LUEi&~fy3c_Q(qQkfFqqgdgbLT5 zNT5#_SpgTM5aToj8SWk&CMiT{8ax9Dc4FFLUQi?xOo`u*F~QyOGVrrN4|NUzib!sf z?mEB{66zv@f-oYX4E(Tc5h%K@2L6vopgeK*2x)`v2)x(5Lv$kiK2wwc9ZvuF$5f)w z;xr%&KuxbgTLUf+v^G;95~e9FW`PL&AjZ_pPZPM5xHGH@znagYfcwqhr?kKq#P$eMw` z_-rg1odT?Z_+J^rHYVv3;wNAlOV=%OzQm&=Bc@3ag{p>E{|$azX(^OVAY%U;ROmKD zM-jUGaX<%#0U^3ebCwFgJ}}Qza_(jvsmKyC{L3tf*a~r8+YgGQnI{0W9N=oD)&fVP zec>9c*eNJHJQ$E-NjQQG1eyi~i7(q{D+-$p)m9Zri<~kpGNPl6JIs$iN`y{}Qgd$- z`w(?FnFShlj<|%IWVHL-OM*wt2>*zrJgKfon&WyP zNh~1)>W~vL0zy2raqQ&3$B-Gs83#4mL|1&Mj)WE#OE7GS3TXh;;9Tk8t5*R!(JIJ` z>M-B`BFj5CRA_iIFj|47NdRgSdv1Dpahu$=IgLJ94e=pEtMElcZz`BAmpM+ck^lR zI~mX?qq@PHkTxg1V8^NBm zVY`qH8MrmG0ghL<<02`H`07$jhi-@aP8or%UBXHebfY12Fn7>c>qwAR$XK_+j;?~Q zGJz#DFi`;YJx!t{90>lm0Ue-sp+vV;>E4(SV%mL{Z^Si%f+a-Nox&W%fJm;u0k}&I++2cnvc{zx))5`T+31Gi zg$x1!g+Hp1V3i1G2G%H{1;z@!CI&)uI2~620W4PwZ) z+qpOXhM+;Cjfs|0T8d9(9;0 z`~b|eE$w!$1@Ru(9@c6aO00)GMBX=}M|l!-eS=uGA6)W*#G6DT1koh%>s@$G-6eZ* z^1)_84-nGOcAJI>MF4!huc5(Py^_=f5N@k-+} z9V6@PfEVE7!F5UmJBA?``6?n9uwEKQz^sre1YXDm9pI13C#*+K7YARLpOMRi5+D*o zkwIYYurDki2@4p3DUoBssR6MlP5MWKD73m#fjc0_c-Tpauqv*oDI-9+^`5^U&LI!e ztr_l^1iZ&UGjPyIiJBn7Z39|CN90#V;f1tq1fi!+Wj$absZvBOu4(`W3#6~ep~2#y zu_=I|I!c#B#duC~9XK&v^f_&t?*a|G*kPgas9YW*ES}7Q^@R|I$w?#1*q&IIpH6Jr zRFWtnRvk~oi`{KM$RTJYF1nrW7~3xsq=}ZGJ10#<67;c;kg?-2Hg#Jj%6E$H(*V45 zO5rtoAeMjzxj8xo6k{BY#c4I<_r(o)2#oVIC@TTC?l62v6iyl@T&?O)Ov!PtwND5&Xj+L%X1~`c+Bh&pb73^^! zZ6A{UL;=XSJ237`M1BLfnLQKa9RW!wodlg1ZI7!Pz+scykeGHg27;YYpgRfp2nQXa z5#VBbP(^@EOu~5=9Q~8166F5#>b|VMG)aF&zFmXL2ZloOFQ%6odm&eHxj)sAHw(9Sf|1TC(55fQd diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..bf66555a702550d873638e2f8cb0d3b8f474414f GIT binary patch literal 3484 zcmeAS@N?(olHy`uVBq!ia0y~yU~XYxV2a>i1BzH3)wm55Pxf?i45^s&_U1v(1_uEa zNB=YSPffWZ7p_*Ck#C%y_Kb;vLE-s5IiQ9Ee2yFp3<9bgj0_BmSQHr;9HuC+Ffeoo z0nKCxY!Uz}^b!Cncj0tmU}(@7RXiFRqiJI_r;HW_qXpw=H8EOgj d.isDirectory()); for (const demo of demos) { const key = `${widget.name}/${demo.name}`; - if (SKIPPED_DEMOS.has(`${key}/${FRAMEWORK}`)) continue; // eslint-disable-line no-continue const fwDir = path.join(widgetDir, demo.name, FRAMEWORK); const matchesFilter = !FILTER || key.includes(FILTER); if (matchesFilter && fs.existsSync(path.join(fwDir, 'index.html'))) { diff --git a/apps/demos/utils/server/csp-check.js b/apps/demos/utils/server/csp-check.js index b3a8afdfac4a..0e19d27275fa 100644 --- a/apps/demos/utils/server/csp-check.js +++ b/apps/demos/utils/server/csp-check.js @@ -176,11 +176,6 @@ function applyShard(demos) { return applyShardGeneric(demos, (d) => `${d.widget}/${d.demo}`, { SHARD_TOTAL, SHARD_INDEX }); } -// TODO: remove once PivotGrid/StandaloneFieldChooser/Angular renders reliably (times out here) -const SKIPPED_DEMOS = new Set([ - 'PivotGrid/StandaloneFieldChooser/Angular', -]); - function findDemos() { const demosDirName = 'Demos'; const demosDir = join(DEMO_ROOT, demosDirName); @@ -202,7 +197,6 @@ function findDemos() { .filter((d) => d.isDirectory()); for (const demo of demos) { - if (SKIPPED_DEMOS.has(`${widget.name}/${demo.name}/${FRAMEWORK}`)) continue; // eslint-disable-line no-continue const fwDir = join(widgetDir, demo.name, FRAMEWORK); if (existsSync(join(fwDir, 'index.html'))) { result.push({ From 63b1929390fe8b6dca14880936beaf8468edc104 Mon Sep 17 00:00:00 2001 From: Andrei Kharitonov Date: Fri, 4 Sep 2026 09:34:47 +0200 Subject: [PATCH 15/45] Core(Data): remove m_ prefix from files --- .../js/__internal/data/{m_array_utils.ts => array_utils.ts} | 0 .../{m_operation_manager.ts => operation_manager.ts} | 0 .../js/__internal/data/data_source/{m_utils.ts => utils.ts} | 0 .../data/{m_endpoint_selector.ts => endpoint_selector.ts} | 0 .../devextreme/js/__internal/data/{m_errors.ts => errors.ts} | 0 .../js/__internal/data/{m_local_store.ts => local_store.ts} | 0 packages/devextreme/js/__internal/data/odata/context.ts | 2 +- .../data/odata/{m_query_adapter.ts => query_adapter.ts} | 4 ++-- .../odata/{m_request_dispatcher.ts => request_dispatcher.ts} | 0 .../devextreme/js/__internal/data/{m_query.ts => query.ts} | 0 .../devextreme/js/__internal/data/query_implementation.ts | 2 +- .../js/__internal/data/{m_remote_query.ts => remote_query.ts} | 0 .../js/__internal/data/{m_store_helper.ts => store_helper.ts} | 0 .../devextreme/js/__internal/data/{m_utils.ts => utils.ts} | 0 .../grids/new/grid_core/data_controller/public_methods.ts | 2 +- .../__internal/grids/new/grid_core/data_controller/utils.ts | 2 +- .../grid_core/filtering/header_filter/legacy_header_filter.ts | 2 +- .../devextreme/js/__internal/scheduler/utils/loader/utils.ts | 2 +- packages/devextreme/js/common/data/array_utils.js | 2 +- .../js/common/data/data_source/operation_manager.js | 2 +- packages/devextreme/js/common/data/data_source/utils.js | 2 +- packages/devextreme/js/common/data/endpoint_selector.js | 2 +- packages/devextreme/js/common/data/errors.js | 2 +- packages/devextreme/js/common/data/local_store.js | 2 +- packages/devextreme/js/common/data/odata/query_adapter.js | 2 +- .../devextreme/js/common/data/odata/request_dispatcher.js | 2 +- packages/devextreme/js/common/data/query.js | 2 +- packages/devextreme/js/common/data/remote_query.js | 2 +- packages/devextreme/js/common/data/store_helper.js | 2 +- packages/devextreme/js/common/data/utils.js | 2 +- 30 files changed, 20 insertions(+), 20 deletions(-) rename packages/devextreme/js/__internal/data/{m_array_utils.ts => array_utils.ts} (100%) rename packages/devextreme/js/__internal/data/data_source/{m_operation_manager.ts => operation_manager.ts} (100%) rename packages/devextreme/js/__internal/data/data_source/{m_utils.ts => utils.ts} (100%) rename packages/devextreme/js/__internal/data/{m_endpoint_selector.ts => endpoint_selector.ts} (100%) rename packages/devextreme/js/__internal/data/{m_errors.ts => errors.ts} (100%) rename packages/devextreme/js/__internal/data/{m_local_store.ts => local_store.ts} (100%) rename packages/devextreme/js/__internal/data/odata/{m_query_adapter.ts => query_adapter.ts} (99%) rename packages/devextreme/js/__internal/data/odata/{m_request_dispatcher.ts => request_dispatcher.ts} (100%) rename packages/devextreme/js/__internal/data/{m_query.ts => query.ts} (100%) rename packages/devextreme/js/__internal/data/{m_remote_query.ts => remote_query.ts} (100%) rename packages/devextreme/js/__internal/data/{m_store_helper.ts => store_helper.ts} (100%) rename packages/devextreme/js/__internal/data/{m_utils.ts => utils.ts} (100%) diff --git a/packages/devextreme/js/__internal/data/m_array_utils.ts b/packages/devextreme/js/__internal/data/array_utils.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_array_utils.ts rename to packages/devextreme/js/__internal/data/array_utils.ts diff --git a/packages/devextreme/js/__internal/data/data_source/m_operation_manager.ts b/packages/devextreme/js/__internal/data/data_source/operation_manager.ts similarity index 100% rename from packages/devextreme/js/__internal/data/data_source/m_operation_manager.ts rename to packages/devextreme/js/__internal/data/data_source/operation_manager.ts diff --git a/packages/devextreme/js/__internal/data/data_source/m_utils.ts b/packages/devextreme/js/__internal/data/data_source/utils.ts similarity index 100% rename from packages/devextreme/js/__internal/data/data_source/m_utils.ts rename to packages/devextreme/js/__internal/data/data_source/utils.ts diff --git a/packages/devextreme/js/__internal/data/m_endpoint_selector.ts b/packages/devextreme/js/__internal/data/endpoint_selector.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_endpoint_selector.ts rename to packages/devextreme/js/__internal/data/endpoint_selector.ts diff --git a/packages/devextreme/js/__internal/data/m_errors.ts b/packages/devextreme/js/__internal/data/errors.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_errors.ts rename to packages/devextreme/js/__internal/data/errors.ts diff --git a/packages/devextreme/js/__internal/data/m_local_store.ts b/packages/devextreme/js/__internal/data/local_store.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_local_store.ts rename to packages/devextreme/js/__internal/data/local_store.ts diff --git a/packages/devextreme/js/__internal/data/odata/context.ts b/packages/devextreme/js/__internal/data/odata/context.ts index 6e070b4c937c..dbd3dadbde42 100644 --- a/packages/devextreme/js/__internal/data/odata/context.ts +++ b/packages/devextreme/js/__internal/data/odata/context.ts @@ -9,7 +9,7 @@ import { each } from '@js/core/utils/iterator'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import type { StoreErrorHandler } from '@ts/data/abstract_store'; -import { errors, handleError } from '../m_errors'; +import { errors, handleError } from '../errors'; import { escapeServiceOperationParams, formatFunctionInvocationUrl } from './utils'; export type ServiceOperationParams = Record; diff --git a/packages/devextreme/js/__internal/data/odata/m_query_adapter.ts b/packages/devextreme/js/__internal/data/odata/query_adapter.ts similarity index 99% rename from packages/devextreme/js/__internal/data/odata/m_query_adapter.ts rename to packages/devextreme/js/__internal/data/odata/query_adapter.ts index 6205484aa0a9..3adfa9edc6f9 100644 --- a/packages/devextreme/js/__internal/data/odata/m_query_adapter.ts +++ b/packages/devextreme/js/__internal/data/odata/query_adapter.ts @@ -4,12 +4,12 @@ import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { isFunction } from '@js/core/utils/type'; -import { errors } from '../m_errors'; +import { errors } from '../errors'; import { isConjunctiveOperator, isUnaryOperation, normalizeBinaryCriterion, -} from '../m_utils'; +} from '../utils'; import { convertPrimitiveValue, generateExpand, diff --git a/packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts b/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts similarity index 100% rename from packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts rename to packages/devextreme/js/__internal/data/odata/request_dispatcher.ts diff --git a/packages/devextreme/js/__internal/data/m_query.ts b/packages/devextreme/js/__internal/data/query.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_query.ts rename to packages/devextreme/js/__internal/data/query.ts diff --git a/packages/devextreme/js/__internal/data/query_implementation.ts b/packages/devextreme/js/__internal/data/query_implementation.ts index 2654903ad637..d7185d49d29e 100644 --- a/packages/devextreme/js/__internal/data/query_implementation.ts +++ b/packages/devextreme/js/__internal/data/query_implementation.ts @@ -1,5 +1,5 @@ import arrayQueryImpl from './array_query'; -import remoteQueryImpl from './m_remote_query'; +import remoteQueryImpl from './remote_query'; export const queryImpl = { array: arrayQueryImpl, diff --git a/packages/devextreme/js/__internal/data/m_remote_query.ts b/packages/devextreme/js/__internal/data/remote_query.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_remote_query.ts rename to packages/devextreme/js/__internal/data/remote_query.ts diff --git a/packages/devextreme/js/__internal/data/m_store_helper.ts b/packages/devextreme/js/__internal/data/store_helper.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_store_helper.ts rename to packages/devextreme/js/__internal/data/store_helper.ts diff --git a/packages/devextreme/js/__internal/data/m_utils.ts b/packages/devextreme/js/__internal/data/utils.ts similarity index 100% rename from packages/devextreme/js/__internal/data/m_utils.ts rename to packages/devextreme/js/__internal/data/utils.ts diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts index 4d43b738af41..b1e2ccb921c4 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import type { FilterDescriptor } from '@js/data'; import type DataSource from '@js/data/data_source'; -import { keysEqual } from '@ts/data/m_utils'; +import { keysEqual } from '@ts/data/utils'; import type { Constructor } from '../types'; import type { GridCoreNewBase } from '../widget'; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts index ac96d84c87ac..b03222a5303b 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts @@ -6,7 +6,7 @@ import type { Store } from '@js/data'; import type { DataSourceLike } from '@js/data/data_source'; import DataSource from '@js/data/data_source'; import { normalizeDataSourceOptions } from '@js/data/data_source/utils'; -import { applyBatch } from '@ts/data/m_array_utils'; +import { applyBatch } from '@ts/data/array_utils'; import type { DataObject, diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts index 76ee06aa32f8..761f198bce92 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts @@ -16,7 +16,7 @@ import { isDefined, isFunction, isObject } from '@js/core/utils/type'; import messageLocalization from '@js/localization/message'; import filteringUtils from '@js/ui/shared/filtering'; import { extend } from '@ts/core/utils/m_extend'; -import { normalizeDataSourceOptions as oldNormalizeDataSourceOptions } from '@ts/data/data_source/m_utils'; +import { normalizeDataSourceOptions as oldNormalizeDataSourceOptions } from '@ts/data/data_source/utils'; import { convertDataFromUTCToLocal, getFormatOptions, diff --git a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts index ccf48a23701c..8a2f31585a13 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts @@ -1,6 +1,6 @@ import type { DataSourceLike } from '@js/data/data_source'; import DataSource from '@js/data/data_source'; -import { normalizeDataSourceOptions } from '@ts/data/data_source/m_utils'; +import { normalizeDataSourceOptions } from '@ts/data/data_source/utils'; export const normalizeDataSource = ( dataSourceOptions: DataSourceLike | null | undefined, diff --git a/packages/devextreme/js/common/data/array_utils.js b/packages/devextreme/js/common/data/array_utils.js index 74993adf03d4..d3f8e91646bc 100644 --- a/packages/devextreme/js/common/data/array_utils.js +++ b/packages/devextreme/js/common/data/array_utils.js @@ -1 +1 @@ -export * from '../../__internal/data/m_array_utils'; +export * from '../../__internal/data/array_utils'; diff --git a/packages/devextreme/js/common/data/data_source/operation_manager.js b/packages/devextreme/js/common/data/data_source/operation_manager.js index 059f55b8e65f..6de10df1d202 100644 --- a/packages/devextreme/js/common/data/data_source/operation_manager.js +++ b/packages/devextreme/js/common/data/data_source/operation_manager.js @@ -1 +1 @@ -export { default } from '../../../__internal/data/data_source/m_operation_manager'; +export { default } from '../../../__internal/data/data_source/operation_manager'; diff --git a/packages/devextreme/js/common/data/data_source/utils.js b/packages/devextreme/js/common/data/data_source/utils.js index 8b2686832ca6..52e070da838e 100644 --- a/packages/devextreme/js/common/data/data_source/utils.js +++ b/packages/devextreme/js/common/data/data_source/utils.js @@ -1 +1 @@ -export * from '../../../__internal/data/data_source/m_utils'; +export * from '../../../__internal/data/data_source/utils'; diff --git a/packages/devextreme/js/common/data/endpoint_selector.js b/packages/devextreme/js/common/data/endpoint_selector.js index e613c011c0ce..ea7b56fad2d1 100644 --- a/packages/devextreme/js/common/data/endpoint_selector.js +++ b/packages/devextreme/js/common/data/endpoint_selector.js @@ -4,4 +4,4 @@ * @param1 options:Object * @hidden */ -export { default } from '../../__internal/data/m_endpoint_selector'; +export { default } from '../../__internal/data/endpoint_selector'; diff --git a/packages/devextreme/js/common/data/errors.js b/packages/devextreme/js/common/data/errors.js index f2ad9f675151..255b551e3774 100644 --- a/packages/devextreme/js/common/data/errors.js +++ b/packages/devextreme/js/common/data/errors.js @@ -88,4 +88,4 @@ * @name ErrorsData.W4002 */ -export * from '../../__internal/data/m_errors'; +export * from '../../__internal/data/errors'; diff --git a/packages/devextreme/js/common/data/local_store.js b/packages/devextreme/js/common/data/local_store.js index 2b903d32d98b..48ec3d1bcc87 100644 --- a/packages/devextreme/js/common/data/local_store.js +++ b/packages/devextreme/js/common/data/local_store.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_local_store'; +export { default } from '../../__internal/data/local_store'; diff --git a/packages/devextreme/js/common/data/odata/query_adapter.js b/packages/devextreme/js/common/data/odata/query_adapter.js index a56104665e6c..dc61288953ee 100644 --- a/packages/devextreme/js/common/data/odata/query_adapter.js +++ b/packages/devextreme/js/common/data/odata/query_adapter.js @@ -1 +1 @@ -export * from '../../../__internal/data/odata/m_query_adapter'; +export * from '../../../__internal/data/odata/query_adapter'; diff --git a/packages/devextreme/js/common/data/odata/request_dispatcher.js b/packages/devextreme/js/common/data/odata/request_dispatcher.js index 13fc3dc1b4c6..434e13d5a8eb 100644 --- a/packages/devextreme/js/common/data/odata/request_dispatcher.js +++ b/packages/devextreme/js/common/data/odata/request_dispatcher.js @@ -1 +1 @@ -export { default } from '../../../__internal/data/odata/m_request_dispatcher'; +export { default } from '../../../__internal/data/odata/request_dispatcher'; diff --git a/packages/devextreme/js/common/data/query.js b/packages/devextreme/js/common/data/query.js index b1e2c444e038..bbb50289ac52 100644 --- a/packages/devextreme/js/common/data/query.js +++ b/packages/devextreme/js/common/data/query.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_query'; +export { default } from '../../__internal/data/query'; diff --git a/packages/devextreme/js/common/data/remote_query.js b/packages/devextreme/js/common/data/remote_query.js index 592ebfe65d2c..51f3df760e1e 100644 --- a/packages/devextreme/js/common/data/remote_query.js +++ b/packages/devextreme/js/common/data/remote_query.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_remote_query'; +export { default } from '../../__internal/data/remote_query'; diff --git a/packages/devextreme/js/common/data/store_helper.js b/packages/devextreme/js/common/data/store_helper.js index bb0fca23c7d1..5b66fa3425b1 100644 --- a/packages/devextreme/js/common/data/store_helper.js +++ b/packages/devextreme/js/common/data/store_helper.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_store_helper'; +export { default } from '../../__internal/data/store_helper'; diff --git a/packages/devextreme/js/common/data/utils.js b/packages/devextreme/js/common/data/utils.js index a59212919375..ba1bc28b4c72 100644 --- a/packages/devextreme/js/common/data/utils.js +++ b/packages/devextreme/js/common/data/utils.js @@ -3,7 +3,7 @@ import { compileGetter, compileSetter } from '../../core/utils/data'; /** * @name Utils */ -export * from '../../__internal/data/m_utils'; +export * from '../../__internal/data/utils'; export { compileGetter, From b88332b4ff107e18c6b3b5e6199928adb3f2dd0b Mon Sep 17 00:00:00 2001 From: Andrei Kharitonov Date: Fri, 4 Sep 2026 11:50:14 +0200 Subject: [PATCH 16/45] Core(Data): fix typing and eslint issues --- .../js/__internal/data/abstract_store.ts | 2 + .../js/__internal/data/array_store.ts | 6 - .../js/__internal/data/array_utils.ts | 379 ++++++++++++------ .../js/__internal/data/custom_store.ts | 5 +- .../data/data_controller/data_controller.ts | 1 - .../data/data_source/data_source.ts | 6 +- .../data/data_source/operation_manager.ts | 31 +- .../js/__internal/data/data_source/utils.ts | 183 ++++++--- .../js/__internal/data/endpoint_selector.ts | 38 +- .../devextreme/js/__internal/data/errors.ts | 18 +- .../js/__internal/data/local_store.ts | 56 ++- .../js/__internal/data/odata/query_adapter.ts | 328 +++++++-------- .../data/odata/request_dispatcher.ts | 83 ++-- .../js/__internal/data/odata/store.ts | 9 +- .../devextreme/js/__internal/data/query.ts | 17 +- .../js/__internal/data/remote_query.ts | 240 +++++++---- .../js/__internal/data/store_helper.ts | 135 +++++-- .../devextreme/js/__internal/data/utils.ts | 286 +++++++------ .../grids/data_grid/grouping/m_grouping.ts | 1 + .../data_grid/grouping/m_grouping_expanded.ts | 1 - .../grids/data_grid/summary/m_summary.ts | 2 - .../m_columns_controller.ts | 1 - .../m_data_source_adapter.ts | 3 - .../header_filter/m_header_filter.ts | 4 +- .../js/__internal/grids/grid_core/m_utils.ts | 1 - .../grids/grid_core/search/m_search.ts | 2 - .../new/grid_core/data_controller/utils.ts | 2 +- .../header_filter/legacy_header_filter.ts | 1 - .../pivot_grid/data_source/m_data_source.ts | 1 - .../pivot_grid/local_store/m_local_store.ts | 1 - .../pivot_grid/remote_store/m_remote_store.ts | 1 - .../m_data_source_adapter.ts | 5 - .../scheduler/utils/loader/utils.ts | 1 + .../ui/collection/collection_widget.edit.ts | 1 - .../collection_widget.live_update.ts | 2 - .../hierarchical_collection/data_adapter.ts | 2 +- .../selection/selection.strategy.deferred.ts | 1 - .../selection/selection.strategy.standard.ts | 4 +- .../ui/selection/selection.strategy.ts | 1 - .../ui/shared/ui.editor_factory_mixin.ts | 1 - 40 files changed, 1120 insertions(+), 742 deletions(-) diff --git a/packages/devextreme/js/__internal/data/abstract_store.ts b/packages/devextreme/js/__internal/data/abstract_store.ts index 8987913c2ed7..21bfe4fd6764 100644 --- a/packages/devextreme/js/__internal/data/abstract_store.ts +++ b/packages/devextreme/js/__internal/data/abstract_store.ts @@ -156,6 +156,7 @@ class Store { loadOptions._langParams = { ...this._langParams, ...loadOptions._langParams }; } + // @ts-expect-error public Query.enumerate() is a Promise, the query resolves a Deferred const result: DeferredObj = queryByOptions( this.createQuery(loadOptions), loadOptions, @@ -193,6 +194,7 @@ class Store { } _totalCountImpl(options?: StoreLoadOptions): DeferredObj { + // @ts-expect-error public Query.count() is a Promise, the query resolves a Deferred const result: DeferredObj = queryByOptions( this.createQuery(options), options, diff --git a/packages/devextreme/js/__internal/data/array_store.ts b/packages/devextreme/js/__internal/data/array_store.ts index 7ae9ef524ffc..814e5a272bb9 100644 --- a/packages/devextreme/js/__internal/data/array_store.ts +++ b/packages/devextreme/js/__internal/data/array_store.ts @@ -47,27 +47,23 @@ class ArrayStore extends Store { const index = indexByKey(this, this._array, key); if (index === -1) { - // @ts-expect-error data/utils is untyped: `rejectedPromise` reads its arguments object const rejected: DeferredObj = rejectedPromise(errors.Error('E4009')); return rejected; } - // @ts-expect-error data/utils is untyped: `trivialPromise` reads its arguments object const resolved: DeferredObj = trivialPromise(this._array[index]); return resolved; } _insertImpl(values: unknown): DeferredObj { - // @ts-expect-error array_utils is untyped: `insert` declares every argument as required const result: DeferredObj = insert(this, this._array, values); return result; } _pushImpl(changes: StoreChange[]): void { - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this, data: this._array, @@ -76,14 +72,12 @@ class ArrayStore extends Store { } _updateImpl(key: unknown, values: unknown): DeferredObj { - // @ts-expect-error array_utils is untyped: `update` declares every argument as required const result: DeferredObj = update(this, this._array, key, values); return result; } _removeImpl(key: unknown): DeferredObj { - // @ts-expect-error array_utils is untyped: `remove` declares every argument as required const result: DeferredObj = remove(this, this._array, key); return result; diff --git a/packages/devextreme/js/__internal/data/array_utils.ts b/packages/devextreme/js/__internal/data/array_utils.ts index 22d11b7c9c69..2e8dd20a73fd 100644 --- a/packages/devextreme/js/__internal/data/array_utils.ts +++ b/packages/devextreme/js/__internal/data/array_utils.ts @@ -3,58 +3,75 @@ import { keysEqual, rejectedPromise, trivialPromise } from '@js/common/data/util import config from '@js/core/config'; import Guid from '@js/core/guid'; import { compileGetter } from '@js/core/utils/data'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { deepExtendArraySafe } from '@js/core/utils/object'; import { - isDefined, isEmptyObject, isObject, isPlainObject, + isDefined, isEmptyObject, isObject, isPlainObject, isString, } from '@js/core/utils/type'; +import type { StoreChange } from '@js/data/store'; import { isCollectionLike } from '@ts/core/utils/m_object'; +import type { StoreKey } from '@ts/data/abstract_store'; -function hasKey(target, keyOrKeys) { - let key; - // @ts-expect-error - const keys = typeof keyOrKeys === 'string' ? keyOrKeys.split() : keyOrKeys.slice(); +export type KeyExpr = StoreKey | Function; - while (keys.length) { - key = keys.shift(); - if (key in target) { - return true; - } - } +export interface KeyInfo { + /* eslint-disable @typescript-eslint/method-signature-style */ + key(): KeyExpr | undefined; + keyOf(obj: unknown): unknown; + /* eslint-enable @typescript-eslint/method-signature-style */ +} - return false; +interface CachedArray extends Array { + _dataByKeyMap?: Record; + _dataByKeyMapLength?: number; } -function findItems(keyInfo, items, key, groupCount) { - let childItems; - let result; +interface GroupItem { + items?: unknown[]; + collapsedItems?: unknown[]; +} - if (groupCount) { - for (let i = 0; i < items.length; i++) { - childItems = items[i].items || items[i].collapsedItems || []; - result = findItems(keyInfo, childItems || [], key, groupCount - 1); - if (result) { - return result; - } - } - } else if (indexByKey(keyInfo, items, key) >= 0) { - return items; - } +export interface ApplyBatchOptions { + keyInfo: KeyInfo; + data: unknown[]; + changes: StoreChange[]; + groupCount?: number; + useInsertIndex?: boolean; + immutable?: boolean; + disableCache?: boolean; + logError?: boolean; + skipCopying?: boolean; } -function getItems(keyInfo, items, key, groupCount) { - if (groupCount) { - return findItems(keyInfo, items, key, groupCount) || []; +export interface ApplyChangesOptions { + keyExpr?: StoreKey; + immutable?: boolean; +} + +function hasKey(target: unknown, keyOrKeys: KeyExpr): boolean { + let keys: string[] = []; + if (isString(keyOrKeys)) { + keys = [keyOrKeys]; + } else if (Array.isArray(keyOrKeys)) { + keys = keyOrKeys.slice(); } - return items; + while (keys.length) { + const key = keys.shift(); + if (isDefined(key) && isObject(target) && key in target) { + return true; + } + } + + return false; } -function generateDataByKeyMap(keyInfo, array) { +function generateDataByKeyMap(keyInfo: KeyInfo, array: CachedArray): void { if (keyInfo.key() && (!array._dataByKeyMap || array._dataByKeyMapLength !== array.length)) { - const dataByKeyMap = {}; + const dataByKeyMap: Record = {}; const arrayLength = array.length; - for (let i = 0; i < arrayLength; i++) { + for (let i = 0; i < arrayLength; i += 1) { dataByKeyMap[JSON.stringify(keyInfo.keyOf(array[i]))] = array[i]; } @@ -63,13 +80,11 @@ function generateDataByKeyMap(keyInfo, array) { } } -function getCacheValue(array, key) { - if (array._dataByKeyMap) { - return array._dataByKeyMap[JSON.stringify(key)]; - } +function getCacheValue(array: CachedArray, key: unknown): unknown { + return array._dataByKeyMap?.[JSON.stringify(key)]; } -function getHasKeyCacheValue(array, key) { +function getHasKeyCacheValue(array: CachedArray, key: unknown): unknown { if (array._dataByKeyMap) { return array._dataByKeyMap[JSON.stringify(key)]; } @@ -77,39 +92,98 @@ function getHasKeyCacheValue(array, key) { return true; } -function setDataByKeyMapValue(array, key, data) { +function setDataByKeyMapValue(array: CachedArray, key: unknown, data: unknown): void { if (array._dataByKeyMap) { array._dataByKeyMap[JSON.stringify(key)] = data; - array._dataByKeyMapLength += data ? 1 : -1; + array._dataByKeyMapLength = (array._dataByKeyMapLength ?? 0) + (data ? 1 : -1); } } -function cloneInstanceWithChangedPaths(instance, changes, clonedInstances) { +function indexByKey(keyInfo: KeyInfo, array: unknown[], key: unknown): number { + const keyExpr = keyInfo.key(); + + if (!getHasKeyCacheValue(array, key)) { + return -1; + } + + for (let i = 0, arrayLength = array.length; i < arrayLength; i += 1) { + if (keysEqual(keyExpr, keyInfo.keyOf(array[i]), key)) { + return i; + } + } + return -1; +} + +function findItems( + keyInfo: KeyInfo, + items: unknown[], + key: unknown, + groupCount: number, +): unknown[] | undefined { + if (groupCount) { + for (const item of items) { + const group: GroupItem = isObject(item) ? item : {}; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const childItems = group.items || group.collapsedItems || []; + const result = findItems(keyInfo, childItems, key, groupCount - 1); + if (result) { + return result; + } + } + } else if (indexByKey(keyInfo, items, key) >= 0) { + return items; + } + + return undefined; +} + +function getItems( + keyInfo: KeyInfo, + items: unknown[], + key: unknown, + groupCount: number | undefined, +): unknown[] { + if (groupCount) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return findItems(keyInfo, items, key, groupCount) || []; + } + + return items; +} + +function cloneInstanceWithChangedPaths( + instance: unknown, + changes: unknown, + clonedInstances: WeakMap = new WeakMap(), +): unknown { if (isCollectionLike(instance)) { return instance; } - clonedInstances = clonedInstances || new WeakMap(); + const source: object = isObject(instance) ? instance : {}; + const result: object = isObject(instance) + ? Object.create(Object.getPrototypeOf(instance)) + : {}; - const result = instance ? Object.create(Object.getPrototypeOf(instance)) : {}; - if (instance) { + if (isObject(instance)) { clonedInstances.set(instance, result); } - const instanceWithoutPrototype = { ...instance }; + const instanceWithoutPrototype = { ...source }; deepExtendArraySafe(result, instanceWithoutPrototype, true, true, true); // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const name in instanceWithoutPrototype) { - const value = instanceWithoutPrototype[name]; - const change = changes?.[name]; + const value: unknown = instanceWithoutPrototype[name]; + const change: unknown = isObject(changes) ? changes[name] : undefined; - if (isObject(value) && !isPlainObject(value) && isObject(change) && !clonedInstances.has(value)) { + if (isObject(value) && !isPlainObject(value) + && isObject(change) && !clonedInstances.has(value)) { result[name] = cloneInstanceWithChangedPaths(value, change, clonedInstances); } } // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const name in result) { - const prop = result[name]; + const prop: unknown = result[name]; if (isObject(prop) && clonedInstances.has(prop)) { result[name] = clonedInstances.get(prop); @@ -119,59 +193,51 @@ function cloneInstanceWithChangedPaths(instance, changes, clonedInstances) { return result; } -function createObjectWithChanges(target, changes) { - // @ts-expect-error +function createObjectWithChanges( + target: unknown, + changes?: unknown, +): Record { const result = cloneInstanceWithChangedPaths(target, changes); + const extended: Record = deepExtendArraySafe(result, changes, true, true, true); - return deepExtendArraySafe(result, changes, true, true, true); + return extended; } -function applyBatch({ - keyInfo, data, changes, groupCount, useInsertIndex, immutable, disableCache, logError, skipCopying, -}) { - const resultItems = immutable === true ? [...data] : data; - - changes.forEach((item) => { - const items = item.type === 'insert' ? resultItems : getItems(keyInfo, resultItems, item.key, groupCount); +function getErrorResult( + isBatch: boolean | undefined, + logError: boolean | undefined, + errorCode: string, +): DeferredObj | undefined { + if (!isBatch) { + const error: unknown = errors.Error(errorCode); + return rejectedPromise(error); + } - !disableCache && generateDataByKeyMap(keyInfo, items); - // eslint-disable-next-line default-case - switch (item.type) { - case 'update': update(keyInfo, items, item.key, item.data, true, immutable, logError); break; - case 'insert': insert(keyInfo, items, item.data, useInsertIndex && isDefined(item.index) ? item.index : -1, true, logError, skipCopying); break; - case 'remove': remove(keyInfo, items, item.key, true, logError); break; - } - }); - return resultItems; -} + if (logError) { + errors.log(errorCode); + } -function getErrorResult(isBatch, logError, errorCode) { - // @ts-expect-error - return !isBatch ? rejectedPromise(errors.Error(errorCode)) : logError && errors.log(errorCode); + return undefined; } -function applyChanges(data, changes, options = {}) { - // @ts-expect-error - const { keyExpr = 'id', immutable = true } = options; - const keyGetter = compileGetter(keyExpr); - const keyInfo = { - key: () => keyExpr, - // @ts-expect-error - keyOf: (obj) => keyGetter(obj), - }; - // @ts-expect-error - return applyBatch({ - keyInfo, - data, - changes, - immutable, - disableCache: true, - logError: true, - }); -} - -function update(keyInfo, array, key, data, isBatch, immutable, logError) { - let target; +function update( + keyInfo: KeyInfo, array: unknown[], key: unknown, data: unknown, +): DeferredObj; +function update( + keyInfo: KeyInfo, array: unknown[], key: unknown, data: unknown, + isBatch: true, immutable?: boolean, logError?: boolean, +): undefined; +function update( + keyInfo: KeyInfo, + array: unknown[], + key: unknown, + data: unknown, + isBatch?: boolean, + immutable?: boolean, + logError?: boolean, +): DeferredObj | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let target: unknown; const extendComplexObject = true; const keyExpr = keyInfo.key(); @@ -192,8 +258,7 @@ function update(keyInfo, array, key, data, isBatch, immutable, logError) { if (immutable === true && isDefined(target)) { const newTarget = createObjectWithChanges(target, data); array[index] = newTarget; - // @ts-expect-error - return !isBatch && trivialPromise(newTarget, key); + return isBatch ? undefined : trivialPromise(newTarget, key); } } } else { @@ -203,34 +268,56 @@ function update(keyInfo, array, key, data, isBatch, immutable, logError) { deepExtendArraySafe(target, data, extendComplexObject, false, true, true); if (!isBatch) { if (config().useLegacyStoreResult) { - // @ts-expect-error return trivialPromise(key, data); } - // @ts-expect-error return trivialPromise(target, key); } + + return undefined; } -function insert(keyInfo, array, data, index, isBatch, logError, skipCopying) { - let keyValue; +function insert( + keyInfo: KeyInfo, array: unknown[], data: unknown, index?: number, +): DeferredObj; +function insert( + keyInfo: KeyInfo, array: unknown[], data: unknown, index: number | undefined, + isBatch: true, logError?: boolean, skipCopying?: boolean, +): undefined; +function insert( + keyInfo: KeyInfo, + array: unknown[], + data: unknown, + index?: number, + isBatch?: boolean, + logError?: boolean, + skipCopying?: boolean, +): DeferredObj | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let keyValue: unknown; const keyExpr = keyInfo.key(); - const obj = isPlainObject(data) && !skipCopying ? extend({}, data) : data; + const obj: unknown = isPlainObject(data) && !skipCopying ? extend({}, data) : data; if (keyExpr) { keyValue = keyInfo.keyOf(obj); - if (keyValue === undefined || typeof keyValue === 'object' && isEmptyObject(keyValue)) { + if (keyValue === undefined || (typeof keyValue === 'object' && isEmptyObject(keyValue))) { if (Array.isArray(keyExpr)) { throw errors.Error('E4007'); } - keyValue = obj[keyExpr] = String(new Guid()); + + const generatedKey = String(new Guid()); + + if (isString(keyExpr) && isObject(obj)) { + obj[keyExpr] = generatedKey; + } + keyValue = generatedKey; } else if (array[indexByKey(keyInfo, array, keyValue)] !== undefined) { return getErrorResult(isBatch, logError, 'E4008'); } } else { keyValue = obj; } - if (index >= 0) { + if (isDefined(index) && index >= 0) { array.splice(index, 0, obj); } else { array.push(obj); @@ -239,38 +326,94 @@ function insert(keyInfo, array, data, index, isBatch, logError, skipCopying) { setDataByKeyMapValue(array, keyValue, obj); if (!isBatch) { - // @ts-expect-error return trivialPromise(config().useLegacyStoreResult ? data : obj, keyValue); } + + return undefined; } -function remove(keyInfo, array, key, isBatch, logError) { +function remove( + keyInfo: KeyInfo, array: unknown[], key: unknown, +): DeferredObj; +function remove( + keyInfo: KeyInfo, array: unknown[], key: unknown, isBatch: true, logError?: boolean, +): undefined; +function remove( + keyInfo: KeyInfo, + array: unknown[], + key: unknown, + isBatch?: boolean, + logError?: boolean, +): DeferredObj | undefined { const index = indexByKey(keyInfo, array, key); if (index > -1) { array.splice(index, 1); setDataByKeyMapValue(array, key, null); } if (!isBatch) { - // @ts-expect-error return trivialPromise(key); - } if (index < 0) { + } + if (index < 0) { return getErrorResult(isBatch, logError, 'E4009'); } + + return undefined; } -function indexByKey(keyInfo, array, key) { - const keyExpr = keyInfo.key(); +function applyBatch({ + keyInfo, data, changes, groupCount, useInsertIndex, immutable, + disableCache, logError, skipCopying, +}: ApplyBatchOptions): unknown[] { + const resultItems = immutable === true ? [...data] : data; - if (!getHasKeyCacheValue(array, key)) { - return -1; - } + changes.forEach((item) => { + const items = item.type === 'insert' + ? resultItems + : getItems(keyInfo, resultItems, item.key, groupCount); - for (let i = 0, arrayLength = array.length; i < arrayLength; i++) { - if (keysEqual(keyExpr, keyInfo.keyOf(array[i]), key)) { - return i; + if (!disableCache) { + generateDataByKeyMap(keyInfo, items); } - } - return -1; + + const insertIndex = useInsertIndex && isDefined(item.index) ? item.index : -1; + + // eslint-disable-next-line default-case + switch (item.type) { + case 'update': + update(keyInfo, items, item.key, item.data, true, immutable, logError); + break; + case 'insert': + insert(keyInfo, items, item.data, insertIndex, true, logError, skipCopying); + break; + case 'remove': + remove(keyInfo, items, item.key, true, logError); + break; + } + }); + return resultItems; +} + +function applyChanges( + data: unknown[], + changes: StoreChange[], + options: ApplyChangesOptions = {}, +): unknown[] { + const { keyExpr = 'id', immutable = true } = options; + // @ts-expect-error compileGetter() is declared as (expr: string) => unknown + const keyGetter: Function = compileGetter(keyExpr); + const keyInfo: KeyInfo = { + key: () => keyExpr, + keyOf: (obj: unknown): unknown => keyGetter(obj), + }; + + return applyBatch({ + keyInfo, + data, + changes, + immutable, + disableCache: true, + logError: true, + }); } export { diff --git a/packages/devextreme/js/__internal/data/custom_store.ts b/packages/devextreme/js/__internal/data/custom_store.ts index 90e720d4056c..77d22482a4c7 100644 --- a/packages/devextreme/js/__internal/data/custom_store.ts +++ b/packages/devextreme/js/__internal/data/custom_store.ts @@ -209,7 +209,7 @@ function runRawLoadWithQuery( const rawDataQuery = arrayQuery(rawData, { errorHandler: store._errorHandler }); const waitList: DeferredObj[] = []; - const result: { items?: unknown[]; totalCount?: unknown } = {}; + const result: { items?: unknown; totalCount?: unknown } = {}; if (!countOnly) { const itemsQuery = storeHelper.queryByOptions(rawDataQuery, loadOptions, false); @@ -217,7 +217,7 @@ function runRawLoadWithQuery( result.items = rawData.slice(0); } else { const itemsPromise: DeferredObj = itemsQuery.enumerate() - .done((asyncResult: unknown[]) => { + .done((asyncResult: unknown) => { result.items = asyncResult; }); waitList.push(itemsPromise); @@ -367,7 +367,6 @@ class CustomStore extends Store { _pushImpl(changes: StoreChange[]): void { if (this.__rawData) { - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this, data: this.__rawData, diff --git a/packages/devextreme/js/__internal/data/data_controller/data_controller.ts b/packages/devextreme/js/__internal/data/data_controller/data_controller.ts index 840781fd8302..3b4d5b4bf14f 100644 --- a/packages/devextreme/js/__internal/data/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/data/data_controller/data_controller.ts @@ -65,7 +65,6 @@ class DataController { this._isSharedDataSource = true; this._dataSource = dataSourceOptions; } else { - // @ts-expect-error const normalizedDataSourceOptions = normalizeDataSourceOptions(dataSourceOptions); this._dataSource = new DataSource( extend(true, {}, {}, normalizedDataSourceOptions), diff --git a/packages/devextreme/js/__internal/data/data_source/data_source.ts b/packages/devextreme/js/__internal/data/data_source/data_source.ts index 72aae3df3fab..1f1cc8ee8fec 100644 --- a/packages/devextreme/js/__internal/data/data_source/data_source.ts +++ b/packages/devextreme/js/__internal/data/data_source/data_source.ts @@ -28,6 +28,7 @@ import type { ChangedEvent, DataSourceEventName, EventOptionName, LoadOperation, LoadResult, NormalizedDataSourceOptions, StoreLoadOptions, } from './types'; +import type { Mapper } from './utils'; // Mirrors the coercion the global `isFinite` applies to non-numeric values. const isFiniteValue = (value: unknown): value is number => isFinite(Number(value)); @@ -58,11 +59,11 @@ export class DataSource { _onPushHandler: Function; - _aggregationTimeoutId?: number; + _aggregationTimeoutId?: ReturnType; _storeLoadOptions: StoreLoadOptions; - _mapFunc?: Function; + _mapFunc?: Mapper; _postProcessFunc?: Function; @@ -567,7 +568,6 @@ export class DataSource { dataSourceChanges = changingArgs.postProcessChanges(dataSourceChanges); } - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this.store(), data: items, diff --git a/packages/devextreme/js/__internal/data/data_source/operation_manager.ts b/packages/devextreme/js/__internal/data/data_source/operation_manager.ts index cfde35fffb6f..4cb21cb79b92 100644 --- a/packages/devextreme/js/__internal/data/data_source/operation_manager.ts +++ b/packages/devextreme/js/__internal/data/data_source/operation_manager.ts @@ -1,32 +1,24 @@ import { CANCELED_TOKEN } from '@js/common/data/data_source/utils'; +import type { DeferredObj } from '@js/core/utils/deferred'; export default class OperationManager { - constructor() { - // @ts-expect-error - this._counter = -1; - // @ts-expect-error - this._deferreds = {}; - } + _counter = -1; + + _deferreds: Record> = {}; - add(deferred) { - // @ts-expect-error - this._counter++; - // @ts-expect-error + add(deferred: DeferredObj): number { + this._counter += 1; this._deferreds[this._counter] = deferred; - // @ts-expect-error return this._counter; } - remove(operationId) { - // @ts-expect-error + remove(operationId: number): boolean { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete return delete this._deferreds[operationId]; } - cancel(operationId) { - // @ts-expect-error + cancel(operationId: number): boolean { if (operationId in this._deferreds) { - // @ts-expect-error this._deferreds[operationId].reject(CANCELED_TOKEN); return true; } @@ -34,13 +26,10 @@ export default class OperationManager { return false; } - cancelAll() { - // @ts-expect-error + cancelAll(): void { while (this._counter > -1) { - // @ts-expect-error this.cancel(this._counter); - // @ts-expect-error - this._counter--; + this._counter -= 1; } } } diff --git a/packages/devextreme/js/__internal/data/data_source/utils.ts b/packages/devextreme/js/__internal/data/data_source/utils.ts index 40192c669e20..c6aed87cfcc6 100644 --- a/packages/devextreme/js/__internal/data/data_source/utils.ts +++ b/packages/devextreme/js/__internal/data/data_source/utils.ts @@ -2,120 +2,173 @@ import ArrayStore from '@js/common/data/array_store'; import { CustomStore } from '@js/common/data/custom_store'; import { normalizeSortingInfo } from '@js/common/data/utils'; import ajaxUtils from '@js/core/utils/ajax'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; -import { each, map } from '@js/core/utils/iterator'; -import { isPlainObject } from '@js/core/utils/type'; -import Store from '@js/data/abstract_store'; +import { isObject, isPlainObject } from '@js/core/utils/type'; +import Store from '@ts/data/abstract_store'; + +import type { NormalizedDataSourceOptions } from './types'; export const CANCELED_TOKEN = 'canceled'; -export const isPending = (deferred) => deferred.state() === 'pending'; +export type Mapper = (item: unknown) => unknown; + +interface GroupItem { + items?: unknown[]; +} + +export interface NormalizationOptions { + fromUrlLoadMode?: string; +} + +interface DataSourceOptionsInput extends Record { + store?: unknown; +} + +export const isPending = (deferred: DeferredObj): boolean => deferred.state() === 'pending'; -export const normalizeStoreLoadOptionAccessorArguments = (originalArguments) => { - // eslint-disable-next-line default-case +export const normalizeStoreLoadOptionAccessorArguments = ( + originalArguments: unknown[], +): unknown => { switch (originalArguments.length) { case 0: return undefined; case 1: return originalArguments[0]; + default: + return originalArguments.slice(); } - return [].slice.call(originalArguments); }; -const mapGroup = (group, level, mapper) => map(group, (item) => { - const { items, ...restItem } = item; - return { - ...restItem, - items: mapRecursive(item.items, level - 1, mapper), - }; -}); - -const mapRecursive = (items, level, mapper) => { +const mapRecursive = (items: unknown, level: number, mapper: Mapper): unknown => { if (!Array.isArray(items)) return items; - return level ? mapGroup(items, level, mapper) : map(items, mapper); + + if (!level) { + return items.map(mapper); + } + + return items.map((item) => { + const groupItem: GroupItem = isObject(item) ? item : {}; + + return { + ...groupItem, + items: mapRecursive(groupItem.items, level - 1, mapper), + }; + }); }; -export const mapDataRespectingGrouping = (items, mapper, groupInfo) => { +export const mapDataRespectingGrouping = ( + items: unknown[], + mapper: Mapper, + groupInfo?: unknown, +): unknown[] => { const level = groupInfo ? normalizeSortingInfo(groupInfo).length : 0; + const mapped = mapRecursive(items, level, mapper); - return mapRecursive(items, level, mapper); + return Array.isArray(mapped) ? mapped : []; }; -export const normalizeLoadResult = (data, extra) => { - if (data?.data) { - extra = data; - data = data.data; - } +export interface NormalizedLoadResult { + data: unknown[]; + extra: unknown; +} - if (!Array.isArray(data)) { - data = [data]; - } +export const normalizeLoadResult = (data: unknown, extra?: unknown): NormalizedLoadResult => { + const loadResult: { data?: unknown } = isObject(data) ? data : {}; + + const resultData: unknown = loadResult.data ? loadResult.data : data; + const resultExtra: unknown = loadResult.data ? data : extra; return { - data, - extra, + data: Array.isArray(resultData) ? resultData : [resultData], + extra: resultExtra, }; }; -const createCustomStoreFromLoadFunc = (options) => { - const storeConfig = {}; +const CUSTOM_STORE_OPTION_NAMES = [ + 'useDefaultSearch', 'key', 'load', 'loadMode', 'cacheRawData', 'byKey', + 'lookup', 'totalCount', 'insert', 'update', 'remove', +]; + +const createCustomStoreFromLoadFunc = (options: DataSourceOptionsInput): CustomStore => { + const storeConfig: Record = {}; - each(['useDefaultSearch', 'key', 'load', 'loadMode', 'cacheRawData', 'byKey', 'lookup', 'totalCount', 'insert', 'update', 'remove'], function () { - storeConfig[this] = options[this]; + CUSTOM_STORE_OPTION_NAMES.forEach((optionName) => { + storeConfig[optionName] = options[optionName]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete options[this]; + delete options[optionName]; }); + return new CustomStore(storeConfig); }; -const createStoreFromConfig = (storeConfig) => { - const alias = storeConfig.type; +const createStoreFromConfig = (storeConfig: Record): Store => { + const alias = String(storeConfig.type); delete storeConfig.type; - // @ts-expect-error - return Store.create(alias, storeConfig); + + const store: Store = Store.create(alias, storeConfig); + + return store; }; -const createCustomStoreFromUrl = (url, normalizationOptions) => new CustomStore({ - load: () => ajaxUtils.sendRequest({ url, dataType: 'json' }), +const createCustomStoreFromUrl = ( + url: string, + normalizationOptions?: NormalizationOptions, +): CustomStore => new CustomStore({ + load: (): unknown => ajaxUtils.sendRequest({ url, dataType: 'json' }), loadMode: normalizationOptions?.fromUrlLoadMode, }); -export const normalizeDataSourceOptions = (options, normalizationOptions) => { - let store; - - if (typeof options === 'string') { - options = { - paginate: false, - store: createCustomStoreFromUrl(options, normalizationOptions), - }; +const resolveStore = (options: DataSourceOptionsInput): Store => { + if ('load' in options) { + return createCustomStoreFromLoadFunc(options); } - if (options === undefined) { - options = []; + const { store } = options; + + if (Array.isArray(store)) { + return new ArrayStore(store); + } + if (isPlainObject(store)) { + return createStoreFromConfig(extend({}, store)); } - if (Array.isArray(options) || options instanceof Store) { - options = { store: options }; - } else { - options = extend({}, options); + // @ts-expect-error the `store` option is user-provided and is not necessarily a Store + const passedThrough: Store = store; + + return passedThrough; +}; + +export const normalizeDataSourceOptions = ( + options: unknown, + normalizationOptions?: NormalizationOptions, +): NormalizedDataSourceOptions => { + let source: unknown = options; + + if (typeof source === 'string') { + source = { + paginate: false, + store: createCustomStoreFromUrl(source, normalizationOptions), + }; } - if (options.store === undefined) { - options.store = []; + if (source === undefined) { + source = []; } - store = options.store; + const normalized: DataSourceOptionsInput = Array.isArray(source) || source instanceof Store + ? { store: source } + : extend({}, source); - if ('load' in options) { - store = createCustomStoreFromLoadFunc(options); - } else if (Array.isArray(store)) { - store = new ArrayStore(store); - } else if (isPlainObject(store)) { - store = createStoreFromConfig(extend({}, store)); + if (normalized.store === undefined) { + normalized.store = []; } - options.store = store; + const store = resolveStore(normalized); - return options; + return { + ...normalized, + store, + }; }; diff --git a/packages/devextreme/js/__internal/data/endpoint_selector.ts b/packages/devextreme/js/__internal/data/endpoint_selector.ts index 22f200d13ddd..7726e93a3db4 100644 --- a/packages/devextreme/js/__internal/data/endpoint_selector.ts +++ b/packages/devextreme/js/__internal/data/endpoint_selector.ts @@ -4,36 +4,44 @@ import { getWindow } from '@js/core/utils/window'; const window = getWindow(); -let IS_WINJS_ORIGIN; -let IS_LOCAL_ORIGIN; +interface Endpoint { + local: string; + production?: string; +} + +type EndpointConfig = Record; + +let isWinJsOrigin = false; +let isLocalOrigin = false; -function isLocalHostName(url) { +function isLocalHostName(url: string): boolean { return /^(localhost$|127\.)/i.test(url); // TODO more precise check for 127.x.x.x IP } -const EndpointSelector = function (config) { - this.config = config; - IS_WINJS_ORIGIN = window.location.protocol === 'ms-appx:'; - IS_LOCAL_ORIGIN = isLocalHostName(window.location.hostname); -}; +class EndpointSelector { + config: EndpointConfig; -EndpointSelector.prototype = { - urlFor(key) { + constructor(config: EndpointConfig) { + this.config = config; + isWinJsOrigin = window.location.protocol === 'ms-appx:'; + isLocalOrigin = isLocalHostName(window.location.hostname); + } + + urlFor(key: string): string { const bag = this.config[key]; if (!bag) { throw errors.Error('E0006'); } if (bag.production) { - // @ts-expect-error - if (IS_WINJS_ORIGIN && !Debug.debuggerEnabled || !IS_WINJS_ORIGIN && !IS_LOCAL_ORIGIN) { + // @ts-expect-error `Debug` is a WinJS global that has no ambient declaration here + if ((isWinJsOrigin && !Debug.debuggerEnabled) || (!isWinJsOrigin && !isLocalOrigin)) { return bag.production; } } return bag.local; - }, - -}; + } +} export default EndpointSelector; diff --git a/packages/devextreme/js/__internal/data/errors.ts b/packages/devextreme/js/__internal/data/errors.ts index 0268bd78af3d..076821979898 100644 --- a/packages/devextreme/js/__internal/data/errors.ts +++ b/packages/devextreme/js/__internal/data/errors.ts @@ -1,5 +1,6 @@ import coreErrors from '@js/core/errors'; import errorUtils from '@js/core/utils/error'; +import { isObject } from '@js/core/utils/type'; export const errors = errorUtils(coreErrors.ERROR_MESSAGES, { @@ -59,15 +60,20 @@ export const errors = errorUtils(coreErrors.ERROR_MESSAGES, { W4002: 'Data loading has failed for some cells due to the following error: {0}', }); + +export type DataErrorHandler = (error: unknown) => void; + // eslint-disable-next-line import/no-mutable-exports -export let errorHandler = null; -export const handleError = function (error) { +export let errorHandler: DataErrorHandler | null = null; + +export const handleError = function (error: unknown): void { /// #DEBUG - const id = error && '__id' in error ? error.__id : 'E4000'; + const id = isObject(error) && '__id' in error ? error.__id : 'E4000'; errors.log(id, error); /// #ENDDEBUG - // @ts-expect-error errorHandler?.(error); }; -// eslint-disable-next-line no-return-assign -export const setErrorHandler = (handler) => errorHandler = handler; + +export const setErrorHandler = (handler: DataErrorHandler | null): void => { + errorHandler = handler; +}; diff --git a/packages/devextreme/js/__internal/data/local_store.ts b/packages/devextreme/js/__internal/data/local_store.ts index 14c956b01cd9..7bb8e4121ab8 100644 --- a/packages/devextreme/js/__internal/data/local_store.ts +++ b/packages/devextreme/js/__internal/data/local_store.ts @@ -3,14 +3,26 @@ import eventsEngine from '@js/common/core/events/core/events_engine'; import ArrayStore from '@js/common/data/array_store'; import { errors } from '@js/common/data/errors'; import domAdapter from '@js/core/dom_adapter'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { getWindow } from '@js/core/utils/window'; +import type { ArrayStoreOptions } from '@ts/data/array_store'; import Store from './abstract_store'; const window = getWindow(); +export interface LocalStoreOptions extends ArrayStoreOptions { + name?: string; + immediate?: boolean; + flushInterval?: number; +} + +interface LocalStoreData { + _array: unknown[]; +} + class LocalStoreBackend { - _store: any; + _store: LocalStoreData; _dirty: boolean; @@ -18,7 +30,7 @@ class LocalStoreBackend { _key: string; - constructor(store, storeOptions) { + constructor(store: LocalStoreData, storeOptions: LocalStoreOptions) { this._store = store; this._dirty = !!storeOptions.data; @@ -31,14 +43,16 @@ class LocalStoreBackend { this.save(); - const immediate = this._immediate = storeOptions.immediate; + const immediate = storeOptions.immediate ?? false; + this._immediate = immediate; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const flushInterval = Math.max(100, storeOptions.flushInterval || 10 * 1000); if (!immediate) { const saveProxy = this.save.bind(this); setInterval(saveProxy, flushInterval); eventsEngine.on(window, 'beforeunload', saveProxy); - // @ts-expect-error + // @ts-expect-error `cordova` is injected by the Cordova container, `Window` has no such field if (window.cordova) { domAdapter.listen(domAdapter.getDocument(), 'pause', saveProxy, false); } @@ -66,16 +80,17 @@ class LocalStoreBackend { this._dirty = false; } - _loadImpl(): any { + _loadImpl(): unknown[] { const raw = window.localStorage.getItem(this._key); if (raw) { - return JSON.parse(raw); + const stored: unknown = JSON.parse(raw); + return Array.isArray(stored) ? stored : []; } return []; } - _saveImpl(array): void { + _saveImpl(array: unknown[]): void { if (!array.length) { window.localStorage.removeItem(this._key); } else { @@ -83,22 +98,21 @@ class LocalStoreBackend { } } } + class LocalStore extends ArrayStore { _backend: LocalStoreBackend; - _array: any; - - constructor(options) { - if (typeof options === 'string') { - options = { name: options }; - } else { - options = options || {}; - } + constructor(options?: LocalStoreOptions | string) { + const storeOptions: LocalStoreOptions = typeof options === 'string' + ? { name: options } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + : options || {}; - super(options); - this._array = options.data || []; + super(storeOptions); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this._array = storeOptions.data || []; - this._backend = new LocalStoreBackend(this, options); + this._backend = new LocalStoreBackend(this, storeOptions); this._backend.load(); } @@ -111,17 +125,17 @@ class LocalStore extends ArrayStore { this._backend.notifyChanged(); } - _insertImpl(values): any { + _insertImpl(values: unknown): DeferredObj { const b = this._backend; return super._insertImpl(values).done(b.notifyChanged.bind(b)); } - _updateImpl(key, values): any { + _updateImpl(key: unknown, values: unknown): DeferredObj { const b = this._backend; return super._updateImpl(key, values).done(b.notifyChanged.bind(b)); } - _removeImpl(key): any { + _removeImpl(key: unknown): DeferredObj { const b = this._backend; return super._removeImpl(key).done(b.notifyChanged.bind(b)); } diff --git a/packages/devextreme/js/__internal/data/odata/query_adapter.ts b/packages/devextreme/js/__internal/data/odata/query_adapter.ts index 3adfa9edc6f9..932de8a1bef3 100644 --- a/packages/devextreme/js/__internal/data/odata/query_adapter.ts +++ b/packages/devextreme/js/__internal/data/odata/query_adapter.ts @@ -1,8 +1,9 @@ import queryAdapters from '@js/common/data/query_adapters'; import config from '@js/core/config'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; -import { isFunction } from '@js/core/utils/type'; +import { isFunction, isString } from '@js/core/utils/type'; +import type { QueryAdapter, RemoteQueryOptions, RemoteTask } from '@ts/data/remote_query'; import { errors } from '../errors'; import { @@ -12,6 +13,7 @@ import { } from '../utils'; import { convertPrimitiveValue, + EdmLiteral, generateExpand, generateSelect, sendRequest, @@ -22,96 +24,112 @@ import { const DEFAULT_PROTOCOL_VERSION = 4; const STRING_FUNCTIONS = ['contains', 'notcontains', 'startswith', 'endswith']; +type Formatter = (prop: string, val: string) => string; + +type FieldTypes = Record; + +interface ODataRequestParams { + [param: string]: unknown; + $orderby?: string; + $skip?: number; + $top?: number; + $select?: string; + $expand?: string; + $filter?: string; + // eslint-disable-next-line spellcheck/spell-checker + $inlinecount?: string; + $count?: string; +} + const compileCriteria = (() => { - let protocolVersion; - let forceLowerCase; - let fieldTypes; + // eslint-disable-next-line @typescript-eslint/init-declarations + let protocolVersion: number; + // eslint-disable-next-line @typescript-eslint/init-declarations + let forceLowerCase: boolean | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let fieldTypes: FieldTypes | undefined; - const createBinaryOperationFormatter = (op) => (prop, val) => `${prop} ${op} ${val}`; + const createBinaryOperationFormatter = (op: string): Formatter => (prop, val) => `${prop} ${op} ${val}`; - const createStringFuncFormatter = (op, reverse) => (prop, val) => { + const createStringFuncFormatter = (op: string, reverse?: boolean): Formatter => (prop, val) => { const bag = [op, '(']; - - if (forceLowerCase) { - prop = prop.indexOf('tolower(') === -1 ? `tolower(${prop})` : prop; - val = val.toLowerCase(); - } + const propName = forceLowerCase && !prop.includes('tolower(') ? `tolower(${prop})` : prop; + const value = forceLowerCase ? val.toLowerCase() : val; if (reverse) { - bag.push(val, ',', prop); + bag.push(value, ',', propName); } else { - bag.push(prop, ',', val); + bag.push(propName, ',', value); } bag.push(')'); return bag.join(''); }; - const isStringFunction = function (name) { + const isStringFunction = function (name: string): boolean { return STRING_FUNCTIONS.some((funcName) => funcName === name); }; - const formatters = { + const formatters: Record = { '=': createBinaryOperationFormatter('eq'), '<>': createBinaryOperationFormatter('ne'), '>': createBinaryOperationFormatter('gt'), '>=': createBinaryOperationFormatter('ge'), '<': createBinaryOperationFormatter('lt'), '<=': createBinaryOperationFormatter('le'), - // @ts-expect-error startswith: createStringFuncFormatter('startswith'), - // @ts-expect-error endswith: createStringFuncFormatter('endswith'), }; - const formattersV2 = extend({}, formatters, { - /* eslint-disable spellcheck/spell-checker */ + /* eslint-disable spellcheck/spell-checker */ + const formattersV2: Record = { + ...formatters, contains: createStringFuncFormatter('substringof', true), notcontains: createStringFuncFormatter('not substringof', true), - }); + }; + /* eslint-enable spellcheck/spell-checker */ - const formattersV4 = extend({}, formatters, { - // @ts-expect-error + /* eslint-disable spellcheck/spell-checker */ + const formattersV4: Record = { + ...formatters, contains: createStringFuncFormatter('contains'), - // @ts-expect-error notcontains: createStringFuncFormatter('not contains'), - }); + }; + /* eslint-enable spellcheck/spell-checker */ - const compileBinary = (criteria) => { - criteria = normalizeBinaryCriterion(criteria); + const compileBinary = (criteria: unknown[]): string => { + const crit = normalizeBinaryCriterion(criteria); - const op = criteria[1]; - const fieldName = criteria[0]; - const fieldType = fieldTypes && fieldTypes[fieldName]; + const [rawFieldName, op] = crit; + const fieldName = isString(rawFieldName) || rawFieldName instanceof EdmLiteral + ? rawFieldName + : String(rawFieldName); + const fieldType = fieldTypes?.[String(fieldName)]; if (fieldType && isStringFunction(op) && fieldType !== 'String') { - // @ts-expect-error - throw new errors.Error('E4024', op, fieldName, fieldType); + throw errors.Error('E4024', op, fieldName, fieldType); } - const formatters = protocolVersion === 4 + const criterionFormatters = protocolVersion === 4 ? formattersV4 : formattersV2; - const formatter = formatters[op.toLowerCase()]; + const formatter = criterionFormatters[op.toLowerCase()]; if (!formatter) { throw errors.Error('E4003', op); } - let value = criteria[2]; - - if (fieldTypes?.[fieldName]) { - value = convertPrimitiveValue(fieldTypes[fieldName], value); - } + const value = fieldType ? convertPrimitiveValue(fieldType, crit[2]) : crit[2]; return formatter( serializePropName(fieldName), - serializeValue(value, protocolVersion, fieldTypes?.[fieldName]), + serializeValue(value, protocolVersion, fieldType), ); }; - const compileUnary = (criteria) => { + const compileUnary = (criteria: unknown[]): string => { const op = criteria[0]; + // eslint-disable-next-line @typescript-eslint/no-use-before-define const crit = compileCore(criteria[1]); if (op === '!') { @@ -121,43 +139,51 @@ const compileCriteria = (() => { throw errors.Error('E4003', op); }; - const compileGroup = (criteria) => { - const bag = []; - let groupOperator; - let nextGroupOperator; + const compileGroup = (criteria: unknown[]): string => { + const bag: string[] = []; + // eslint-disable-next-line @typescript-eslint/init-declarations + let groupOperator: string | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let nextGroupOperator: string | undefined; - each(criteria, function (index, criterion) { + criteria.forEach((criterion) => { if (Array.isArray(criterion)) { if (bag.length > 1 && groupOperator !== nextGroupOperator) { - // @ts-expect-error - throw new errors.Error('E4019'); + throw errors.Error('E4019'); } - // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-use-before-define bag.push(`(${compileCore(criterion)})`); groupOperator = nextGroupOperator; nextGroupOperator = 'and'; } else { - nextGroupOperator = isConjunctiveOperator(this) ? 'and' : 'or'; + nextGroupOperator = isConjunctiveOperator(criterion) ? 'and' : 'or'; } }); return bag.join(` ${groupOperator} `); }; - const compileCore = (criteria) => { - if (Array.isArray(criteria[0])) { - return compileGroup(criteria); + const compileCore = (criteria: unknown): string => { + const criterion: unknown[] = Array.isArray(criteria) ? criteria : [criteria]; + + if (Array.isArray(criterion[0])) { + return compileGroup(criterion); } - if (isUnaryOperation(criteria)) { - return compileUnary(criteria); + if (isUnaryOperation(criterion)) { + return compileUnary(criterion); } - return compileBinary(criteria); + return compileBinary(criterion); }; - return (criteria, version, types, filterToLower) => { + return ( + criteria: unknown, + version: number, + types: FieldTypes | undefined, + filterToLower: boolean | undefined, + ): string => { fieldTypes = types; forceLowerCase = filterToLower ?? config().oDataFilterToLower; protocolVersion = version; @@ -166,76 +192,62 @@ const compileCriteria = (() => { }; })(); -const createODataQueryAdapter = (queryOptions) => { - /* eslint-disable @typescript-eslint/naming-convention */ - let _sorting = []; - const _criteria = []; - const _expand = queryOptions.expand; - let _select; - let _skip; - let _take; - let _countQuery; - - const _oDataVersion = queryOptions.version || DEFAULT_PROTOCOL_VERSION; - - const hasSlice = () => _skip || _take !== undefined; - - const hasFunction = (criterion) => { - for (let i = 0; i < criterion.length; i++) { - if (isFunction(criterion[i])) { - return true; - } - - if (Array.isArray(criterion[i]) && hasFunction(criterion[i])) { - return true; - } - } - return false; - }; - - const requestData = () => { - const result = {}; - - if (!_countQuery) { - if (_sorting.length) { - // @ts-expect-error - result.$orderby = _sorting.join(','); +const createODataQueryAdapter = (queryOptions: RemoteQueryOptions): QueryAdapter => { + let sorting: string[] = []; + const criteria: unknown[] = []; + const { expand } = queryOptions; + // eslint-disable-next-line @typescript-eslint/init-declarations + let select: string[] | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let skip: number | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let take: number | undefined; + let countQuery = false; + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const oDataVersion = queryOptions.version || DEFAULT_PROTOCOL_VERSION; + + const hasSlice = (): boolean => !!skip || take !== undefined; + + const hasFunction = (criterion: unknown[]): boolean => criterion.some( + (item) => isFunction(item) || (Array.isArray(item) && hasFunction(item)), + ); + + const requestData = (): ODataRequestParams => { + const result: ODataRequestParams = {}; + + if (!countQuery) { + if (sorting.length) { + result.$orderby = sorting.join(','); } - if (_skip) { - // @ts-expect-error - result.$skip = _skip; + if (skip) { + result.$skip = skip; } - if (_take !== undefined) { - // @ts-expect-error - result.$top = _take; + if (take !== undefined) { + result.$top = take; } - // @ts-expect-error - result.$select = generateSelect(_oDataVersion, _select) || undefined; - // @ts-expect-error // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - result.$expand = generateExpand(_oDataVersion, _expand, _select) || undefined; + result.$select = generateSelect(oDataVersion, select) || undefined; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + result.$expand = generateExpand(oDataVersion, expand, select) || undefined; } - if (_criteria.length) { - const criteria = _criteria.length < 2 ? _criteria[0] : _criteria; - const fieldTypes = queryOptions?.fieldTypes; - const filterToLower = queryOptions?.filterToLower; - // @ts-expect-error - result.$filter = compileCriteria(criteria, _oDataVersion, fieldTypes, filterToLower); + if (criteria.length) { + const filterCriteria: unknown = criteria.length < 2 ? criteria[0] : criteria; + const { fieldTypes, filterToLower } = queryOptions; + result.$filter = compileCriteria(filterCriteria, oDataVersion, fieldTypes, filterToLower); } - if (_countQuery) { - // @ts-expect-error + if (countQuery) { result.$top = 0; } - if (queryOptions.requireTotalCount || _countQuery) { + if (queryOptions.requireTotalCount || countQuery) { // todo: tests!!! - if (_oDataVersion !== 4) { - // @ts-expect-error + if (oDataVersion !== 4) { + // eslint-disable-next-line spellcheck/spell-checker result.$inlinecount = 'allpages'; } else { - // @ts-expect-error result.$count = 'true'; } } @@ -243,19 +255,13 @@ const createODataQueryAdapter = (queryOptions) => { return result; }; - const tryLiftSelect = (tasks) => { - let selectIndex = -1; - for (let i = 0; i < tasks.length; i++) { - if (tasks[i].name === 'select') { - selectIndex = i; - break; - } - } + const tryLiftSelect = (tasks: RemoteTask[]): void => { + const selectIndex = tasks.findIndex((task) => task.name === 'select'); if (selectIndex < 0 || !isFunction(tasks[selectIndex].args[0])) return; const nextTask = tasks[1 + selectIndex]; - if (!nextTask || nextTask.name !== 'slice') return; + if (nextTask?.name !== 'slice') return; tasks[1 + selectIndex] = tasks[selectIndex]; tasks[selectIndex] = nextTask; @@ -265,96 +271,94 @@ const createODataQueryAdapter = (queryOptions) => { optimize: tryLiftSelect, - exec(url) { + exec(url: string): DeferredObj { return sendRequest( - _oDataVersion, + oDataVersion, { url, - params: extend(requestData(), queryOptions?.params), + params: extend(requestData(), queryOptions.params), }, { beforeSend: queryOptions.beforeSend, jsonp: queryOptions.jsonp, withCredentials: queryOptions.withCredentials, - countOnly: _countQuery, + countOnly: countQuery, processDatesAsUtc: queryOptions.processDatesAsUtc, fieldTypes: queryOptions.fieldTypes, - isPaged: isFinite(_take), + isPaged: isFinite(Number(take)), }, ); }, - /* eslint-disable @typescript-eslint/no-invalid-void-type */ - multiSort(args): boolean | void { - let rules; + + multiSort(args: unknown[]): boolean | undefined { + const rules: string[] = []; if (hasSlice()) { return false; } - for (let i = 0; i < args.length; i++) { - const getter = args[i][0]; - const desc = !!args[i][1]; - let rule; + for (const arg of args) { + const [getter, desc] = Array.isArray(arg) ? arg : []; if (typeof getter !== 'string') { return false; } - rule = serializePropName(getter); - if (desc) { - rule += ' desc'; - } - - rules = rules || []; - rules.push(rule); + rules.push(desc ? `${serializePropName(getter)} desc` : serializePropName(getter)); } - _sorting = rules; + sorting = rules; + + return undefined; }, - slice(skipCount, takeCount): boolean | void { + slice(skipCount: number, takeCount: number): boolean | undefined { if (hasSlice()) { return false; } - _skip = skipCount; - _take = takeCount; + skip = skipCount; + take = takeCount; + + return undefined; }, - filter(criterion): boolean | void { + filter(...args: unknown[]): boolean | undefined { if (hasSlice()) { return false; } - if (!Array.isArray(criterion)) { - criterion = [].slice.call(arguments); - } + const [first] = args; + const criterion: unknown[] = Array.isArray(first) ? first : args.slice(); if (hasFunction(criterion)) { return false; } - if (_criteria.length) { - // @ts-expect-error - _criteria.push('and'); + if (criteria.length) { + criteria.push('and'); } - // @ts-expect-error - _criteria.push(criterion); + criteria.push(criterion); + + return undefined; }, - select(expr): boolean | void { - if (_select || isFunction(expr)) { + select(...args: unknown[]): boolean | undefined { + const [expr] = args; + + if (select || isFunction(expr)) { return false; } - if (!Array.isArray(expr)) { - expr = [].slice.call(arguments); - } + select = (Array.isArray(expr) ? expr : args.slice()).map(String); + + return undefined; + }, - _select = expr; + count(): boolean { + countQuery = true; + return countQuery; }, - // eslint-disable-next-line no-return-assign - count: () => _countQuery = true, }; }; diff --git a/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts b/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts index e37f10217123..36acb68ebdde 100644 --- a/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts +++ b/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts @@ -1,72 +1,91 @@ import '@js/common/data/odata/query_adapter'; import { sendRequest } from '@js/common/data/odata/utils'; +import type { DeferredObj } from '@js/core/utils/deferred'; const DEFAULT_PROTOCOL_VERSION = 4; +export interface RequestDispatcherOptions { + url?: string; + beforeSend?: Function; + jsonp?: boolean; + version?: number; + withCredentials?: boolean; + processDatesAsUtc?: boolean; + deserializeDates?: boolean; + filterToLower?: boolean; +} + export default class RequestDispatcher { - constructor(options) { - options = options || {}; - // @ts-expect-error - this._url = String(options.url).replace(/\/+$/, ''); - // @ts-expect-error - this._beforeSend = options.beforeSend; - // @ts-expect-error - this._jsonp = options.jsonp; - // @ts-expect-error - this._version = options.version || DEFAULT_PROTOCOL_VERSION; - // @ts-expect-error - this._withCredentials = options.withCredentials; - // @ts-expect-error - this._processDatesAsUtc = options.processDatesAsUtc ?? options.deserializeDates ?? false; - // @ts-expect-error - this._filterToLower = options.filterToLower; + _url: string; + + _beforeSend?: Function; + + _jsonp?: boolean; + + _version: number; + + _withCredentials?: boolean; + + _processDatesAsUtc: boolean; + + _filterToLower?: boolean; + + constructor(options?: RequestDispatcherOptions) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const dispatcherOptions: RequestDispatcherOptions = options || {}; + + this._url = String(dispatcherOptions.url).replace(/\/+$/, ''); + this._beforeSend = dispatcherOptions.beforeSend; + this._jsonp = dispatcherOptions.jsonp; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this._version = dispatcherOptions.version || DEFAULT_PROTOCOL_VERSION; + this._withCredentials = dispatcherOptions.withCredentials; + this._processDatesAsUtc = dispatcherOptions.processDatesAsUtc + ?? dispatcherOptions.deserializeDates ?? false; + this._filterToLower = dispatcherOptions.filterToLower; } - sendRequest(url, method, params, payload) { + sendRequest( + url: string, + method?: string, + params?: Record | null, + payload?: unknown, + ): DeferredObj { return sendRequest( this.version, { url, method, - params: params || {}, + params: params ?? {}, payload, }, { - // @ts-expect-error beforeSend: this._beforeSend, - // @ts-expect-error jsonp: this._jsonp, - // @ts-expect-error withCredentials: this._withCredentials, - // @ts-expect-error processDatesAsUtc: this._processDatesAsUtc, }, ); } - get version() { - // @ts-expect-error + get version(): number { return this._version; } - get beforeSend() { - // @ts-expect-error + get beforeSend(): Function | undefined { return this._beforeSend; } - get url() { - // @ts-expect-error + get url(): string { return this._url; } - get jsonp() { - // @ts-expect-error + get jsonp(): boolean | undefined { return this._jsonp; } - get filterToLower() { - // @ts-expect-error + get filterToLower(): boolean | undefined { return this._filterToLower; } } diff --git a/packages/devextreme/js/__internal/data/odata/store.ts b/packages/devextreme/js/__internal/data/odata/store.ts index 4464dc43d888..d02f3cf322fe 100644 --- a/packages/devextreme/js/__internal/data/odata/store.ts +++ b/packages/devextreme/js/__internal/data/odata/store.ts @@ -35,13 +35,6 @@ export interface ODataLoadOptions extends StoreLoadOptions { customQueryParams?: Record; } -// `RequestDispatcher` does not declare its own fields yet, so the members the store reads -// directly are described here until that class is typed. -type StoreRequestDispatcher = RequestDispatcher & { - _withCredentials?: unknown; - _processDatesAsUtc?: unknown; -}; - const expandKeyType = (key: StoreKey, keyType: string): FieldTypes => ({ [String(key)]: keyType }); const getProperty = (source: unknown, name: string): unknown => ( @@ -72,7 +65,7 @@ const mergeFieldTypesWithKeyType = ( }; class ODataStore extends Store { - _requestDispatcher: StoreRequestDispatcher; + _requestDispatcher: RequestDispatcher; _fieldTypes: FieldTypes; diff --git a/packages/devextreme/js/__internal/data/query.ts b/packages/devextreme/js/__internal/data/query.ts index c35f346ae08c..f25f87f4734f 100644 --- a/packages/devextreme/js/__internal/data/query.ts +++ b/packages/devextreme/js/__internal/data/query.ts @@ -1,9 +1,16 @@ import { queryImpl } from '@js/common/data/query_implementation'; +import type { ArrayQuery, QueryOptions } from '@ts/data/array_query'; +import type { RemoteQuery, RemoteQueryOptions } from '@ts/data/remote_query'; -const query = function () { - const impl = Array.isArray(arguments[0]) ? 'array' : 'remote'; - // @ts-expect-error - return queryImpl[impl].apply(this, arguments); -}; +function query(array: unknown[], queryOptions?: QueryOptions): ArrayQuery; +function query(url: string, queryOptions?: RemoteQueryOptions): RemoteQuery; +function query( + source: unknown[] | string, + queryOptions?: QueryOptions & RemoteQueryOptions, +): ArrayQuery | RemoteQuery { + return Array.isArray(source) + ? queryImpl.array(source, queryOptions) + : queryImpl.remote(source, queryOptions); +} export default query; diff --git a/packages/devextreme/js/__internal/data/remote_query.ts b/packages/devextreme/js/__internal/data/remote_query.ts index 53fb79c54c50..d7ed4b829649 100644 --- a/packages/devextreme/js/__internal/data/remote_query.ts +++ b/packages/devextreme/js/__internal/data/remote_query.ts @@ -1,29 +1,73 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import arrayQueryImpl from '@js/common/data/array_query'; import { errors, handleError } from '@js/common/data/errors'; import queryAdapters from '@js/common/data/query_adapters'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; -import { each } from '@js/core/utils/iterator'; import { isFunction } from '@js/core/utils/type'; +import type { ArrayQuery, LangParams } from '@ts/data/array_query'; + +export interface RemoteTask { + name: string; + args: unknown[]; +} + +export interface QueryAdapter { + [taskName: string]: unknown; + optimize?: (tasks: RemoteTask[]) => void; + exec: (url: string) => DeferredObj; +} + +export interface RemoteQueryOptions { + adapter?: string | ((options: RemoteQueryOptions) => QueryAdapter); + errorHandler?: (error: unknown) => void; + langParams?: LangParams; + version?: number; + expand?: string | string[] | Function; + fieldTypes?: Record; + filterToLower?: boolean; + requireTotalCount?: boolean; + params?: Record; + beforeSend?: Function; + jsonp?: boolean; + withCredentials?: boolean; + processDatesAsUtc?: boolean; + deserializeDates?: boolean; +} + +export interface RemoteQuery { + sortBy: (...args: unknown[]) => RemoteQuery; + thenBy: (...args: unknown[]) => RemoteQuery; + filter: (...args: unknown[]) => RemoteQuery; + slice: (...args: unknown[]) => RemoteQuery; + select: (...args: unknown[]) => RemoteQuery; + groupBy: (...args: unknown[]) => RemoteQuery; + count: (...args: unknown[]) => DeferredObj; + min: (...args: unknown[]) => DeferredObj; + max: (...args: unknown[]) => DeferredObj; + sum: (...args: unknown[]) => DeferredObj; + avg: (...args: unknown[]) => DeferredObj; + aggregate: (...args: unknown[]) => DeferredObj; + enumerate: (...args: unknown[]) => DeferredObj; +} + +const createTask = function (name: string, args: unknown[]): RemoteTask { + return { name, args }; +}; -const remoteQueryImpl = function (url, queryOptions, tasks) { - tasks = tasks || []; - queryOptions = queryOptions || {}; - - const createTask = function (name, args) { - return { name, args }; - }; +const remoteQueryImpl = function ( + url: string, + options?: RemoteQueryOptions, + previousTasks?: RemoteTask[], +): RemoteQuery { + const tasks: RemoteTask[] = previousTasks ?? []; + const queryOptions: RemoteQueryOptions = options ?? {}; - const exec = function (executorTask) { - // @ts-expect-error - const d = new Deferred(); - let _adapterFactory; - let _adapter; - let _taskQueue; - let _currentTask; - let _mergedSortArgs; + const exec = function (executorTask: RemoteTask): DeferredObj { + const d = Deferred(); + // eslint-disable-next-line @typescript-eslint/init-declarations + let mergedSortArgs: unknown[][] | undefined; - const rejectWithNotify = function (error) { + const rejectWithNotify = function (error: unknown): void { const handler = queryOptions.errorHandler; if (handler) { handler(error); @@ -33,90 +77,108 @@ const remoteQueryImpl = function (url, queryOptions, tasks) { d.reject(error); }; - function mergeSortTask(task) { - // eslint-disable-next-line default-case + function mergeSortTask(task: RemoteTask): boolean { switch (task.name) { case 'sortBy': - _mergedSortArgs = [task.args]; + mergedSortArgs = [task.args]; return true; case 'thenBy': - if (!_mergedSortArgs) { + if (!mergedSortArgs) { throw errors.Error('E4004'); } - _mergedSortArgs.push(task.args); + mergedSortArgs.push(task.args); return true; - } - return false; + default: + return false; + } } - function unmergeSortTasks() { - const head = _taskQueue[0]; - const unmergedTasks = []; - - if (head && head.name === 'multiSort') { - _taskQueue.shift(); - each(head.args[0], function () { - // @ts-expect-error - unmergedTasks.push(createTask(unmergedTasks.length ? 'thenBy' : 'sortBy', this)); - }); + function unmergeSortTasks(queue: RemoteTask[]): RemoteTask[] { + const head = queue[0]; + const unmergedTasks: RemoteTask[] = []; + + if (head?.name === 'multiSort') { + queue.shift(); + const [sortArgsList] = head.args; + if (Array.isArray(sortArgsList)) { + sortArgsList.forEach((sortArgs: unknown) => { + unmergedTasks.push(createTask( + unmergedTasks.length ? 'thenBy' : 'sortBy', + Array.isArray(sortArgs) ? sortArgs : [sortArgs], + )); + }); + } } - _taskQueue = unmergedTasks.concat(_taskQueue); + return unmergedTasks.concat(queue); } - try { - _adapterFactory = queryOptions.adapter; - if (!isFunction(_adapterFactory)) { - _adapterFactory = queryAdapters[_adapterFactory]; + function passTaskToAdapter(adapter: QueryAdapter, task: RemoteTask): boolean { + if (String(task.name) === 'enumerate') { + return true; } - _adapter = _adapterFactory(queryOptions); - - _taskQueue = [].concat(tasks).concat(executorTask); + const taskHandler = adapter[task.name]; - const { optimize } = _adapter; - if (optimize) optimize(_taskQueue); + return isFunction(taskHandler) && taskHandler.apply(adapter, task.args) !== false; + } - while (_taskQueue.length) { - /* eslint-disable-next-line prefer-destructuring */ - _currentTask = _taskQueue[0]; + function collectAdapterTasks(adapter: QueryAdapter, queue: RemoteTask[]): void { + while (queue.length) { + const currentTask = queue[0]; - if (!mergeSortTask(_currentTask)) { - if (_mergedSortArgs) { - _taskQueue.unshift(createTask('multiSort', [_mergedSortArgs])); - _mergedSortArgs = null; + if (!mergeSortTask(currentTask)) { + if (mergedSortArgs) { + queue.unshift(createTask('multiSort', [mergedSortArgs])); + mergedSortArgs = undefined; + // eslint-disable-next-line no-continue continue; } - if (String(_currentTask.name) !== 'enumerate') { - if (!_adapter[_currentTask.name] || _adapter[_currentTask.name].apply(_adapter, _currentTask.args) === false) { - break; - } + if (!passTaskToAdapter(adapter, currentTask)) { + break; } } - _taskQueue.shift(); + queue.shift(); } + } - unmergeSortTasks(); + try { + const adapterFactory = isFunction(queryOptions.adapter) + ? queryOptions.adapter + : queryAdapters[String(queryOptions.adapter)]; + + const adapter: QueryAdapter = adapterFactory(queryOptions); + + let taskQueue: RemoteTask[] = [...tasks, executorTask]; + + const { optimize } = adapter; + if (optimize) optimize(taskQueue); + + collectAdapterTasks(adapter, taskQueue); - _adapter.exec(url) - .done((result, extra) => { - if (!_taskQueue.length) { + taskQueue = unmergeSortTasks(taskQueue); + + adapter.exec(url) + .done((result: unknown, extra: unknown) => { + if (!taskQueue.length) { d.resolve(result, extra); } else { - let clientChain = arrayQueryImpl(result, { + // @ts-expect-error the adapter resolves with whatever the service returned + let clientChain: ArrayQuery = arrayQueryImpl(result, { errorHandler: queryOptions.errorHandler, }); - each(_taskQueue, function () { - clientChain = clientChain[this.name].apply(clientChain, this.args); + taskQueue.forEach((task) => { + const method = clientChain[task.name]; + if (isFunction(method)) { + clientChain = method.apply(clientChain, task.args); + } }); - clientChain - // @ts-expect-error - .done(d.resolve) - .fail(d.reject); + // @ts-expect-error the queue ends with enumerate, so the chain ends with a Deferred + clientChain.done(d.resolve).fail(d.reject); } }) .fail(rejectWithNotify); @@ -124,32 +186,32 @@ const remoteQueryImpl = function (url, queryOptions, tasks) { rejectWithNotify(x); } + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise return d.promise(); }; - const query = {}; - - each( - ['sortBy', 'thenBy', 'filter', 'slice', 'select', 'groupBy'], - function () { - const name = String(this); - query[name] = function () { - return remoteQueryImpl(url, queryOptions, tasks.concat(createTask(name, arguments))); - }; - }, + const chain = (name: string, args: unknown[]): RemoteQuery => remoteQueryImpl( + url, + queryOptions, + tasks.concat(createTask(name, args)), ); - each( - ['count', 'min', 'max', 'sum', 'avg', 'aggregate', 'enumerate'], - function () { - const name = String(this); - query[name] = function () { - return exec.call(this, createTask(name, arguments)); - }; - }, - ); - - return query; + return { + sortBy: (...args: unknown[]): RemoteQuery => chain('sortBy', args), + thenBy: (...args: unknown[]): RemoteQuery => chain('thenBy', args), + filter: (...args: unknown[]): RemoteQuery => chain('filter', args), + slice: (...args: unknown[]): RemoteQuery => chain('slice', args), + select: (...args: unknown[]): RemoteQuery => chain('select', args), + groupBy: (...args: unknown[]): RemoteQuery => chain('groupBy', args), + + count: (...args: unknown[]): DeferredObj => exec(createTask('count', args)), + min: (...args: unknown[]): DeferredObj => exec(createTask('min', args)), + max: (...args: unknown[]): DeferredObj => exec(createTask('max', args)), + sum: (...args: unknown[]): DeferredObj => exec(createTask('sum', args)), + avg: (...args: unknown[]): DeferredObj => exec(createTask('avg', args)), + aggregate: (...args: unknown[]): DeferredObj => exec(createTask('aggregate', args)), + enumerate: (...args: unknown[]): DeferredObj => exec(createTask('enumerate', args)), + }; }; export default remoteQueryImpl; diff --git a/packages/devextreme/js/__internal/data/store_helper.ts b/packages/devextreme/js/__internal/data/store_helper.ts index 9c30c9243f73..798d00fd5bee 100644 --- a/packages/devextreme/js/__internal/data/store_helper.ts +++ b/packages/devextreme/js/__internal/data/store_helper.ts @@ -1,85 +1,134 @@ import arrayQuery from '@js/common/data/array_query'; +import type { SortingInfo } from '@js/common/data/utils'; import { normalizeSortingInfo } from '@js/common/data/utils'; -// @ts-expect-error -import { grep } from '@js/core/utils/common'; import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; +import { isObject } from '@js/core/utils/type'; +import type { LangParams } from '@ts/data/array_query'; + +export interface DataQuery { + /* eslint-disable @typescript-eslint/method-signature-style */ + setLangParams?(langParams: LangParams): void; + filter(criteria: unknown): this; + sortBy(getter: unknown, desc?: unknown, compare?: unknown): this; + thenBy(getter: unknown, desc?: unknown, compare?: unknown): this; + select(getter: unknown): this; + slice(skip: number, take?: number): this; + groupBy(getter: unknown): this; + toArray(): unknown[]; + /* eslint-enable @typescript-eslint/method-signature-style */ +} + +interface GroupingInfo extends Array { + keepInitialKeyOrder?: boolean; +} + +export interface QueryByOptions { + filter?: unknown; + sort?: unknown; + select?: unknown; + group?: unknown; + skip?: number; + take?: number; + langParams?: LangParams; +} + +interface GroupResult { + items?: unknown[]; +} -function multiLevelGroup(query, groupInfo) { - query = query.groupBy(groupInfo[0].selector); +function multiLevelGroup( + query: TQuery, + groupInfo: SortingInfo[], +): TQuery { + let result = query.groupBy(groupInfo[0].selector); if (groupInfo.length > 1) { - query = query.select((g) => extend({}, g, { - items: multiLevelGroup(arrayQuery(g.items), groupInfo.slice(1)).toArray(), - })); + result = result.select((group: unknown) => { + const { items }: GroupResult = isObject(group) ? group : {}; + + const merged: unknown = extend({}, group, { + items: multiLevelGroup(arrayQuery(items ?? []), groupInfo.slice(1)).toArray(), + }); + + return merged; + }); } - return query; + return result; } -function arrangeSortingInfo(groupInfo, sortInfo) { - const filteredGroup = []; - each(groupInfo, (_, group) => { - const collision = grep(sortInfo, (sort) => group.selector === sort.selector); +function arrangeSortingInfo(groupInfo: SortingInfo[], sortInfo: SortingInfo[]): SortingInfo[] { + const filteredGroup = groupInfo.filter( + (group) => !sortInfo.some((sort) => group.selector === sort.selector), + ); - if (collision.length < 1) { - // @ts-expect-error - filteredGroup.push(group); - } - }); return filteredGroup.concat(sortInfo); } -function queryByOptions(query, options, isCountQuery) { - options = options || {}; +function queryByOptions( + query: TQuery, + options?: QueryByOptions, + isCountQuery?: boolean, +): TQuery { + const queryOptions: QueryByOptions = options ?? {}; + let result = query; - const { filter } = options; + const { filter } = queryOptions; - if (options?.langParams) { - query.setLangParams?.(options.langParams); + if (queryOptions.langParams) { + result.setLangParams?.(queryOptions.langParams); } if (filter) { - query = query.filter(filter); + result = result.filter(filter); } if (isCountQuery) { - return query; + return result; } - let { sort } = options; - const { select } = options; - let { group } = options; - const { skip } = options; - const { take } = options; + const { + sort, select, skip, take, + } = queryOptions; - if (group) { - group = normalizeSortingInfo(group); - group.keepInitialKeyOrder = !!options.group.keepInitialKeyOrder; + // eslint-disable-next-line @typescript-eslint/init-declarations + let group: GroupingInfo | undefined; + if (queryOptions.group) { + const groupOption: { keepInitialKeyOrder?: unknown } = isObject(queryOptions.group) + ? queryOptions.group + : {}; + + group = normalizeSortingInfo(queryOptions.group); + group.keepInitialKeyOrder = !!groupOption.keepInitialKeyOrder; } + if (sort || group) { - sort = normalizeSortingInfo(sort || []); - if (group && !group.keepInitialKeyOrder) { - sort = arrangeSortingInfo(group, sort); - } - each(sort, function (index) { - query = query[index ? 'thenBy' : 'sortBy'](this.selector, this.desc, this.compare); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const sortInfo = normalizeSortingInfo(sort || []); + const sortRules = group && !group.keepInitialKeyOrder + ? arrangeSortingInfo(group, sortInfo) + : sortInfo; + + sortRules.forEach((rule, index) => { + result = index + ? result.thenBy(rule.selector, rule.desc, rule.compare) + : result.sortBy(rule.selector, rule.desc, rule.compare); }); } if (select) { - query = query.select(select); + result = result.select(select); } if (group) { - query = multiLevelGroup(query, group); + result = multiLevelGroup(result, group); } if (take || skip) { - query = query.slice(skip || 0, take); + result = result.slice(skip ?? 0, take); } - return query; + return result; } export default { diff --git a/packages/devextreme/js/__internal/data/utils.ts b/packages/devextreme/js/__internal/data/utils.ts index ea0a65a494d8..373235940504 100644 --- a/packages/devextreme/js/__internal/data/utils.ts +++ b/packages/devextreme/js/__internal/data/utils.ts @@ -1,17 +1,20 @@ /* eslint-disable spellcheck/spell-checker */ import domAdapter from '@js/core/dom_adapter'; import { equalByValue } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; import { map } from '@js/core/utils/iterator'; import readyCallbacks from '@js/core/utils/ready_callbacks'; -import { isFunction } from '@js/core/utils/type'; +import { isFunction, isObject, isString } from '@js/core/utils/type'; import { getWindow } from '@js/core/utils/window'; const ready = readyCallbacks.add; export const XHR_ERROR_UNLOAD = 'DEVEXTREME_XHR_ERROR_UNLOAD'; -export const normalizeBinaryCriterion = function (crit) { +export type BinaryCriterion = [unknown, string, unknown]; + +export const normalizeBinaryCriterion = function (crit: unknown[]): BinaryCriterion { return [ crit[0], crit.length < 3 ? '=' : String(crit[1]).toLowerCase(), @@ -19,40 +22,60 @@ export const normalizeBinaryCriterion = function (crit) { ]; }; -export const normalizeSortingInfo = function (info) { - if (!Array.isArray(info)) { - info = [info]; - } +export type SortingSelector = string | Function; + +export interface SortingInfo { + selector: SortingSelector | undefined; + desc: boolean; + compare?: unknown; + isExpanded?: boolean; + groupInterval?: unknown; +} + +interface SortingDescriptor { + getter?: SortingSelector; + field?: SortingSelector; + selector?: SortingSelector; + desc?: unknown; + dir?: unknown; + compare?: unknown; +} - return map(info, (i) => { - const result = { - selector: isFunction(i) || typeof i === 'string' ? i : i.getter || i.field || i.selector, - desc: !!(i.desc || String(i.dir).charAt(0).toLowerCase() === 'd'), +export const normalizeSortingInfo = function (info: unknown): SortingInfo[] { + const infoArray: unknown[] = Array.isArray(info) ? info : [info]; + + return infoArray.map((item: unknown): SortingInfo => { + const descriptor: SortingDescriptor = isObject(item) ? item : {}; + const result: SortingInfo = { + selector: isFunction(item) || isString(item) + ? item + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + : descriptor.getter || descriptor.field || descriptor.selector, + desc: !!(descriptor.desc || String(descriptor.dir).charAt(0).toLowerCase() === 'd'), }; - if (i.compare) { - // @ts-expect-error - result.compare = i.compare; + if (descriptor.compare) { + result.compare = descriptor.compare; } return result; }); }; export const errorMessageFromXhr = (function () { - const textStatusMessages = { + const textStatusMessages: Record = { timeout: 'Network connection timeout', error: 'Unspecified network error', parsererror: 'Unexpected server response', }; /// #DEBUG - const textStatusDetails = { + const textStatusDetails: Record = { timeout: 'possible causes: the remote host is not accessible, overloaded or is not included into the domain white-list when being run in the native container', error: 'if the remote host is located on another domain, make sure it properly supports cross-origin resource sharing (CORS), or use the JSONP approach instead', parsererror: 'the remote host did not respond with valid JSON data', }; /// #ENDDEBUG - const explainTextStatus = function (textStatus) { + const explainTextStatus = function (textStatus: string): string { let result = textStatusMessages[textStatus]; if (!result) { @@ -67,79 +90,99 @@ export const errorMessageFromXhr = (function () { }; // T542570, https://stackoverflow.com/a/18170879 - let unloading; + let unloading = false; ready(() => { const window = getWindow(); domAdapter.listen(window, 'beforeunload', () => { unloading = true; }); }); - return function (xhr, textStatus) { + return function (xhr: unknown, textStatus: unknown): string { if (unloading) { return XHR_ERROR_UNLOAD; } - if (xhr.status < 400) { - return explainTextStatus(textStatus); + + const status = isObject(xhr) && 'status' in xhr ? Number(xhr.status) : NaN; + + if (status < 400) { + return explainTextStatus(String(textStatus)); } - return xhr.statusText; + + const statusText = isObject(xhr) && 'statusText' in xhr ? xhr.statusText : undefined; + + return isString(statusText) ? statusText : ''; }; }()); +type AggregatableValue = number | string | Date; + export const aggregators = { count: { seed: 0, - step(count) { return 1 + count; }, + step(count: number): number { return 1 + count; }, }, sum: { seed: 0, - step(sum, item) { return sum + item; }, + step(sum: number, item: number): number { return sum + item; }, }, min: { - step(min, item) { return item < min ? item : min; }, + step(min: AggregatableValue, item: AggregatableValue): AggregatableValue { + return item < min ? item : min; + }, }, max: { - step(max, item) { return item > max ? item : max; }, + step(max: AggregatableValue, item: AggregatableValue): AggregatableValue { + return item > max ? item : max; + }, }, avg: { seed: [0, 0], - step(pair, value) { + step(pair: [number, number], value: number): [number, number] { return [pair[0] + value, pair[1] + 1]; }, - finalize(pair) { + finalize(pair: [number, number]): number { return pair[1] ? pair[0] / pair[1] : NaN; }, }, }; -export const processRequestResultLock = (function () { +interface RequestResultLock { + obtain: () => void; + release: () => void; + promise: () => DeferredObj; + reset: () => void; +} + +export const processRequestResultLock: RequestResultLock = (function (): RequestResultLock { let lockCount = 0; - let lockDeferred; + // eslint-disable-next-line @typescript-eslint/init-declarations + let lockDeferred: DeferredObj | undefined; - const obtain = function () { + const obtain = function (): void { if (lockCount === 0) { - // @ts-expect-error - lockDeferred = new Deferred(); + lockDeferred = Deferred(); } - lockCount++; + lockCount += 1; }; - const release = function () { - lockCount--; + const release = function (): void { + lockCount -= 1; if (lockCount < 1) { - lockDeferred.resolve(); + lockDeferred?.resolve(); } }; - const promise = function () { - // @ts-expect-error - const deferred = lockCount === 0 ? new Deferred().resolve() : lockDeferred; + const promise = function (): DeferredObj { + const deferred = lockCount === 0 || !lockDeferred + ? Deferred().resolve() + : lockDeferred; + + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise return deferred.promise(); }; - const reset = function () { + const reset = function (): void { lockCount = 0; - if (lockDeferred) { - lockDeferred.resolve(); - } + lockDeferred?.resolve(); }; return { @@ -150,21 +193,25 @@ export const processRequestResultLock = (function () { }; }()); -export function isDisjunctiveOperator(condition) { - return /^(or|\|\||\|)$/i.test(condition); +export function isDisjunctiveOperator(condition: unknown): boolean { + return /^(or|\|\||\|)$/i.test(String(condition)); } -export function isConjunctiveOperator(condition) { - return /^(and|&&|&)$/i.test(condition); +export function isConjunctiveOperator(condition: unknown): boolean { + return /^(and|&&|&)$/i.test(String(condition)); } -export const keysEqual = function (keyExpr, key1, key2) { +const isRecord = (value: unknown): value is Record => isObject(value) + || Array.isArray(value); + +export const keysEqual = function (keyExpr: unknown, key1: unknown, key2: unknown): boolean { if (Array.isArray(keyExpr)) { - const names = map(key1, (v, k) => k); - let name; - for (let i = 0; i < names.length; i++) { - name = names[i]; - if (!equalByValue(key1[name], key2[name], { strict: false })) { + const names: string[] = map(key1, (value: unknown, name: string): string => name); + const values1: Record = isRecord(key1) ? key1 : {}; + const values2: Record = isRecord(key2) ? key2 : {}; + + for (const name of names) { + if (!equalByValue(values1[name], values2[name], { strict: false })) { return false; } } @@ -176,44 +223,13 @@ export const keysEqual = function (keyExpr, key1, key2) { const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; -// eslint-disable-next-line @typescript-eslint/naming-convention -export const base64_encode = function (input) { - if (!Array.isArray(input)) { - input = stringToByteArray(String(input)); - } - - let result = ''; - - function getBase64Char(index) { - return BASE64_CHARS.charAt(index); - } - - for (let i = 0; i < input.length; i += 3) { - const octet1 = input[i]; - const octet2 = input[i + 1]; - const octet3 = input[i + 2]; - - result += map( - [ - octet1 >> 2, - ((octet1 & 3) << 4) | octet2 >> 4, - isNaN(octet2) ? 64 : ((octet2 & 15) << 2) | octet3 >> 6, - isNaN(octet3) ? 64 : octet3 & 63, - ], - getBase64Char, - ).join(''); - } - - return result; -}; +/* eslint-disable no-bitwise */ -function stringToByteArray(str) { +function stringToByteArray(str: string): number[] { const bytes: number[] = []; - let code: number; - let i; - for (i = 0; i < str.length; i++) { - code = str.charCodeAt(i); + for (let i = 0; i < str.length; i += 1) { + const code = str.charCodeAt(i); if (code < 128) { bytes.push(code); @@ -222,21 +238,54 @@ function stringToByteArray(str) { } else if (code < 65536) { bytes.push(224 + (code >> 12), 128 + ((code >> 6) & 63), 128 + (code & 63)); } else if (code < 2097152) { - bytes.push(240 + (code >> 18), 128 + ((code >> 12) & 63), 128 + ((code >> 6) & 63), 128 + (code & 63)); + bytes.push( + 240 + (code >> 18), + 128 + ((code >> 12) & 63), + 128 + ((code >> 6) & 63), + 128 + (code & 63), + ); } } return bytes; } -export const isUnaryOperation = function (crit) { - return crit[0] === '!' && Array.isArray(crit[1]); +// eslint-disable-next-line @typescript-eslint/naming-convention +export const base64_encode = function (input: string | number[]): string { + const bytes: number[] = Array.isArray(input) ? input : stringToByteArray(String(input)); + + let result = ''; + + function getBase64Char(index: number): string { + return BASE64_CHARS.charAt(index); + } + + for (let i = 0; i < bytes.length; i += 3) { + const octet1 = bytes[i]; + const octet2 = bytes[i + 1]; + const octet3 = bytes[i + 2]; + + result += [ + octet1 >> 2, + ((octet1 & 3) << 4) | (octet2 >> 4), + isNaN(octet2) ? 64 : ((octet2 & 15) << 2) | (octet3 >> 6), + isNaN(octet3) ? 64 : octet3 & 63, + ].map(getBase64Char).join(''); + } + + return result; +}; + +/* eslint-enable no-bitwise */ + +export const isUnaryOperation = function (crit: unknown): boolean { + return Array.isArray(crit) && crit[0] === '!' && Array.isArray(crit[1]); }; -const isGroupOperator = function (value) { +const isGroupOperator = function (value: unknown): boolean { return value === 'and' || value === 'or'; }; -export const isUniformEqualsByOr = function (crit) { +export const isUniformEqualsByOr = function (crit: unknown[]): boolean { if (crit.length > 2 && Array.isArray(crit[0]) && crit[1] === 'or' && typeof crit[0][0] === 'string' && crit[0][1] === '=') { const [prop] = crit[0]; return !crit.find((el, i) => (i % 2 !== 0 ? el !== 'or' @@ -245,7 +294,11 @@ export const isUniformEqualsByOr = function (crit) { return false; }; -export const isGroupCriterion = function (crit) { +export const isGroupCriterion = function (crit: unknown): boolean { + if (!Array.isArray(crit)) { + return false; + } + const first = crit[0]; const second = crit[1]; @@ -261,21 +314,29 @@ export const isGroupCriterion = function (crit) { return false; }; -export const trivialPromise = function () { - // @ts-expect-error - const d = new Deferred(); - return d.resolve.apply(d, arguments).promise(); +export const trivialPromise = function (...args: T[]): DeferredObj { + const d = Deferred(); + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return d.resolve(...args).promise(); }; -export const rejectedPromise = function () { - // @ts-expect-error - const d = new Deferred(); - return d.reject.apply(d, arguments).promise(); +export const rejectedPromise = function (...args: T[]): DeferredObj { + const d = Deferred(); + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return d.reject(...args).promise(); }; -function throttle(func, timeout) { - let timeoutId; - return function () { +type ThrottleTimeout = number | (() => number); + +type TimeoutId = ReturnType; + +function throttle( + func: (this: unknown) => void, + timeout: ThrottleTimeout, +): (this: unknown) => TimeoutId | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let timeoutId: TimeoutId | undefined; + return function (this: unknown): TimeoutId | undefined { if (!timeoutId) { timeoutId = setTimeout(() => { timeoutId = undefined; @@ -286,19 +347,20 @@ function throttle(func, timeout) { }; } -export function throttleChanges(func, timeout) { - let cache = []; - const throttled = throttle(function () { +export function throttleChanges( + func: (this: unknown, changes: T[]) => void, + timeout: ThrottleTimeout, +): (this: unknown, changes: T[]) => TimeoutId | undefined { + let cache: T[] = []; + const throttled = throttle(function (this: unknown): void { func.call(this, cache); cache = []; }, timeout); - return function (changes) { + return function (this: unknown, changes: T[]): TimeoutId | undefined { if (Array.isArray(changes)) { - // @ts-expect-error cache.push(...changes); } - // @ts-expect-error - return throttled.call(this, cache); + return throttled.call(this); }; } diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts index 608c733636ba..e8886c114650 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts @@ -105,6 +105,7 @@ const dataSourceAdapterExtender = (Base: ModuleType) => class groups[i].isExpanded = group[i].isExpanded; } } + // @ts-expect-error normalizeSortingInfo() types selector wider than public KeySelector dataSource.group(groups); that._grouping.foreachGroups((groupInfo, parents) => { if (groupIndex === undefined || groupIndex === parents.length - 1) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts index 023624dca8b4..f153e81939df 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts @@ -343,7 +343,6 @@ export class GroupingHelper extends GroupingHelperCore { if (groupCount) { let { data } = options; - // @ts-expect-error const query = dataQuery(data); storeHelper.multiLevelGroup(query, groups).enumerate().done((groupedData) => { data = groupedData; diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts index baeb374a1b7b..b6d8e21f13df 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts @@ -117,7 +117,6 @@ const sortGroupsBySummaryCore = function (items, groups, sortByGroups) { let query; if (group && sorts && sorts.length) { - // @ts-expect-error query = dataQuery(items); each(sorts, function (index) { if (index === 0) { @@ -307,7 +306,6 @@ export const summaryDataSourceAdapterExtender = ( } private sortLastLevelGroupItems(items, groups, paths) { - // @ts-expect-error const groupedItems = storeHelper.multiLevelGroup(dataQuery(items), groups).toArray(); let result = []; diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts index aeab55d20f08..859b58d185bc 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts @@ -1823,7 +1823,6 @@ export class ColumnsController extends modules.Controller { } if (isPlainObject(dataSource) || (dataSource instanceof Store) || Array.isArray(dataSource)) { if (that.valueExpr) { - // @ts-expect-error const dataSourceOptions = normalizeDataSourceOptions(dataSource); dataSourceOptions.paginate = false; dataSource = new DataSource(dataSourceOptions); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index 2a4db0edfc7c..10c8b496f377 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -297,7 +297,6 @@ export default class DataSourceAdapter extends modules.Controller { this.resetPagesCache(true); if (this._cachedStoreData) { - // @ts-expect-error applyBatch({ keyInfo: store, data: this._cachedStoreData, @@ -365,7 +364,6 @@ export default class DataSourceAdapter extends modules.Controller { const getItemCount = () => (groupCount ? this.itemsCount() : this.items().length); const oldItemCount = getItemCount(); - // @ts-expect-error applyBatch({ keyInfo, data: this._items, @@ -374,7 +372,6 @@ export default class DataSourceAdapter extends modules.Controller { useInsertIndex: true, skipCopying: !this._needToCopyDataObject(), }); - // @ts-expect-error applyBatch({ keyInfo, data: dataSource.items(), diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 10d151fa7380..5a11f96b354a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -79,13 +79,11 @@ function ungroupUTCDates(items, dateParts?, dates?) { export function convertDataFromUTCToLocal(data, column) { const dates = ungroupUTCDates(data); - // @ts-expect-error const query = dataQuery(dates); const group = gridCoreUtils.getHeaderFilterGroupParameters({ ...column, calculateCellValue: (date) => date, }); - // @ts-expect-error return storeHelper.queryByOptions(query, { group }).toArray(); } @@ -245,7 +243,6 @@ export class HeaderFilterController extends Modules.ViewController { if (!dataSource) return; if (isDefined(headerFilterDataSource) && !isFunction(headerFilterDataSource)) { - // @ts-expect-error options.dataSource = normalizeDataSourceOptions(headerFilterDataSource); } else if (column.lookup) { isLookup = true; @@ -275,6 +272,7 @@ export class HeaderFilterController extends Modules.ViewController { dataSource.customLoader.load(options).done(({ data }) => { const convertUTCDates = remoteGrouping && isUTCFormat(column.serializationFormat) && cutoffLevel > 3; if (convertUTCDates) { + // @ts-expect-error queryByOptions().toArray() is typed as unknown[] data = convertDataFromUTCToLocal(data, column); } that._processGroupItems(data, null, null, { diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index ae057d8d7009..abdc6d0299b8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -674,7 +674,6 @@ export default { lookupDataSourceOptions = lookupDataSourceOptions({}); } } - // @ts-expect-error return normalizeDataSourceOptions(lookupDataSourceOptions); }, diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index f35045af3838..71faffb9cc83 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -106,9 +106,7 @@ const dataController = ( const filterValue = parseValue(column, text); if (lookup?.items) { - // @ts-expect-error dataQuery(lookup.items, { langParams }) - // @ts-expect-error .filter( column.createFilterExpression.call( { diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts index b03222a5303b..24c6f5c90bb8 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts @@ -35,7 +35,7 @@ export function normalizeDataSource( }; } - // TODO: research making second param not required + // @ts-expect-error the public DataSource constructor takes the public option types return new DataSource(normalizeDataSourceOptions(dataSourceLike, undefined)); } diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts index 761f198bce92..1072316e25ba 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts @@ -158,7 +158,6 @@ export const getDataSourceOptions = ( const options: any = {}; if (isDefined(headerFilterDataSource) && !isFunction(headerFilterDataSource)) { - // @ts-expect-error options.dataSource = oldNormalizeDataSourceOptions(headerFilterDataSource); } else { const cutoffLevel = Array.isArray(group) ? group.length - 1 : 0; diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts b/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts index b5b1683bc03f..ef1a61eaa54e 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts @@ -341,7 +341,6 @@ class PivotGridDataSource { createLocalOrRemoteStore(dataSourceOptions, notifyProgress) { const StoreConstructor = dataSourceOptions.remoteOperations || dataSourceOptions.paginate ? RemoteStore : LocalStore; - // @ts-expect-error return new StoreConstructor(extend(normalizeDataSourceOptions(dataSourceOptions), { onChanged: null, onLoadingChanged: null, diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts b/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts index 174166bf6091..4ee6f3179b67 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts @@ -424,7 +424,6 @@ const LocalStore = Class.inherit((function () { if (dataSource.store() instanceof CustomStore && filter) { filter = processFilter(filter, fieldSelectors); - // @ts-expect-error return dataQuery(dataSource.items()).filter(filter).toArray(); } diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts b/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts index 3412120c14f2..b514df7df779 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts @@ -561,7 +561,6 @@ class RemoteStore { skip: 0, take: 20, }).done((data) => { - // @ts-expect-error const normalizedArguments = normalizeLoadResult(data); d.resolve(pivotGridUtils.discoverObjectFields(normalizedArguments.data, fields)); }).fail(d.reject); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index ae7cc27ff427..35ad2a94f1b2 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -45,9 +45,7 @@ const getChildKeys = function (that, keys) { return childKeys; }; -// @ts-expect-error const applySorting = (data: any[], sort: any): any => queryByOptions( - // @ts-expect-error query(data), { sort, @@ -163,7 +161,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { result = result || []; for (let i = 0; i < data.length; i++) { - // @ts-expect-error const item = createObjectWithChanges(data[i]); key = this._keyGetter(item); @@ -417,7 +414,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { if (loadedData.length) { if (needLocalFiltering) { - // @ts-expect-error loadedData = query(loadedData).filter(filter).toArray(); } @@ -563,7 +559,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { public customizeLoadResultHandler(options) { const data = options.data = this._convertDataToPlainStructure(options.data); if (!options.remoteOperations.filtering && options.loadOptions.filter) { - // @ts-expect-error options.fullData = queryByOptions(query(options.data), { sort: options.loadOptions && options.loadOptions.sort }).toArray(); } this._updateHasItemsMap(options); diff --git a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts index 8a2f31585a13..982fd38239af 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts @@ -19,6 +19,7 @@ export const normalizeDataSource = ( ...options, }; + // @ts-expect-error the public DataSource constructor takes the public option types return new DataSource(result); }; diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts index 06d89a472973..85bcffe6447f 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts @@ -277,7 +277,6 @@ class CollectionWidget< if (that._disposed) { return; } - // @ts-expect-error arguments const items = normalizeLoadResult(loadResult).data; dataController.applyMapFunction(items); diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts index d1d0c0fdfd0b..a73e374f5136 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts @@ -204,7 +204,6 @@ class CollectionWidgetLiveUpdate< } else { const changedItem = items[indexByKey(keyInfo, items, change.key)]; if (changedItem) { - // @ts-expect-error ts-error update(keyInfo, items, change.key, change.data).done(() => { this._renderItem( items.indexOf(changedItem), @@ -224,7 +223,6 @@ class CollectionWidgetLiveUpdate< isPartialRefresh?: boolean, ): void { when( - // @ts-expect-error ts-error isPartialRefresh ?? insert(keyInfo, items, change.data, change.index), ).done(() => { this._beforeItemElementInserted(change); diff --git a/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts b/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts index 4e5370ec834f..c5048c5be6d0 100644 --- a/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts +++ b/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts @@ -691,8 +691,8 @@ class DataAdapter { lookForParents(matches, 0); if (this.options.sort) { + // @ts-expect-error queryByOptions().toArray() is typed as unknown[] matches = storeHelper - // @ts-expect-error ts-error .queryByOptions(query(matches), { sort: this.options.sort, langParams: this.options.langParams, diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts index d6e370a5d233..a261105c07d6 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts @@ -89,7 +89,6 @@ export default class DeferredStrategy< const queryParams = this._getQueryParams(); - // @ts-expect-error dataQuery return !!dataQuery([itemData], queryParams).filter(selectionFilter).toArray().length; } diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts index 4e1ae4bd8b5d..8e711f66e938 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts @@ -139,11 +139,11 @@ export default class StandardStrategy< forceCombinedFilter, ); - let deselectedItems = []; + let deselectedItems: TItem[] = []; if (isDeselect) { const { selectedItems } = this.options; + // @ts-expect-error dataQuery().toArray() is typed as unknown[] deselectedItems = combinedFilter && keys.length !== selectedItems.length - // @ts-expect-error dataQuery ? dataQuery(selectedItems).filter(combinedFilter).toArray() : selectedItems.slice(0); } diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts index 932eb2111af9..1a7b9056d022 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts @@ -199,7 +199,6 @@ export default class SelectionStrategy< if (localFilter && !isSelectAll) { filteredItems = filteredItems.filter(localFilter); } else if (needLoadAllData) { - // @ts-expect-error dataQuary filteredItems = dataQuery(filteredItems).filter(remoteFilter).toArray(); } diff --git a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts index 0458f8f1c274..501d8e2b300b 100644 --- a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts +++ b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts @@ -180,7 +180,6 @@ function prepareLookupEditor(options): void { } if (isObject(dataSource) || Array.isArray(dataSource)) { - // @ts-expect-error ts- dataSource = normalizeDataSourceOptions(dataSource); if (isFilterRow) { postProcess = dataSource.postProcess; From 8c9dd6efa47240a5441c865e4366aa8c160ecf25 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Mon, 7 Sep 2026 15:50:12 +0000 Subject: [PATCH 17/45] chore(deps): bump axe to v4.13.0 (#34935) --- .../Angular/app/app.component.html | 7 +++- .../Stepper/StepTemplate/React/IconOnly.tsx | 5 +-- .../Stepper/StepTemplate/ReactJs/IconOnly.js | 10 ++++-- .../Demos/Stepper/StepTemplate/Vue/App.vue | 6 +++- .../Stepper/StepTemplate/jQuery/index.js | 4 ++- .../Overview/Angular/app/app.component.css | 2 +- .../Demos/TabPanel/Overview/React/styles.css | 2 +- .../TabPanel/Overview/ReactJs/styles.css | 2 +- .../Demos/TabPanel/Overview/Vue/TaskItem.vue | 2 +- .../Demos/TabPanel/Overview/jQuery/styles.css | 2 +- apps/demos/testing/common.test.ts | 15 ++++++++- .../accessibility/scheduler/appointment.ts | 28 ++++++++-------- .../accessibility/scheduler/axe_options.ts | 10 ++++++ .../accessibility/scheduler/scheduler.ts | 10 +++--- .../widgets/fluent/tabs/layout/tab/_base.scss | 26 ++++----------- pnpm-lock.yaml | 32 +++++++++---------- pnpm-workspace.yaml | 2 +- 17 files changed, 96 insertions(+), 69 deletions(-) diff --git a/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html b/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html index 0128c7d521dd..e1c08fa86bc7 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html +++ b/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html @@ -41,6 +41,11 @@ [elementAttr]="{ 'aria-labelledby': 'label-iconOnly' }" >
- +
diff --git a/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx b/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx index d84e95907c85..bb0e60634491 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx +++ b/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx @@ -2,10 +2,11 @@ import React from 'react'; interface IconOnlyProps { icon: string; + label: string; } -export default function IconOnly({ icon }: IconOnlyProps) { +export default function IconOnly({ icon, label }: IconOnlyProps) { return ( - + ); } diff --git a/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js b/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js index 1344ad909c1d..466eafa507af 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js +++ b/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js @@ -1,5 +1,11 @@ import React from 'react'; -export default function IconOnly({ icon }) { - return ; +export default function IconOnly({ icon, label }) { + return ( + + ); } diff --git a/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue b/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue index a0a8ea0375a2..2fd89b68b2c9 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue +++ b/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue @@ -57,7 +57,11 @@ > diff --git a/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js b/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js index fd5945874d17..a80ea8666056 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js +++ b/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js @@ -33,7 +33,9 @@ $(() => { linear: false, elementAttr: { 'aria-labelledby': 'label-iconOnly' }, itemTemplate(data) { - return ``; + return $('') + .addClass(`dx-icon dx-icon-${data.icon}`) + .attr({ role: 'img', 'aria-label': data.label }); }, }); }); diff --git a/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css b/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css index 046af40d3171..320a5f6dc23e 100644 --- a/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css +++ b/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css @@ -104,7 +104,7 @@ ::ng-deep .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } ::ng-deep .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/React/styles.css b/apps/demos/Demos/TabPanel/Overview/React/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/React/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/React/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css b/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue b/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue index bfe6973f9762..76699ee28f0d 100644 --- a/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue +++ b/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue @@ -66,7 +66,7 @@ withDefaults(defineProps<{ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css b/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/testing/common.test.ts b/apps/demos/testing/common.test.ts index d318a8b6cde1..40c4cd51fe31 100644 --- a/apps/demos/testing/common.test.ts +++ b/apps/demos/testing/common.test.ts @@ -111,6 +111,10 @@ const getIgnoredRules = (testName) => { 'Gantt-Validation': ['aria-required-parent', 'aria-valid-attr-value'], 'Localization-UsingGlobalize': ['label'], + + // Icon-only tabs render no text, so they have no accessible name. + // Naming them needs Tabs widget support (like Button labels icon-only buttons). + 'Tabs-Overview': ['aria-tab-name'], }; return [ @@ -119,6 +123,15 @@ const getIgnoredRules = (testName) => { ]; }; +// The appointment title and date do not reach the AA contrast ratio: the date line is dimmed +// to 70% opacity and the appointments are painted with the resource colors. +const getAxeContext = (testName) => (testName.startsWith('Scheduler-') + ? { + include: ['.demo-container'], + exclude: ['.dx-scheduler-appointment-title', '.dx-scheduler-appointment-content-date'], + } + : '.demo-container'); + const getClientScripts = (approach: string) => { const scripts = [ { module: 'mockdate' }, @@ -250,7 +263,7 @@ Object.values(FRAMEWORKS).forEach((approach) => { options.rules[ruleName] = { enabled: false }; }); - const axeResult = await axeCheck(t, '.demo-container', options); + const axeResult = await axeCheck(t, getAxeContext(testName), options); const { error, results } = axeResult; if (results.violations.length > 0) { diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts index 6a7727e2871a..f9251fd8e70b 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts @@ -2,7 +2,7 @@ import Scheduler from 'devextreme-testcafe-models/scheduler'; import { a11yCheck } from '../../../helpers/accessibility/utils'; import { createWidget } from '../../../helpers/createWidget'; import url from '../../../helpers/getPageUrl'; -import { a11yCheckConfig } from './axe_options'; +import { a11yCheckConfig, a11yContext } from './axe_options'; fixture.disablePageReloads`Scheduler - Appointment` .page(url(__dirname, '../../container.html')); @@ -26,7 +26,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.hasAriaDescription()) .ok(); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -46,7 +46,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.hasAriaDescription()) .ok(); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -67,7 +67,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource1; Group 1: resource1'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -101,7 +101,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource1; Group 1: resource1'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -136,7 +136,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource11, resource21; Group 1: resource11; Group 2: resource21, resource22'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -180,7 +180,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(recurrenceIcon.getAttribute('aria-label')) .eql('Recurring appointment'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -210,7 +210,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(appt.element.getAttribute('aria-activedescendant')) .eql(null); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -266,7 +266,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex await t.expect(apptLabels[1]).eql(labels[1]); await t.expect(apptLabels[2]).eql(labels[2]); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -336,7 +336,7 @@ test('appointments & collector buttons can be navigated', async (t) => { scheduler.appointmentTooltip.element.count, ).eql(1); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [ @@ -387,7 +387,7 @@ test('Scheduler a11y: Disabled time ranges are not supported', async (t) => { .expect(actualNextAriaLabel) .eql(expectedAriaLabels.next); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [ @@ -410,7 +410,7 @@ test('Scheduler a11y: appointments does not have info about reccurence', async ( .expect(recurrenceIcon.getAttribute('aria-label')) .eql('Recurring appointment'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -440,7 +440,7 @@ test('Scheduler a11y: Appointment collector button doesn\'t have info about date .expect(schedulerCollector.element().getAttribute('aria-roledescription')) .contains(dateText); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -479,7 +479,7 @@ test('appointment aria label should contain date with right timezone', async (t) .expect(appointment.getAriaLabel()) .eql('Install New Router in Dev Room: March 29, 2021, 2:30 PM - 3:30 PM'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts index f0717745a987..c07b0adad4bb 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts @@ -1 +1,11 @@ export const a11yCheckConfig = {}; + +// The appointment title and date do not reach the AA contrast ratio: +// the date line is dimmed to 70% opacity over the accent-colored appointment. +export const a11yContext = { + include: ['#container'], + exclude: [ + '.dx-scheduler-appointment-title', + '.dx-scheduler-appointment-content-date', + ], +}; diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts index 7ba23fe3582b..88297d703858 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts @@ -2,7 +2,7 @@ import Scheduler from 'devextreme-testcafe-models/scheduler'; import { a11yCheck } from '../../../helpers/accessibility/utils'; import { createWidget } from '../../../helpers/createWidget'; import url from '../../../helpers/getPageUrl'; -import { a11yCheckConfig } from './axe_options'; +import { a11yCheckConfig, a11yContext } from './axe_options'; fixture.disablePageReloads`Scheduler - a11y` .page(url(__dirname, '../../container.html')); @@ -20,7 +20,7 @@ test('Scheduler should have right aria attributes after view changed', async (t) await t.expect(scheduler.element.getAttribute('aria-label')).contains('Scheduler. Week view'); await t.expect(scheduler.getGeneralStatusContainer().textContent).contains('Scheduler. Week view'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [], @@ -40,7 +40,7 @@ test('Scheduler table elements have right aria attributes', async (t) => { ).eql('true'); } - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [], @@ -52,7 +52,7 @@ test('Scheduler table elements have right aria attributes', async (t) => { 'agenda', 'day', 'month', 'timelineDay', 'timelineMonth', 'timelineWeek', 'timelineWorkWeek', 'week', 'workWeek', ].forEach((currentView) => { test(`Scheduler has no axe errors on view ${currentView}`, async (t) => { - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -108,7 +108,7 @@ const groupingCases = [ ] as const).forEach(({ name, dataSource, parentIdExpr }) => { groupingCases.forEach(({ view, orientation }) => { test(`Scheduler should pass accessibility checks with ${name} grouping on view ${view} (${orientation})`, async (t) => { - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: groupedAppointments, diff --git a/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss b/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss index 43392b992139..a55f2877d88d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss @@ -31,21 +31,13 @@ } .dx-tab-text .dx-tab-text-span { - visibility: visible; - - .dx-tab-text-span-pseudo { - visibility: hidden; - } + font-weight: $fluent-tab-font-weight-accent; } } &.dx-state-disabled { color: $tabs-tab-disabled-color; - .dx-tab-text .dx-tab-text-span-pseudo { - color: $tabs-tab-disabled-color; - } - .dx-icon { color: $tabs-tab-icon-disabled-color; } @@ -61,22 +53,16 @@ line-height: $fluent-line-height; .dx-tab-text-span { - position: relative; display: block; - font-weight: $fluent-tab-font-weight-accent; - visibility: hidden; + font-weight: $fluent-tab-font-weight-basic; } .dx-tab-text-span-pseudo { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + display: block; + height: 0; pointer-events: none; - color: $tabs-tab-color; - font-weight: $fluent-tab-font-weight-basic; - visibility: visible; + font-weight: $fluent-tab-font-weight-accent; + visibility: hidden; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7afce36ee551..38ad9bbed3fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,8 +83,8 @@ catalogs: specifier: 8.52.0 version: 8.52.0 axe-core: - specifier: 4.11.3 - version: 4.11.3 + specifier: 4.13.0 + version: 4.13.0 devexpress-diagram: specifier: 2.2.31 version: 2.2.31 @@ -262,7 +262,7 @@ importers: version: 17.0.35 axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 chokidar: specifier: 5.0.0 version: 5.0.0 @@ -624,7 +624,7 @@ importers: version: 5.10.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) '@testcafe-community/axe': specifier: 3.5.0 - version: 3.5.0(axe-core@4.11.3)(testcafe@3.7.5) + version: 3.5.0(axe-core@4.13.0)(testcafe@3.7.5) '@types/eslint': specifier: 8.56.12 version: 8.56.12 @@ -657,7 +657,7 @@ importers: version: 0.9.1(typescript@6.0.3)(vue@3.5.32(typescript@6.0.3)) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 babel-jest: specifier: 30.4.1 version: 30.4.1(@babel/core@7.29.7) @@ -1101,7 +1101,7 @@ importers: version: 5.10.0(eslint@9.39.4(jiti@2.6.1)) '@testcafe-community/axe': specifier: 3.5.0 - version: 3.5.0(axe-core@4.11.3)(testcafe@3.7.5) + version: 3.5.0(axe-core@4.13.0)(testcafe@3.7.5) '@types/jquery': specifier: 'catalog:' version: 4.0.1 @@ -1113,7 +1113,7 @@ importers: version: 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 devextreme: specifier: workspace:* version: link:../../packages/devextreme/artifacts/npm/devextreme @@ -1436,7 +1436,7 @@ importers: version: 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 babel-plugin-inferno: specifier: 6.8.5 version: 6.8.5(@babel/core@7.29.7) @@ -8751,14 +8751,14 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.3: - resolution: {integrity: sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==} - engines: {node: '>=4'} - axe-core@4.12.1: resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} engines: {node: '>=4'} + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} + engines: {node: '>=4'} + axios@1.18.1: resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} @@ -24009,9 +24009,9 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@testcafe-community/axe@3.5.0(axe-core@4.11.3)(testcafe@3.7.5)': + '@testcafe-community/axe@3.5.0(axe-core@4.13.0)(testcafe@3.7.5)': dependencies: - axe-core: 4.11.3 + axe-core: 4.13.0 chalk: 2.4.2 testcafe: 3.7.5(supports-color@7.2.0) @@ -26073,10 +26073,10 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.3: {} - axe-core@4.12.1: {} + axe-core@4.13.0: {} + axios@1.18.1: dependencies: follow-redirects: 1.16.0(debug@4.4.3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cc6a4529add4..005b76c8b0a8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -66,7 +66,7 @@ overrides: "postcss-selector-parser@>=7.1.0 <7.1.3": "^7.1.3" "fflate@>=0.8.0 <0.8.3": "^0.8.3" catalog: - axe-core: 4.11.3 + axe-core: 4.13.0 inferno: ^8.2.3 inferno-hydrate: ^8.2.3 inferno-create-element: ^8.2.3 From 2035e3cc292f82c45dc608e0aa2384442b935834 Mon Sep 17 00:00:00 2001 From: Anna Shakhova <68295572+anna-shakhova@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:33:06 +0000 Subject: [PATCH 18/45] Grids: retarget `dataController._dataSource` readers (#35076) --- .../__tests__/export.integration.test.ts | 119 ++++++++++++++++++ .../grids/data_grid/export/m_export.ts | 6 +- .../data_controller/data_controller.ts | 2 +- ...data_source_controller.integration.test.ts | 14 +-- .../focus/__tests__/focus.integration.test.ts | 70 +++++++++++ .../grids/grid_core/focus/m_focus.ts | 11 +- .../virtual_scrolling/m_virtual_scrolling.ts | 20 ++- .../utils/__tests__/items.test.ts | 8 ++ .../virtual_scrolling/utils/items.ts | 4 +- .../dataController.tests.js | 41 +++--- .../selection.tests.js | 2 +- 11 files changed, 254 insertions(+), 43 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts diff --git a/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts b/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts new file mode 100644 index 000000000000..95e088cca601 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts @@ -0,0 +1,119 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import { Deferred } from '@js/core/utils/deferred'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; + +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '../../../grid_core/__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, value: 'a' }, + { id: 2, value: 'b' }, +]; + +const SELECTED_ROWS_DATA = [DATA[0]]; + +const setup = async (options: DataGridProperties) => { + const { instance } = await createDataGrid({ + dataSource: DATA, + keyExpr: 'id', + selectedRowKeys: [1], + ...options, + }); + + await flushAsync(); + + const exportController = instance.getController('export'); + const selectionController = instance.getController('selection'); + + const filteredItems = Deferred().resolve(SELECTED_ROWS_DATA); + + return { + exportController, + filteredItems, + loadSelectedItemsWithFilter: jest + .spyOn(selectionController, 'loadSelectedItemsWithFilter') + .mockReturnValue(filteredItems), + getSelectedRowsData: jest + .spyOn(selectionController, 'getSelectedRowsData') + .mockReturnValue(SELECTED_ROWS_DATA), + getAllItems: jest.spyOn(exportController, '_getAllItems'), + }; +}; + +describe('ExportController', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('_getSelectedItems', () => { + it('loads the selected items with a filter when loadItemsOnExportingSelectedItems is enabled', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + filteredItems, + } = await setup({ loadItemsOnExportingSelectedItems: true } as DataGridProperties); + + exportController._getSelectedItems(); + + expect(loadSelectedItemsWithFilter).toHaveBeenCalledTimes(1); + expect(getSelectedRowsData).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(filteredItems, true); + }); + + it('loads the selected items with a filter when filtering is a remote operation', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + filteredItems, + } = await setup({ remoteOperations: { filtering: true } }); + + exportController._getSelectedItems(); + + expect(loadSelectedItemsWithFilter).toHaveBeenCalledTimes(1); + expect(getSelectedRowsData).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(filteredItems, true); + }); + + it('uses the already selected rows when every operation is local', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + } = await setup({}); + + exportController._getSelectedItems(); + + expect(getSelectedRowsData).toHaveBeenCalledTimes(1); + expect(loadSelectedItemsWithFilter).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(SELECTED_ROWS_DATA); + }); + + it('uses the already selected rows when loadItemsOnExportingSelectedItems is disabled explicitly, even with remote filtering', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + } = await setup({ + loadItemsOnExportingSelectedItems: false, + remoteOperations: { filtering: true }, + } as DataGridProperties); + + exportController._getSelectedItems(); + + expect(getSelectedRowsData).toHaveBeenCalledTimes(1); + expect(loadSelectedItemsWithFilter).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(SELECTED_ROWS_DATA); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts index e94c768bf7e8..624c83309f76 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts @@ -16,6 +16,7 @@ import List from '@ts/ui/list/list.edit.search'; import type { ColumnHeadersView } from '../../grid_core/column_headers/m_column_headers'; import type { ColumnsController } from '../../grid_core/columns_controller/m_columns_controller'; import type { DataController } from '../../grid_core/data_controller/data_controller'; +import type { DataSourceController } from '../../grid_core/data_source/data_source_controller'; import type { EditingController } from '../../grid_core/editing/m_editing'; import type { HeaderPanel } from '../../grid_core/header_panel/m_header_panel'; import type { ModuleType } from '../../grid_core/m_types'; @@ -337,6 +338,8 @@ export class ExportController extends dataGridCore.ViewController { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _selectionController!: SelectionController; private _headersView!: ColumnHeadersView; @@ -353,6 +356,7 @@ export class ExportController extends dataGridCore.ViewController { this.throwWarningIfNoOnExportingEvent(); this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._selectionController = this.getController('selection'); this._rowsView = this.getView('rowsView'); this._headersView = this.getView('columnHeadersView'); @@ -701,7 +705,7 @@ export class ExportController extends dataGridCore.ViewController { private needLoadItemsOnExportingSelectedItems(): boolean { return this.option('loadItemsOnExportingSelectedItems') - ?? (this._dataController._dataSource?.remoteOperations().filtering ?? false); + ?? (this.dataSourceController.remoteOperations().filtering ?? false); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index ec04bb2b8892..242093e9c26a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -66,7 +66,7 @@ import { import { generateRowValues } from './utils/row_values'; export class DataController extends modules.Controller { - public _dataSource?: DataSourceAdapter | null; + protected _dataSource?: DataSourceAdapter | null; protected _items!: ProcessedItem[]; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts index cfae192b7095..96eefe8ae70f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts @@ -103,7 +103,7 @@ describe('dataSource controller holds the adapter', () => { it('holds the same adapter object as DataController', async () => { const { instance } = await createDataGrid({ dataSource: DATA }); const dataSourceController = instance.getController('dataSource'); - const adapter = instance.getController('data')._dataSource; + const adapter = instance.getController('data').dataSource(); expect(adapter).toBeTruthy(); expect(dataSourceController.hasAdapter()).toBe(true); @@ -120,7 +120,7 @@ describe('dataSource controller holds the adapter', () => { await flushAsync(); expect(dataSourceController.getAdapter()).not.toBe(firstAdapter); - expect(dataSourceController.getAdapter()).toBe(dataController._dataSource); + expect(dataSourceController.getAdapter()).toBe(dataController.dataSource()); }); it('releases the adapter when the dataSource option is cleared', async () => { @@ -130,9 +130,9 @@ describe('dataSource controller holds the adapter', () => { instance.option('dataSource', undefined); await flushAsync(); - expect(instance.getController('data')._dataSource).toBeNull(); expect(dataSourceController.hasAdapter()).toBe(false); expect(dataSourceController.getAdapter()).toBeNull(); + expect(instance.getController('data').dataSource()).toBeUndefined(); expect(dataSourceController.getDataSource()).toBeNull(); expect(dataSourceController.store()).toBeUndefined(); }); @@ -147,7 +147,7 @@ describe('dataSource controller holds the adapter', () => { await flushAsync(); expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); }); it('still holds the same adapter after a refresh', async () => { @@ -159,7 +159,7 @@ describe('dataSource controller holds the adapter', () => { await refreshed; expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); }); it('releases the adapter on dispose', async () => { @@ -170,7 +170,7 @@ describe('dataSource controller holds the adapter', () => { instance.dispose(); $container.remove(); - expect(dataController._dataSource).toBeNull(); + expect(dataController.dataSource()).toBeUndefined(); expect(dataSourceController.hasAdapter()).toBe(false); }); @@ -181,7 +181,7 @@ describe('dataSource controller holds the adapter', () => { const dataSourceController = instance.getController('dataSource'); expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data')._dataSource); + expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); } finally { disposeTreeList($container); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts new file mode 100644 index 000000000000..0fa8c9bfa5d8 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts @@ -0,0 +1,70 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; + +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '../../__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, name: 'Alex' }, + { id: 2, name: 'Dan' }, +]; + +const setup = async (options: DataGridProperties) => { + const { instance } = await createDataGrid({ + keyExpr: 'id', + focusedRowEnabled: true, + ...options, + }); + + await flushAsync(); + + return { + columnsController: instance.getController('columns'), + dataSourceController: instance.getController('dataSource'), + }; +}; + +describe('Focus columns extender', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('getSortDataSourceParameters', () => { + it('sorts by the data index when every operation is local', async () => { + const { columnsController, dataSourceController } = await setup({ dataSource: DATA }); + + const result = columnsController.getSortDataSourceParameters(); + + expect(result).toHaveLength(1); + expect(result[0].selector).toBe(dataSourceController.getDataIndexGetter()); + expect(result[0].desc).toBe(false); + }); + + it('sorts by the key columns when operations are remote', async () => { + const { columnsController } = await setup({ + dataSource: DATA, + remoteOperations: true, + }); + + const result = columnsController.getSortDataSourceParameters(); + + expect(result).toEqual([{ selector: 'id', desc: false }]); + }); + + it('leaves the base sort parameters alone when there is no data source', async () => { + const { columnsController, dataSourceController } = await setup({ + columns: [{ dataField: 'name', sortOrder: 'asc' }], + }); + + expect(dataSourceController.getAdapter()).toBeNull(); + expect(columnsController.getSortDataSourceParameters()).toEqual([ + { selector: 'name', desc: false }, + ]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index fb91bbd9d68d..ae40935ea655 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -9,6 +9,7 @@ import type { Key } from '@ts/grids/new/grid_core/data_controller/types'; import type { ColumnsController } from '../columns_controller/m_columns_controller'; import type { DataController } from '../data_controller/data_controller'; +import type { DataSourceController } from '../data_source/data_source_controller'; import type { EditingController } from '../editing/m_editing'; import { isNewRowTempKey } from '../editing/m_editing_utils'; import type { EditorFactory } from '../editor_factory/m_editor_factory'; @@ -534,8 +535,11 @@ const focusEditorFactoryViewControllerExtender = ( const columns = (Base: ModuleType) => class FocusColumnsExtender extends Base { protected focusController!: FocusController; + protected dataSourceController!: DataSourceController; + public init(isApplyingUserState?: boolean): void { this.focusController = this.getController('focus'); + this.dataSourceController = this.getController('dataSource'); super.init(isApplyingUserState); } @@ -543,9 +547,8 @@ const columns = (Base: ModuleType) => class FocusColumnsExten public getSortDataSourceParameters(_, sortByKey?) { // @ts-expect-error let result = super.getSortDataSourceParameters.apply(this, arguments); - const dataSource = this._dataController._dataSource; - let key = dataSource?.store()?.key(); - const remoteOperations = dataSource && dataSource.remoteOperations() || {}; + let key = this.dataSourceController.key(); + const remoteOperations = this.dataSourceController.remoteOperations(); const isLocalOperations = Object.keys(remoteOperations).every((operationName) => !remoteOperations[operationName]); if (key && (this.option('focusedRowEnabled') && this.focusController.isAutoNavigateToFocusedRow() !== false || sortByKey)) { @@ -555,7 +558,7 @@ const columns = (Base: ModuleType) => class FocusColumnsExten if (notSortedKeys.length) { result = result || []; if (isLocalOperations) { - result.push({ selector: dataSource?.getDataIndexGetter(), desc: false }); + result.push({ selector: this.dataSourceController.getDataIndexGetter(), desc: false }); } else { notSortedKeys.forEach((notSortedKey) => result.push({ selector: notSortedKey, desc: false })); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts index 8a8071655a3b..ca5ae364efd4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts @@ -11,6 +11,7 @@ import { getWindow } from '@js/core/utils/window'; import LoadIndicator from '@js/ui/load_indicator'; import errors from '@js/ui/widget/ui.errors'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -39,6 +40,10 @@ import { isInfiniteMode, isVirtualMode, isVirtualPaging } from './utils/scrollin export type VirtualScrollingDataSourceAdapter = InstanceType>; +interface VirtualScrollingDataSourceController extends DataSourceController { + getAdapter: () => VirtualScrollingDataSourceAdapter | null; +} + export const updateLoading = function (that) { const beginPageIndex = that._virtualScrollController.beginPageIndex(-1); @@ -488,6 +493,8 @@ export const resizing = (Base: ModuleType) => class VirtualS export const rowsView = (Base: ModuleType) => class VirtualScrollingRowsViewExtender extends Base { protected _dataController!: DataController & Partial; + protected dataSourceController!: VirtualScrollingDataSourceController; + protected _errorHandlingController!: ErrorHandlingViewController; private _isFixedTableRendering: any; @@ -502,6 +509,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo super.init(); this._errorHandlingController = this.getController('errorHandling'); + this.dataSourceController = this.getController('dataSource') as VirtualScrollingDataSourceController; this._dataController.pageChanged.add((pageIndex) => { const scrollTop = this._scrollTop; @@ -581,18 +589,18 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo const deferred = super._renderCore.apply(this, arguments as any); - const dataSource = this._dataController._dataSource as VirtualScrollingDataSourceAdapter | null | undefined; + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource && e) { + if (dataSourceAdapter && e) { const itemCount = e.items ? e.items.length : 20; const viewportSize = this._dataController // @ts-expect-error .viewportSize() || 20; if (gridCoreUtils.isVirtualRowRendering(this) && itemCount > 0 && this.option(LEGACY_SCROLLING_MODE) !== false) { - dataSource._renderTime = (Date.now() - startRenderTime) * viewportSize / itemCount; + dataSourceAdapter._renderTime = (Date.now() - startRenderTime) * viewportSize / itemCount; } else { - dataSource._renderTime = Date.now() - startRenderTime; + dataSourceAdapter._renderTime = Date.now() - startRenderTime; } } return deferred; @@ -707,7 +715,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo private _correctRowHeights(rowHeights) { const dataController = this._dataController; - const dataSource = dataController._dataSource; + const dataSourceAdapter = this.dataSourceController.getAdapter(); const correctedRowHeights: any = []; const visibleRows = dataController.getVisibleRows(); let itemSize = 0; @@ -726,7 +734,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo itemSize = 0; } lastLoadIndex = currentItem.loadIndex; - } else if (isItemCountableByDataSource(currentItem, dataSource as unknown as GroupCountableDataSource)) { + } else if (isItemCountableByDataSource(currentItem, dataSourceAdapter as unknown as GroupCountableDataSource)) { if (firstCountableItem) { firstCountableItem = false; } else { diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts index e1c1680aad3b..ac6f3d39f1a3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts @@ -96,6 +96,14 @@ describe('isItemCountableByDataSource', () => { it('should not count other row types', () => { expect(isItemCountableByDataSource(asItem({ rowType: 'groupFooter' }), dataSource)).toBe(false); }); + + it('should not count a group row when there is no data source', () => { + expect(isItemCountableByDataSource(asItem({ rowType: 'group', data: 'countable' }), null)).toBe(false); + }); + + it('should still count a data row when there is no data source', () => { + expect(isItemCountableByDataSource(asItem({ rowType: 'data', isNewRow: false }), null)).toBe(true); + }); }); describe('updateItemIndices', () => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts index 28412664c581..17612088f962 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts @@ -30,9 +30,9 @@ export const correctCount = ( export const isItemCountableByDataSource = ( item: ProcessedItem, - dataSource: GroupCountableDataSource, + dataSourceAdapter: GroupCountableDataSource | null | undefined, ): boolean => (item.rowType === 'data' && !item.isNewRow) - || (item.rowType === 'group' && dataSource.isGroupItemCountable(item.data)); + || (item.rowType === 'group' && (dataSourceAdapter?.isGroupItemCountable(item.data) ?? false)); export const updateItemIndices = (items: ProcessedItem[]): ProcessedItem[] => { items.forEach((item, index) => { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js index 4b78e7d2de73..ddf33f7e262b 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js @@ -344,7 +344,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // assert assert.ok(dataSource._disposed, 'dataSource is disposed'); - assert.strictEqual(this.dataController._dataSource, null, 'dataSourceAdapter is removed'); + assert.strictEqual(this.dataSourceController.getAdapter(), null, 'dataSourceAdapter is removed'); }); // T697860 @@ -503,7 +503,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }, { selector: 'age', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }, { selector: 'age', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[1].data.key, 30); assert.equal(changedCallCount, 1, 'changed called one time'); // T122785 @@ -535,7 +535,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.deepEqual(this.dataController.items()[0].data.collapsedItems, [{ name: 'Alex', age: 30 }]); assert.equal(this.dataController.items()[0].data.items, null); @@ -577,7 +577,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(loadingCount, 1, 'loading called one time'); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[0].data.items.length, 1); @@ -590,7 +590,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(changedCallCount, 1, 'changed called one time'); // T372049 assert.equal(loadingCount, 1, 'loading is not called on collapseAll'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.strictEqual(this.dataController.items()[0].data.items, null); }); @@ -632,7 +632,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.deepEqual(loadingArgs[0].group, [{ selector: 'name', isExpanded: true, desc: false }], 'loading group arg'); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[0].data.items.length, 1); @@ -645,7 +645,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(changedCallCount, 1, 'changed called one time after collapseAll'); // T372049 assert.equal(loadingArgs.length, 1, 'loading is not called after collapseAll'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.strictEqual(this.dataController.items()[0].data.items, null); }); @@ -873,8 +873,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act this.dataController.resetDataSource(); - const dataIndexGetter = this.dataController._dataSource.getDataIndexGetter(); - this.dataController._dataSource.getDataIndexGetter(); + const dataIndexGetter = this.dataSourceController.getAdapter().getDataIndexGetter(); assert.deepEqual(this.dataController._columnsController.getSortDataSourceParameters(), [{ desc: false, selector: dataIndexGetter }], 'Sort parameters'); // assert assert.equal(dataSource.items()[0].name, 'Alex', 'Item0'); @@ -1955,7 +1954,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'team', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'team', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'internal'); assert.equal(this.dataController.items()[1].data.name, 'Bob'); }); @@ -2028,7 +2027,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController._dataSource.group()); + assert.ok(this.dataSourceController.getAdapter().group()); assert.equal(this.dataController.items()[0].rowType, 'group'); }); @@ -2080,8 +2079,8 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.deepEqual(this.dataController._dataSource.sort(), [{ selector: 'age', desc: false }], 'sort from user state is applied by dataField'); - assert.deepEqual(this.dataController._dataSource.group(), null, 'group from user state not applied because dataSource not has name column'); + assert.deepEqual(this.dataSourceController.getAdapter().sort(), [{ selector: 'age', desc: false }], 'sort from user state is applied by dataField'); + assert.deepEqual(this.dataSourceController.getAdapter().group(), null, 'group from user state not applied because dataSource not has name column'); assert.deepEqual(this.dataController.items().length, 3); assert.deepEqual(this.dataController.items()[0].values, [20]); assert.deepEqual(this.dataController.items()[1].values, [25]); @@ -3579,7 +3578,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft // B233350 QUnit.test('virtual items on end when visibleRowsCount < pageSize and last page size greater than half page size', function(assert) { const dataController = this.dataController; - dataController._dataSource.store().remove(999); + this.dataSourceController.getAdapter().store().remove(999); dataController.refresh(); dataController.setViewportItemIndex(998); assert.strictEqual(this.dataController.pageIndex(), 49); @@ -3683,7 +3682,7 @@ const setupVirtualRenderingModule = function() { this.dataController.viewportItemSize(10); this.dataController.viewportSize(10); - this.dataController._dataSource._renderTime = 50; + this.dataSourceController.getAdapter()._renderTime = 50; this.clock.tick(10); @@ -5700,7 +5699,7 @@ QUnit.module('Infinite scrolling', { // T193217 QUnit.test('setViewportItemIndex to end current page several times load next page one time', function(assert) { let loadingCount = 0; - this.dataController._dataSource.customizeStoreLoadOptions.add(function() { + this.dataSourceController.getAdapter().customizeStoreLoadOptions.add(function() { loadingCount++; }); // act @@ -7845,7 +7844,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains', filterValue: 'Al' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -7867,7 +7866,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains', filterValue: 'Al' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -7889,7 +7888,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -14411,7 +14410,7 @@ QUnit.module('Using DataSource instance', { this.clock.tick(10); assert.equal(this.columnsController.getGroupColumns().length, 1, 'grouped columns count'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'field1', desc: false, isExpanded: true }], 'dataSource group when autoExpandAll true'); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'field1', desc: false, isExpanded: true }], 'dataSource group when autoExpandAll true'); // act this.option('grouping.autoExpandAll', false); @@ -14420,7 +14419,7 @@ QUnit.module('Using DataSource instance', { // assert assert.equal(this.columnsController.getGroupColumns().length, 1, 'grouped columns count'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'field1', desc: false, isExpanded: false }], 'dataSource group when autoExpandAll false'); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'field1', desc: false, isExpanded: false }], 'dataSource group when autoExpandAll false'); assert.equal(this.dataController.totalItemsCount(), 2); assert.equal(this.dataController.items().length, 2); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js index ad017bce1050..115ab7f16195 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js @@ -3192,7 +3192,7 @@ QUnit.module('Selection when grouping', { this.dataController.pageIndex(1); this.selectionController.changeItemSelection(1); - assert.ok(this.dataController._dataSource.items()[0].isContinuation); + assert.ok(this.dataSourceController.getAdapter().items()[0].isContinuation); assert.ok(!this.dataController.items()[0].isSelected); assert.ok(this.dataController.items()[1].isSelected); assert.ok(!this.dataController.items()[2].isSelected); From 4083ca6ec20d8459b537fb13d4d4816e69be263f Mon Sep 17 00:00:00 2001 From: Dmitry Lavrinovich <52966626+dmlvr@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:16:55 +0000 Subject: [PATCH 19/45] Lookup - Screen readers do not pick up NoDataText when Lookup is empty (#35026) --- .../scss/widgets/base/gridBase/_index.scss | 9 -- .../js/__internal/core/widget/widget.ts | 1 + .../views/a11y_status_container_component.ts | 3 +- .../__snapshots__/widget.test.ts.snap | 2 +- .../new/grid_core/accessibility/status.tsx | 3 +- .../options.integration.test.ts.snap | 54 ++++++++ .../view.integration.test.tsx.snap | 12 ++ .../__tests__/__mock__/model/scheduler.ts | 3 +- .../a11y_status/a11y_status_render.ts | 3 +- .../__internal/ui/calendar/calendar.views.ts | 3 +- .../js/__internal/ui/list/list.base.ts | 31 +++++ .../js/__internal/ui/validation_summary.ts | 2 +- .../lookup.tests.js | 56 ++++++++ .../listParts/commonTests.js | 126 ++++++++++++++++-- 14 files changed, 281 insertions(+), 27 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss index 8a59bf54afba..2c56c22f1444 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss @@ -17,15 +17,6 @@ flex: 0 0 auto; } -// NOTE: a11y aria-live container must be visible to allow screen readers read it -.dx-gridbase-a11y-status-container { - position: fixed; - left: 0; - top: 0; - clip: rect(1px, 1px, 1px, 1px); - clip-path: polygon(0 0); -} - @mixin grid-base($widget-name) { @include grid-base-cell($widget-name); diff --git a/packages/devextreme/js/__internal/core/widget/widget.ts b/packages/devextreme/js/__internal/core/widget/widget.ts index 7a9b950fecbd..479d82c583cb 100644 --- a/packages/devextreme/js/__internal/core/widget/widget.ts +++ b/packages/devextreme/js/__internal/core/widget/widget.ts @@ -33,6 +33,7 @@ export const DISABLED_STATE_CLASS = 'dx-state-disabled'; export const ACTIVE_STATE_CLASS = 'dx-state-active'; export const FOCUSED_STATE_CLASS = 'dx-state-focused'; export const HOVER_STATE_CLASS = 'dx-state-hover'; +export const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; const INVISIBLE_STATE_CLASS = 'dx-state-invisible'; export const EMPTY_ACTIVE_STATE_UNIT = ''; diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts b/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts index 875af6837f7d..824b1e86c46c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts @@ -1,8 +1,9 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; const CLASSES = { - container: 'dx-gridbase-a11y-status-container', + container: SCREEN_READER_ONLY_CLASS, }; export interface A11yStatusContainerComponentProps { diff --git a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap index d3a512588033..b10cd6f13ea4 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap +++ b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap @@ -10,7 +10,7 @@ exports[`common initial render should be successfull 1`] = ` role="group" >
+
+ Items: 2 +
+
+ Items: 2 +
+
+ Items: 2 +
+
+ Items: 2 +
+
+ Items: 5 +
+
+ Items: 5 +
+
+ Items: 5 +
+
+ Items: 1 +
+
+ Items: 1 +
+
+ Items: 5 +
+
+ Items: 5 +
$('
') diff --git a/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts b/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts index ee55cec31e05..6383dbca9d1c 100644 --- a/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts +++ b/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts @@ -8,6 +8,7 @@ import dateSerialization from '@js/core/utils/date_serialization'; import type { CalendarSelectionMode, DayOfWeek, WeekNumberRule, } from '@js/ui/calendar'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import type { BaseViewProperties } from './calendar.base_view'; import BaseView from './calendar.base_view'; @@ -18,8 +19,6 @@ const CALENDAR_WEEK_NUMBER_CELL_CLASS = 'dx-calendar-week-number-cell'; const CALENDAR_WEEK_NUMBER_HEADER_CELL_CLASS = 'dx-week-number-header'; const CALENDAR_WEEK_SELECTION_CLASS = 'dx-calendar-week-selection'; -const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; - const WEEK_NUMBER_TEXT = 'WeekNumber'; export interface MonthViewProperties extends BaseViewProperties { diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts index d3411be672fd..3cb11fe713f5 100644 --- a/packages/devextreme/js/__internal/ui/list/list.base.ts +++ b/packages/devextreme/js/__internal/ui/list/list.base.ts @@ -45,6 +45,7 @@ import { render } from '@ts/core/utils/ink_ripple'; import supportUtils from '@ts/core/utils/m_support'; import type { OptionChanged } from '@ts/core/widget/types'; import type { SupportedKeys } from '@ts/core/widget/widget'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import { getDataSourceOptions } from '@ts/data/data_converter/grouped'; import type { CollectionItemInfo, @@ -132,6 +133,8 @@ export class ListBase extends CollectionWidget { _$nextButton!: dxElementWrapper | null; + _$a11yStatusContainer?: dxElementWrapper; + _holdTimer?: ReturnType; _loadNextPageTimer?: ReturnType; @@ -975,6 +978,7 @@ export class ListBase extends CollectionWidget { this._itemElementsCache = $(); this.$element().addClass(LIST_CLASS); + this._renderA11yStatusContainer(); super._initMarkup(); const { useInkRipple } = this.option(); @@ -997,10 +1001,37 @@ export class ListBase extends CollectionWidget { const isEmpty = super._renderEmptyMessage(rootNodes); this.setAria({ role: isEmpty ? undefined : 'application' }, this._focusTarget()); + this._updateA11yStatusText(); return isEmpty; } + _renderA11yStatusContainer(): void { + const isContainerRendered = this._$a11yStatusContainer?.parent().is(this.$element()); + + if (isContainerRendered) { + return; + } + + this._$a11yStatusContainer = $('
') + .addClass(SCREEN_READER_ONLY_CLASS) + .attr('role', 'status') + .appendTo(this.$element()); + } + + _updateA11yStatusText(): void { + if (this._dataController.isLoading()) { + this._$a11yStatusContainer?.text(''); + return; + } + + const { noDataText } = this.option(); + const itemsCount = this._editStrategy.itemsGetter().length; + const itemsLabel = messageLocalization.format('dxList-listAriaLabel'); + + this._$a11yStatusContainer?.text(itemsCount ? `${itemsLabel}: ${itemsCount}` : noDataText ?? ''); + } + _isMultiSelectMode(): boolean { const { selectionMode } = this.option(); return selectionMode === 'multiple' || selectionMode === 'all'; diff --git a/packages/devextreme/js/__internal/ui/validation_summary.ts b/packages/devextreme/js/__internal/ui/validation_summary.ts index 13538c1d59f7..8bbdf63d43b6 100644 --- a/packages/devextreme/js/__internal/ui/validation_summary.ts +++ b/packages/devextreme/js/__internal/ui/validation_summary.ts @@ -3,6 +3,7 @@ import registerComponent from '@js/core/component_registrator'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { OptionChanged } from '@ts/core/widget/types'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import type { PostprocessRenderItemInfo } from '@ts/ui/collection/collection_widget.base'; import type { CollectionWidgetEditProperties } from '@ts/ui/collection/collection_widget.edit'; import CollectionWidget from '@ts/ui/collection/collection_widget.edit'; @@ -17,7 +18,6 @@ import ValidationEngine from '@ts/ui/validation_engine'; import type Validator from '@ts/ui/validator'; const VALIDATION_SUMMARY_CLASS = 'dx-validationsummary'; -const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; const ITEM_CLASS = `${VALIDATION_SUMMARY_CLASS}-item`; const ITEM_DATA_KEY = `${VALIDATION_SUMMARY_CLASS}-item-data`; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js index 1176c8994059..472cd7a1c70d 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js @@ -3969,6 +3969,62 @@ if(devices.real().deviceType === 'desktop') { }); } +QUnit.module('screen reader announcements (T1334729)', { + beforeEach: function() { + fx.off = true; + this.clock = sinon.useFakeTimers(); + + this.instance = $('#lookup').dxLookup({ + dataSource: [1, 2, 3], + searchTimeout: 0, + 'dropDownOptions.animation': null, + opened: true + }).dxLookup('instance'); + + this.search = (value) => { + this.instance._searchBox.option('value', value); + this.clock.tick(0); + }; + + this.getA11yStatus = () => $(`.${LIST_CLASS}`).children('[role="status"]'); + this.getItemsAnnouncement = (itemsCount) => `${messageLocalization.format('dxList-listAriaLabel')}: ${itemsCount}`; + }, + afterEach: function() { + this.clock.restore(); + fx.off = false; + } +}, () => { + QUnit.test('list should have a single live region', function(assert) { + assert.strictEqual(this.getA11yStatus().length, 1); + }); + + QUnit.test('item count should be announced when the drop-down is opened', function(assert) { + assert.strictEqual(this.getA11yStatus().text(), this.getItemsAnnouncement(3)); + }); + + QUnit.test('noDataText should be announced if search returns no items', function(assert) { + this.search('4'); + + assert.strictEqual(this.getA11yStatus().text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test('item count should be announced if search returns items', function(assert) { + this.search('1'); + + assert.strictEqual(this.getA11yStatus().text(), this.getItemsAnnouncement(1)); + }); + + QUnit.test('live region should not be re-created on search', function(assert) { + const a11yStatusElement = this.getA11yStatus().get(0); + + this.search('4'); + + assert.strictEqual(this.getA11yStatus().get(0), a11yStatusElement, 'the live region is the same element'); + assert.strictEqual(a11yStatusElement.textContent, messageLocalization.format('dxCollectionWidget-noDataText'), + 'the announcement is written into that element, otherwise screen readers do not announce it'); + }); +}); + QUnit.module('default options', { beforeEach: function() { fx.off = true; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js index 806250bc64e4..bf34a80301c4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js @@ -19,9 +19,12 @@ import { CustomStore } from 'common/data/custom_store'; import DOMComponent from 'core/dom_component'; import List from 'ui/list'; import { setScrollView } from '__internal/ui/list/list.base'; +import { SCREEN_READER_ONLY_CLASS } from '__internal/core/widget/widget'; + import ScrollView from 'ui/scroll_view'; import eventsEngine from 'common/core/events/core/events_engine'; import ariaAccessibilityTestHelper from '../../../helpers/ariaAccessibilityTestHelper.js'; +import messageLocalization from 'common/core/localization/message'; const LIST_ITEM_CLASS = 'dx-list-item'; const LIST_ITEM_CONTENT_CLASS = 'dx-list-item-content'; @@ -1004,7 +1007,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1014,7 +1017,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1024,7 +1027,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1068,7 +1071,7 @@ QUnit.module('options changed', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '01354'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '01354'); assert.deepEqual(element.dxList('instance').option('items'), [0, 1, 3, 5, 4]); element.dxList({ @@ -1077,7 +1080,7 @@ QUnit.module('options changed', moduleSetup, () => { } }); - assert.equal(element.text(), '26897'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '26897'); assert.deepEqual(element.dxList('instance').option('items'), [2, 6, 8, 9, 7]); }); @@ -1086,13 +1089,13 @@ QUnit.module('options changed', moduleSetup, () => { items: [0, 1, 3, 5, 4] }); - assert.equal(element.text(), '01354'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '01354'); element.dxList({ items: [2, 6, 8, 9, 7] }); - assert.equal(element.text(), '26897'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '26897'); }); QUnit.test('scrollingEnabled', function(assert) { @@ -2759,14 +2762,14 @@ QUnit.module('infinite list scenario', moduleSetup, () => { } }); - assert.equal(element.text(), '12', 'correct items generated'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12', 'correct items generated'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2], 'correct items presented in options'); element.find(`.${LIST_ITEM_CLASS}`).data('rendered', true); element.dxScrollView('instance').scrollBottom(); - assert.equal(element.text(), '1234', 'correct items generated'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '1234', 'correct items generated'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4], 'correct items presented in options'); assert.strictEqual(element.find(`.${LIST_ITEM_CLASS}`).eq(0).data('rendered'), true, 'first item is not rerendered'); @@ -2875,7 +2878,7 @@ QUnit.module('infinite list scenario', moduleSetup, () => { element.dxScrollView('instance').scrollBottom(); - assert.equal(element.text(), '12', 'error occurred'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12', 'error occurred'); assert.equal(element.dxList('instance')._startIndexForAppendedItems, null, 'flag set correctly'); }); @@ -4952,6 +4955,9 @@ if(devices.real().deviceType === 'desktop') { } QUnit.module('Accessibility', () => { + const getA11yStatus = (instance) => instance.$element().children('[role="status"]'); + const getItemsAnnouncement = (itemsCount) => `${messageLocalization.format('dxList-listAriaLabel')}: ${itemsCount}`; + QUnit.test('SelectAll checkbox should have aria-label="Select All" attribute', function(assert) { $('#list').dxList({ selectionMode: 'all', @@ -5378,4 +5384,104 @@ QUnit.module('Accessibility', () => { assert.strictEqual(instance.$element().find(`.${SCROLLVIEW_CONTENT_CLASS}`).eq(0).attr('role'), 'application'); }); }); + + QUnit.test('list should render a single visually hidden live region (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1'] }).dxList('instance'); + + const $a11yStatus = getA11yStatus(instance); + + assert.strictEqual($a11yStatus.length, 1, 'the list has a single live region'); + assert.ok($a11yStatus.hasClass(SCREEN_READER_ONLY_CLASS), 'the live region is visually hidden'); + }); + + QUnit.test('item count should be announced (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1', 'Item 2'] }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2)); + }); + + QUnit.test('item count of a grouped list should not count groups (T1334729)', function(assert) { + const instance = $('#list').dxList({ + grouped: true, + items: [ + { key: 'group 1', items: ['Item 1', 'Item 2'] }, + { key: 'group 2', items: ['Item 3'] }, + ], + }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(3)); + }); + + QUnit.test('noDataText should be announced when the list is empty (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [] }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test('custom noDataText should be announced and follow its runtime change (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [], noDataText: 'custom-no-data' }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), 'custom-no-data', 'custom noDataText is announced'); + + instance.option('noDataText', 'another-no-data'); + + assert.strictEqual(getA11yStatus(instance).text(), 'another-no-data', 'announcement is updated after runtime change'); + }); + + QUnit.test('nothing should be announced when the list is empty and noDataText is empty (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [], noDataText: '' }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), ''); + }); + + QUnit.test('nothing should be announced while the initial data is loading (T1334729)', function(assert) { + const loadResult = $.Deferred(); + const instance = $('#list').dxList({ + dataSource: new CustomStore({ load: () => loadResult.promise(), loadMode: 'processed' }) + }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), '', 'nothing is announced while data is loading'); + + loadResult.resolve(['Item 1', 'Item 2']); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2), 'item count is announced once data is loaded'); + }); + + QUnit.test('announcement should be cleared while new data is loading (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1', 'Item 2', 'Item 3'] }).dxList('instance'); + const loadResult = $.Deferred(); + + instance.option('dataSource', new CustomStore({ load: () => loadResult.promise(), loadMode: 'processed' })); + + assert.strictEqual(getA11yStatus(instance).text(), '', + 'neither the outdated item count nor noDataText is announced while data is loading'); + + loadResult.resolve(['Item 1']); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(1), 'item count is announced once data is loaded'); + }); + + [true, false].forEach(repaintChangesOnly => { + QUnit.test(`noDataText should be announced when dataSource is cleared at runtime and repaintChangesOnly=${repaintChangesOnly} (T1334729)`, function(assert) { + const instance = $('#list').dxList({ dataSource: ['Item 1'], repaintChangesOnly }).dxList('instance'); + const a11yStatusElement = getA11yStatus(instance).get(0); + + instance.option('dataSource', []); + + assert.strictEqual(getA11yStatus(instance).get(0), a11yStatusElement, + 'the live region is not re-created, otherwise screen readers do not announce its text'); + assert.strictEqual(getA11yStatus(instance).text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test(`item count should be announced when dataSource is set at runtime and repaintChangesOnly=${repaintChangesOnly} (T1334729)`, function(assert) { + const instance = $('#list').dxList({ dataSource: [], repaintChangesOnly }).dxList('instance'); + const a11yStatusElement = getA11yStatus(instance).get(0); + + instance.option('dataSource', ['Item 1', 'Item 2']); + + assert.strictEqual(getA11yStatus(instance).get(0), a11yStatusElement, + 'the live region is not re-created, otherwise screen readers do not announce its text'); + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2)); + }); + }); }); From d89ef0a96800176a23b8216f52bf9b93ff805b27 Mon Sep 17 00:00:00 2001 From: Aleksei Semikozov <2846685+aleksei-semikozov@users.noreply.github.com> Date: Tue, 8 Sep 2026 08:24:49 +0000 Subject: [PATCH 20/45] fix (demos): Revert the ResolveTimeConflicts date hardcode and mask the fluent etalon instead (#35079) Co-authored-by: Aleksey Semikozov --- .../Angular/app/app.component.ts | 9 --------- .../Scheduler/ResolveTimeConflicts/React/App.tsx | 9 --------- .../ResolveTimeConflicts/ReactJs/App.js | 8 -------- .../Scheduler/ResolveTimeConflicts/Vue/App.vue | 9 --------- .../ResolveTimeConflicts/jQuery/index.js | 9 --------- ...onflict-informer (fluent.blue.light)_mask.png | Bin 0 -> 3170 bytes 6 files changed, 44 deletions(-) create mode 100644 apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts index 8b275fbc3e96..96bd4aa81eac 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Angular/app/app.component.ts @@ -86,15 +86,6 @@ export class AppComponent { this.form?.validate(); } }); - - this.form?.on('contentReady', (contentReadyEvent: DxFormTypes.ContentReadyEvent) => { - const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); - untilEditor?.on('valueChanged', () => { - contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' - && untilEditor?.option('value', new Date(2026, 8, 3)); - }); - untilEditor?.option('value', new Date(2026, 8, 3)); - }); }; customizeItem = (item: DxFormTypes.SimpleItem): void => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx index 11ccad20985b..42a44a6958aa 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/React/App.tsx @@ -173,15 +173,6 @@ const App = () => { formRef.current?.validate(); } }); - - e.component.on('contentReady', (contentReadyEvent: FormTypes.ContentReadyEvent) => { - const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); - untilEditor?.on('valueChanged', () => { - contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' - && untilEditor?.option('value', new Date(2026, 8, 3)); - }); - untilEditor?.option('value', new Date(2026, 8, 3)); - }); }, [setConflictError]); const customizeItem = useCallback((item: FormTypes.SimpleItem) => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js index 9cbd4f8a600f..e13487f0fae2 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/ReactJs/App.js @@ -149,14 +149,6 @@ const App = () => { formRef.current?.validate(); } }); - e.component.on('contentReady', (contentReadyEvent) => { - const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); - untilEditor?.on('valueChanged', () => { - contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== - 'until' && untilEditor?.option('value', new Date(2026, 8, 3)); - }); - untilEditor?.option('value', new Date(2026, 8, 3)); - }); }, [setConflictError], ); diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue index 38507b99935d..260dc02e5e76 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/Vue/App.vue @@ -240,15 +240,6 @@ const onFormInitialized = (e: DxFormTypes.InitializedEvent) => { form?.validate(); } }); - - e.component!.on('contentReady', (contentReadyEvent: DxFormTypes.ContentReadyEvent) => { - const untilEditor = contentReadyEvent.component.getEditor('recurrenceEndUntilEditor'); - untilEditor?.on('valueChanged', () => { - contentReadyEvent.component.getEditor('recurrenceRepeatEndEditor')?.option('value') !== 'until' - && untilEditor?.option('value', new Date(2026, 8, 3)); - }); - untilEditor?.option('value', new Date(2026, 8, 3)); - }); }; const customizeItem = (item: DxFormTypes.SimpleItem) => { diff --git a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js index 9358db8a1b7a..b6a6887daf51 100644 --- a/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js +++ b/apps/demos/Demos/Scheduler/ResolveTimeConflicts/jQuery/index.js @@ -43,15 +43,6 @@ $(() => { form.validate(); } }); - - form.on('contentReady', (e) => { - const untilEditor = e.component.getEditor('recurrenceEndUntilEditor'); - untilEditor.on('valueChanged', () => { - e.component.getEditor('recurrenceRepeatEndEditor').option('value') !== 'until' - && untilEditor.option('value', new Date(2026, 8, 3)); - }); - untilEditor.option('value', new Date(2026, 8, 3)); - }); }, items: [ { diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f261f4139ef4e6d7735e406e1a7e047c9ff1f4 GIT binary patch literal 3170 zcmeAS@N?(olHy`uVBq!ia0y~yU~XYxV2a>i1BzH3)ws>Tz@zEu;uuoF`1T;9;2{P9 z<_+u2|4)-=SP#k(m~jP27_4MuU`PmJW?(q51gO8klNTsBSqey~nF0x) zEFdwea5Q*E6UAs&7%eGA%g51L0azi9R+?05D!k-n=&7Ek{%8GeZifAXti5qbnn9wx moUy@mv_&-94ji^Qo}ck1-`Xu1pTvR18iS{+pUXO@geCylgehtO literal 0 HcmV?d00001 From d96abb4dd33b642bb971644ed4c0b19adff2d651 Mon Sep 17 00:00:00 2001 From: Alyar Date: Tue, 8 Sep 2026 08:29:01 +0000 Subject: [PATCH 21/45] Grids - DataController: Extract user state knowledge to extenders (#35064) Co-authored-by: Alyar <> --- .../data_controller/data_controller.ts | 3 +- .../grids/grid_core/data_controller/types.ts | 2 +- .../search_data_controller.user_state.test.ts | 27 ++++++++ .../grids/grid_core/search/m_search.ts | 9 ++- .../__tests__/__mock__/helpers/utils.ts | 66 +++++++++++++++++++ .../state_storing.user_state.test.ts | 28 ++++++++ 6 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 242093e9c26a..989298f6f505 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -1629,11 +1629,10 @@ export class DataController extends modules.Controller { } /** - * @extended: TreeList's state_storing + * @extended: search, TreeList's state_storing */ public getUserState(): UserState { return { - searchText: this.option('searchPanel.text'), pageIndex: this.pageIndex(), pageSize: this.pageSize(), }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts index 4e2aeb8eba80..b67a9d13623a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts @@ -18,9 +18,9 @@ export interface RefreshOptions { } export interface UserState { - searchText: string | undefined; pageIndex: number; pageSize: number; + searchText?: string; expandedRowKeys?: unknown[]; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts b/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts new file mode 100644 index 000000000000..30c939574e2a --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts @@ -0,0 +1,27 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, + beforeTest, + createDataGrid, +} from '../../__tests__/__mock__/helpers/utils'; + +describe('Search data controller user state', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should include the search panel text', async () => { + const { instance } = await createDataGrid({ + dataSource: [], + searchPanel: { text: 'Alex' }, + }); + + expect(instance.getController('data').getUserState()).toEqual({ + searchText: 'Alex', + pageIndex: 0, + pageSize: 20, + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index 71faffb9cc83..30ae6e25a9b7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -12,7 +12,7 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { DataController } from '../data_controller/data_controller'; -import type { DataFilter } from '../data_controller/types'; +import type { DataFilter, UserState } from '../data_controller/types'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import modules from '../m_modules'; import type { ModuleType, OptionChanged } from '../m_types'; @@ -62,6 +62,13 @@ const dataController = ( return super.publicMethods().concat(['searchByText']); } + public getUserState(): UserState { + return { + ...super.getUserState(), + searchText: this.option('searchPanel.text'), + }; + } + protected calculateAdditionalFilter(): DataFilter { const dataSource = this.getDataSource(); const langParams = dataSource?.loadOptions?.()?.langParams; diff --git a/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts b/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts new file mode 100644 index 000000000000..ffd6ce2fb2dd --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts @@ -0,0 +1,66 @@ +import { jest } from '@jest/globals'; +import fx from '@js/common/core/animation/fx'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { Properties as TreeListProperties } from '@js/ui/tree_list'; +import TreeList from '@js/ui/tree_list'; +import type { Controllers, Views } from '@ts/grids/grid_core/m_types'; +import { TreeListModel } from '@ts/grids/tree_list/__tests__/__mock__/model/tree_list'; + +export interface TreeListInstance extends TreeList { + getController: (name: T) => Controllers[T]; + getView: (name: T) => Views[T]; +} + +export const SELECTORS = { + treeListContainer: '#treeListContainer', +}; + +export const TREELIST_CONTAINER_ID = 'treeListContainer'; + +export const createTreeList = ( + options: TreeListProperties = {}, +): Promise<{ + $container: dxElementWrapper; + component: TreeListModel; + instance: TreeListInstance; +}> => new Promise((resolve) => { + const $container = $('
') + .attr('id', TREELIST_CONTAINER_ID) + .appendTo(document.body); + + const treeListOptions: TreeListProperties = { + keyExpr: 'id', + ...options, + }; + + const instance = new TreeList( + $container.get(0) as HTMLDivElement, + treeListOptions, + ) as TreeListInstance; + const component = new TreeListModel($container.get(0) as HTMLElement); + + jest.runAllTimers(); + + resolve({ + $container, + component, + instance, + }); +}); + +export const beforeTest = (): void => { + fx.off = true; + jest.useFakeTimers(); +}; + +export const afterTest = (): void => { + const $container = $(SELECTORS.treeListContainer); + const treeList = TreeList.getInstance($container.get(0)); + + treeList?.dispose(); + $container.remove(); + jest.clearAllMocks(); + jest.useRealTimers(); + fx.off = false; +}; diff --git a/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts b/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts new file mode 100644 index 000000000000..5158ac72be87 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts @@ -0,0 +1,28 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, + beforeTest, + createTreeList, +} from './__mock__/helpers/utils'; + +describe('TreeList user state', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should include the expandedRowKeys', async () => { + const { instance } = await createTreeList({ + dataSource: [{ id: 1, parentId: 0, name: 'Alex' }], + expandedRowKeys: [1], + }); + + expect(instance.getController('data').getUserState()).toEqual({ + searchText: '', + expandedRowKeys: [1], + pageIndex: 0, + pageSize: 20, + }); + }); +}); From a65d008cefc1539932e62780a8a069816d7d12d0 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Tue, 8 Sep 2026 08:34:06 +0000 Subject: [PATCH 22/45] Legacy themes: remove the dead pull-down raster and the duplicated colour-scheme copies (#35088) --- .../fluent/color-schemes/dark/pulldown.png | Bin 328 -> 0 bytes .../fluent/color-schemes/light/pulldown.png | Bin 328 -> 0 bytes .../dark => }/grid/text-stub.png | Bin .../color-schemes/carmine/pulldown.png | Bin 328 -> 0 bytes .../color-schemes/contrast/pulldown.png | Bin 214 -> 0 bytes .../color-schemes/darkmoon/pulldown.png | Bin 221 -> 0 bytes .../color-schemes/darkviolet/pulldown.png | Bin 221 -> 0 bytes .../color-schemes/greenmist/pulldown.png | Bin 328 -> 0 bytes .../color-schemes/light/toast/error.png | Bin 435 -> 0 bytes .../color-schemes/light/toast/info.png | Bin 475 -> 0 bytes .../color-schemes/light/toast/success.png | Bin 463 -> 0 bytes .../color-schemes/light/toast/warning.png | Bin 351 -> 0 bytes .../color-schemes/softblue/pulldown.png | Bin 328 -> 0 bytes .../{color-schemes/dark => }/toast/error.png | Bin .../{color-schemes/dark => }/toast/info.png | Bin .../dark => }/toast/success.png | Bin .../dark => }/toast/warning.png | Bin .../color-schemes/dark/grid/text-stub.png | Bin 1230 -> 0 bytes .../material/color-schemes/dark/pulldown.png | Bin 328 -> 0 bytes .../color-schemes/light/grid/text-stub.png | Bin 1230 -> 0 bytes .../material/color-schemes/light/pulldown.png | Bin 328 -> 0 bytes .../light => material}/grid/text-stub.png | Bin .../scss/widgets/fluent/_colors.scss | 5 --- .../scss/widgets/fluent/gridBase/_colors.scss | 5 +-- .../scss/widgets/fluent/icons/_index.scss | 6 --- .../scss/widgets/generic/_colors.scss | 18 -------- .../scss/widgets/generic/icons/_index.scss | 6 --- .../scss/widgets/generic/toast/_colors.scss | 41 +++--------------- .../scss/widgets/material/_colors.scss | 5 --- .../widgets/material/gridBase/_colors.scss | 5 +-- .../scss/widgets/material/icons/_index.scss | 6 --- 31 files changed, 9 insertions(+), 88 deletions(-) delete mode 100644 packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/fluent/color-schemes/light/pulldown.png rename packages/devextreme-scss/images/widgets/fluent/{color-schemes/dark => }/grid/text-stub.png (100%) delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/carmine/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/contrast/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/error.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/info.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/success.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png delete mode 100644 packages/devextreme-scss/images/widgets/generic/color-schemes/softblue/pulldown.png rename packages/devextreme-scss/images/widgets/generic/{color-schemes/dark => }/toast/error.png (100%) rename packages/devextreme-scss/images/widgets/generic/{color-schemes/dark => }/toast/info.png (100%) rename packages/devextreme-scss/images/widgets/generic/{color-schemes/dark => }/toast/success.png (100%) rename packages/devextreme-scss/images/widgets/generic/{color-schemes/dark => }/toast/warning.png (100%) delete mode 100644 packages/devextreme-scss/images/widgets/material/color-schemes/dark/grid/text-stub.png delete mode 100644 packages/devextreme-scss/images/widgets/material/color-schemes/dark/pulldown.png delete mode 100644 packages/devextreme-scss/images/widgets/material/color-schemes/light/grid/text-stub.png delete mode 100644 packages/devextreme-scss/images/widgets/material/color-schemes/light/pulldown.png rename packages/devextreme-scss/images/widgets/{fluent/color-schemes/light => material}/grid/text-stub.png (100%) diff --git a/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png b/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png deleted file mode 100644 index 436cc90d966632a84d6e0640dc624b2536fb8e4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 328 zcmV-O0k{5%P)uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-0000uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-0000uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-00001<%J%-}MD zK4qxB&cxysPV6Z>2Td6d?yOJ>S9T0p8MZuA4OVvYKR@BjRShie_{q+N04`xIwx_su QyZ`_I07*qoM6N<$f?<$cBme*a diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png deleted file mode 100644 index 5d3b6b3f0b8857dcbc8565ca8a79e301143f81a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^B0y}!!2%?|l^U-FQcFBt978;gC(k&_(7rE1;c&f1 zii8Z;MyDE!I;`PJ7kWK2u(c#`h3V@9 zYYr#;km){jI3R+{NHgo2{F+Qb#_ea4nj@sT!(V=j%;7Lsdl0g%Tugmc`lSz=&Rq@F2U Q26Qijr>mdKI;Vst0Aep!bpQYW diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png deleted file mode 100644 index 5d3b6b3f0b8857dcbc8565ca8a79e301143f81a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^B0y}!!2%?|l^U-FQcFBt978;gC(k&_(7rE1;c&f1 zii8Z;MyDE!I;`PJ7kWK2u(c#`h3V@9 zYYr#;km){jI3R+{NHgo2{F+Qb#_ea4nj@sT!(V=j%;7Lsdl0g%Tugmc`lSz=&Rq@F2U Q26Qijr>mdKI;Vst0Aep!bpQYW diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png deleted file mode 100644 index 436cc90d966632a84d6e0640dc624b2536fb8e4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 328 zcmV-O0k{5%P)uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-00001p=5JksLNar*noep7igFxJ>cbP*(oghee2n2EmfeT+CRT?9VI{OAmRKx){Z&O%` z6>D~7n`OFX^jOXRGqZxI5fGD8P*DRl6$Lplfu64&Mr158YAnf!hA|;2YYU={H7TJB zGi5%2Xk*UQhMBQC5n?c`m}!_9n=>E=!{z`w{Lbo3$o@vtE+#y=CjAX@&@a;@Ab(2;(DAf*?%z>Nz_QV&yJn(>q;I z(r6m9>*dEnuGZif1-T)HLa3}#wxFv4h@lc{hbPL;e6d3ewSd!H4-I6* zn_V*|KXT~$k&i|5z}Q<5s^qnbZtzR$qI->GFd>2zcX49nVqP;%=pebdFR7% zNB)B8SvGuf&pCT+A`moaamE8*#=c(81jZBpa7ELaXiY9bd;Va?^fly_qk`O?tz+Jl z{e6VM(=zs(Lnk5-oG2$s@C2cDmg`bPAh=OtLP4Z4`<0jjB_u3 zm6&ZMCKY5Fb6SbHP+~HnWr5_GjZ&n+ixPu*8p$o|PGpTcEh01TMk0CA+me#UkH-Gg ze5t$~2eb@gmw}eS^OuSowVf(R$5NQ{ya}Bhr3xYm&6=_^E+oOTRk+ExkiQDI^&6Gx zFm;Zbz}5Y7#*Hj>KUXgYU6o*d{I=FPo8W6@U%*lD0b(TqAHm5ta0qsD5L{eKsoh!!7j-d9 zQ9-`z(1iTCH%U{%nR1eR_uO-S_na$;(JWj7LS{rj#Eg&t7t8#;$?4!TMj364k5iAa z@tBt&5}V^;S7G{$Y7mKy=$ByzOxJ`c6jKH!%z#A$h(fVQLFsm;4I!(D`dN&e zZwh`_2-b$&vxOj>X>S4{H2g5(Tl2lpLl6#g8(LW?%rxgSJ&p^YFLn`x9X<`q&V;Do zdW%=hg8n!{5Vn~Zw;Ks)&Z{_)Dd-$Q=x|q7K|LCUVweNn#!r`}$&XCA$%7v0A_ymj zxHGv4OI{_Q4uY`HvO)@Bm2QTa2LD>AsGwTK7YIU^hZ(qw3DPPH~QTbtpn-6&eS-)wl>`Uhv!GsGVMN0|Ts002ovPDHLk FV1kNN$L0V4 diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png deleted file mode 100644 index 7324d22ea49ba714538dcfb9c7db1893558b58d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmV-l0igbgP)jT-sag85dh8lg|Z=mXLVJ_E)JK{dE6m><a8xGctvV^}eL|!uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-00007fK6dpc#4O+ez30}+TrRfISo4k+pgwQ51CR8CRV!mRCeqN?snyYpuB zec$`uytkJo#t()2KI#JigtLd$JY5a?z76%#uXFC+4|Lf<#)@PTmq=A}5s(c$g<#gw zrcoYg#@v}7(Fgz=KQ#+QQp_EZbZoJjk6~+;L)id~9IQE-K7$CHLer+5X8u@uz(CVT zGshD-KIbT?Y#y$=XtF+D(CagL%3uy2fFm`D3Rs9}ux8ELo>WUSOorNOF9V6Jk+bkoX}fmV)hvq1jxcB<0mi zI~U!hnKB`c#BtSXm955D>`rq+DwX1RkrTxzMMS+hn`pJD?F|G9D)MyKbcl&<=qqYd zxI)qlb=oSyayqiM*S;otVO&jfIDzH;k^*2Z_y17K>YzQ6M{9onQ`jraIf%<64_92B zHm)?_r*b64MH<0w0pr==7AMM>V6TiFs3>sHanm+%)!W;I=WJ(4wRF>&dt}pLcj;9By0G^f z05&LDRW8(CzFAoeKjQnxUW6eU!VviBvrqVs zlfA<8O=}!V@A>WF-9(%FZ1m0bM=x$}IdyBHclW>ePUF?a(*DQ04#kvpNqvD0ZFYU> z&r@HDCkKO^&GexLSXlXf^OOEc$8O7YVE2A`Zuz@!uZ5>xx5@~_uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-00007fK6dpc#4O+ez30}+TrRfISo4k+pgwQ51CR8CRV!mRCeqN?snyYpuB zec$`uytkJo#t()2KI#JigtLd$JY5a?z76%#uXFC+4|Lf<#)@PTmq=A}5s(c$g<#gw zrcoYg#@v}7(Fgz=KQ#+QQp_EZbZoJjk6~+;L)id~9IQE-K7$CHLer+5X8u@uz(CVT zGshD-KIbT?Y#y$=XtF+D(CagL%3uy2fFm`D3Rs9}ux8ELo>WUSOorNOF9V6Jk+bkoX}fmV)hvq1jxcB<0mi zI~U!hnKB`c#BtSXm955D>`rq+DwX1RkrTxzMMS+hn`pJD?F|G9D)MyKbcl&<=qqYd zxI)qlb=oSyayqiM*S;otVO&jfIDzH;k^*2Z_y17K>YzQ6M{9onQ`jraIf%<64_92B zHm)?_r*b64MH<0w0pr==7AMM>V6TiFs3>sHanm+%)!W;I=WJ(4wRF>&dt}pLcj;9By0G^f z05&LDRW8(CzFAoeKjQnxUW6eU!VviBvrqVs zlfA<8O=}!V@A>WF-9(%FZ1m0bM=x$}IdyBHclW>ePUF?a(*DQ04#kvpNqvD0ZFYU> z&r@HDCkKO^&GexLSXlXf^OOEc$8O7YVE2A`Zuz@!uZ5>xx5@~_uf2p^V1As%A0e-Kw~Cy$vmS aUGD{By6(+O*Zar-0000 Date: Tue, 8 Sep 2026 08:51:42 +0000 Subject: [PATCH 23/45] refactor(ci): store flaky candidates in an artifact (#35083) --- .github/workflows/testcafe_tests.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/testcafe_tests.yml b/.github/workflows/testcafe_tests.yml index 1c7167fab625..45f067d17332 100644 --- a/.github/workflows/testcafe_tests.yml +++ b/.github/workflows/testcafe_tests.yml @@ -470,8 +470,12 @@ jobs: quarantined_failed=0 result=./artifacts/quarantine-run-result.json new_flakes_summary=./artifacts/new-flakes-summary.txt + new_flakes=./artifacts/flaky-candidates.jsonl + report_json=./artifacts/flaky-candidates.json mkdir -p ./artifacts : > "$new_flakes_summary" + : > "$new_flakes" + rm -f "$report_json" # Every test gets up to 3 attempts (initial run + 2 retries) and passes # if at least one attempt succeeds. A reported failure means all attempts failed. @@ -549,12 +553,23 @@ jobs: while IFS= read -r name; do if jq -e --arg n "$name" '.tests | index($n)' "$result" > /dev/null; then echo "FAILED | $name | $job_name" >> "$new_flakes_summary" + verdict=failed else echo "passed | $name | $job_name" >> "$new_flakes_summary" + verdict=passed fi + jq -cn --arg test "$name" --arg sourceJob "$job_name" --arg theme "$theme" \ + --arg componentFolder "$componentFolder" --arg timezone "$timezone" \ + --arg platform "$platform" --arg verdict "$verdict" \ + '{ test: $test, sourceJob: $sourceJob, theme: $theme, componentFolder: $componentFolder, timezone: $timezone, platform: $platform, verdict: $verdict }' \ + >> "$new_flakes" done < <(jq -r '.tests[]' "$report") done + if [ -s "$new_flakes" ]; then + jq -s '{ schemaVersion: 1, candidates: . }' "$new_flakes" > "$report_json" + fi + sudo ln -sf /usr/share/zoneinfo/GMT /etc/localtime echo "" @@ -631,6 +646,15 @@ jobs: path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/failedtests/**/* if-no-files-found: ignore + - name: Upload flaky candidates report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flaky-candidates + path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/flaky-candidates.json + retention-days: 7 + if-no-files-found: ignore + merge-artifacts: runs-on: ubuntu-latest needs: [testcafe, testcafe-quarantine] From b37a4757dd564562a6e10214e5a7fdd9fae19765 Mon Sep 17 00:00:00 2001 From: Andrey Vorobev <738482+vorobey@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:40:45 +0000 Subject: [PATCH 24/45] chore: fix demos render signal origin (#35078) --- apps/demos/README.md | 33 +++- apps/demos/utils/server/demo-render-signal.js | 16 +- .../server/demo-render-signal.runtime.js | 75 ++++++- .../server/demo-render-signal.runtime.test.js | 187 +++++++++++++++++- 4 files changed, 304 insertions(+), 7 deletions(-) diff --git a/apps/demos/README.md b/apps/demos/README.md index 0fb102135541..c307e742ac9f 100644 --- a/apps/demos/README.md +++ b/apps/demos/README.md @@ -35,9 +35,40 @@ Angular, React, and Vue demos are not bundled from their own entry point directl Once the demo has rendered, the shim posts one message to the embedding page: ```js -window.parent.postMessage({ type: 'demo-rendered' }, '*'); +window.parent.postMessage({ type: 'demo-rendered' }, targetOrigin); ``` +#### Allowed embedding origins + +`targetOrigin` is never `'*'` — the runtime resolves the embedder's origin and posts only when it is trusted: + +1. Not framed, or framed by a page on the demo's own origin — posts to the own origin. This covers local development and the visual tests, so `localhost` needs no configuration. +2. Framed cross-origin — the origin comes from `location.ancestorOrigins[0]`, falling back to the origin of `document.referrer` (Firefox has no `ancestorOrigins`), and must match the allowlist. +3. Origin not derivable (for example, an embedder sending `Referrer-Policy: no-referrer` on Firefox) or not on the allowlist — nothing is posted, and the demo logs a warning to the console. + +The allowlist defaults to the sandboxes that embed the demos: + +| Entry | Covers | +| --- | --- | +| `*.devexpress.com` | `js.devexpress.com`, `js-stage.devexpress.com`, `az-jsserver.corp.devexpress.com` | +| `js.devexpress.devx` | the internal dev host | +| `localhost` | the local site on any port, when it frames demos served from another port | + +An entry is `[://][:]`, and an omitted part matches anything: + +- **Host** — either an exact hostname or a `*.` subdomain wildcard. The wildcard requires a dot before the suffix, so `*.devexpress.com` accepts `js.devexpress.com` and `az-jsserver.corp.devexpress.com` but rejects the `devexpress.com` apex and `evil-devexpress.com`. +- **Scheme** — omit it to accept both HTTP and HTTPS; write `https://js.devexpress.com` to accept only HTTPS. +- **Port** — omit it to accept any port; write `localhost:44332` to accept only that one. A pinned port must match the origin's explicit port, so `:443` will not match `https://host`. + +Anything that is not a bare origin — a trailing slash or a path, for example — never matches and is silently ignored. + +Override the list at build time with a comma-separated `DEMO_PARENT_ORIGINS`: + +``` +DEMO_PARENT_ORIGINS='https://js.devexpress.com,https://staging.example:8443' node utils/server/csp-bundle.js --framework=React +``` + +The list is baked into every demo bundle, so adding a sandbox origin means rebuilding the demos. ### Before Commiting Changes diff --git a/apps/demos/utils/server/demo-render-signal.js b/apps/demos/utils/server/demo-render-signal.js index 0e2f05cbc79d..8466c9631057 100644 --- a/apps/demos/utils/server/demo-render-signal.js +++ b/apps/demos/utils/server/demo-render-signal.js @@ -6,17 +6,29 @@ const RUNTIME_PATH = path.join(__dirname, 'demo-render-signal.runtime.js'); const VIRTUAL_ENTRY_PREFIX = 'demo-render-signal:'; const VIRTUAL_ENTRY_NAMESPACE = 'demo-render-signal'; +function allowedParentOrigins() { + const fromEnv = String(process.env.DEMO_PARENT_ORIGINS || '') + .split(',') + .map((origin) => origin.trim()) + .filter(Boolean); + return fromEnv.length > 0 ? fromEnv : null; +} + function toImportPath(filePath) { return JSON.stringify(filePath.split(path.sep).join('/')); } function createEntryShimSource(entryPath, { tsNoCheck = false } = {}) { + const origins = allowedParentOrigins(); + const imported = ['signal', 'signalWhenRendered']; + if (origins) imported.unshift('setAllowedOrigins'); + return ` ${tsNoCheck ? '// @ts-nocheck\n' : ''} // Generated by utils/server/demo-render-signal.js. import themes from ${JSON.stringify(THEMES_SPECIFIER)}; - import { signal, signalWhenRendered } from ${toImportPath(RUNTIME_PATH)}; - + import { ${imported.join(', ')} } from ${toImportPath(RUNTIME_PATH)}; +${origins ? `\n setAllowedOrigins(${JSON.stringify(origins)});\n` : ''} themes.initialized(function () { import(${toImportPath(entryPath)}).then(signalWhenRendered, function (error) { console.error('demo failed to load', error); diff --git a/apps/demos/utils/server/demo-render-signal.runtime.js b/apps/demos/utils/server/demo-render-signal.runtime.js index a82fb1f54b68..cb843746e4e7 100644 --- a/apps/demos/utils/server/demo-render-signal.runtime.js +++ b/apps/demos/utils/server/demo-render-signal.runtime.js @@ -5,13 +5,86 @@ export const ROOT_SELECTOR = '#app, demo-app'; export const RENDER_TIMEOUT_MS = 10000; +export const DEFAULT_ALLOWED_ORIGINS = [ + '*.devexpress.com', + 'js.devexpress.devx', + 'localhost', +]; + +const PATTERN = /^(?:([a-z][a-z0-9+.-]*):\/\/)?([^/:]+)(?::(\d+))?$/i; + +let allowedOrigins = DEFAULT_ALLOWED_ORIGINS; let posted = false; +export function setAllowedOrigins(origins) { + if (Array.isArray(origins) && origins.length > 0) { + allowedOrigins = origins; + } +} + +function hostMatches(hostname, patternHost) { + if (!patternHost.startsWith('*.')) return hostname === patternHost; + + const suffix = patternHost.slice(1); + return hostname.length > suffix.length && hostname.endsWith(suffix); +} + +function matchesPattern(url, pattern) { + const parts = PATTERN.exec(pattern); + if (!parts) return false; + + const [, scheme, host, port] = parts; + if (scheme && `${scheme.toLowerCase()}:` !== url.protocol) return false; + if (port && port !== url.port) return false; + + return hostMatches(url.hostname, host.toLowerCase()); +} + +function isAllowed(origin) { + let url; + try { + url = new URL(origin); + } catch { + return false; + } + return allowedOrigins.some((pattern) => matchesPattern(url, pattern)); +} + +function parentOrigin() { + const { ancestorOrigins } = window.location; + if (ancestorOrigins && ancestorOrigins.length > 0) return ancestorOrigins[0]; + + if (!document.referrer) return null; + try { + return new URL(document.referrer).origin; + } catch { + return null; + } +} + +export function resolveTargetOrigin() { + const ownOrigin = window.location.origin; + if (window.parent === window) return ownOrigin; + + const origin = parentOrigin(); + if (!origin) return null; + if (origin === ownOrigin) return origin; + + return isAllowed(origin) ? origin : null; +} + export function signal() { if (posted) return; posted = true; + + const targetOrigin = resolveTargetOrigin(); + if (!targetOrigin) { + console.warn(`Demo render signal was not sent: the embedding origin is unknown or not in [${allowedOrigins.join(', ')}].`); + return; + } + try { - window.parent.postMessage({ type: MESSAGE_TYPE }, '*'); + window.parent.postMessage({ type: MESSAGE_TYPE }, targetOrigin); } catch { // A parent that rejects the message is the parent's problem. } diff --git a/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js b/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js index 27af89bbffbd..1ee30a1a1e42 100644 --- a/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js +++ b/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js @@ -1,7 +1,11 @@ +const OWN_ORIGIN = 'http://localhost:8080'; +const ALLOWED_PARENT = 'https://js.devexpress.com'; + let messages; let root; let rafCallbacks; let observers; +let warnings; function loadRuntime() { let runtime; @@ -22,6 +26,10 @@ function flushFrames() { } } +function embedIn(origin) { + global.window.location.ancestorOrigins = origin === null ? [] : [origin]; +} + beforeEach(() => { jest.useFakeTimers(); @@ -29,13 +37,15 @@ beforeEach(() => { root = null; rafCallbacks = []; observers = []; + warnings = []; global.window = { parent: { postMessage: (data, targetOrigin) => messages.push({ data, targetOrigin }), }, + location: { origin: OWN_ORIGIN, ancestorOrigins: [ALLOWED_PARENT] }, }; - global.document = { querySelector: () => root }; + global.document = { querySelector: () => root, referrer: '' }; global.requestAnimationFrame = (callback) => rafCallbacks.push(callback); global.MutationObserver = class { constructor(callback) { @@ -50,10 +60,13 @@ beforeEach(() => { this.disconnected = true; } }; + + jest.spyOn(console, 'warn').mockImplementation((message) => warnings.push(message)); }); afterEach(() => { jest.useRealTimers(); + jest.restoreAllMocks(); delete global.window; delete global.document; delete global.requestAnimationFrame; @@ -61,12 +74,12 @@ afterEach(() => { }); describe('signal', () => { - test('posts the contract message to the parent', () => { + test('posts the contract message to the embedding origin', () => { const { signal, MESSAGE_TYPE } = loadRuntime(); signal(); - expect(messages).toEqual([{ data: { type: MESSAGE_TYPE }, targetOrigin: '*' }]); + expect(messages).toEqual([{ data: { type: MESSAGE_TYPE }, targetOrigin: ALLOWED_PARENT }]); expect(MESSAGE_TYPE).toBe('demo-rendered'); }); @@ -85,6 +98,174 @@ describe('signal', () => { expect(() => signal()).not.toThrow(); }); + + test('warns instead of posting to an origin that is not allowed', () => { + embedIn('https://evil.example'); + const { signal } = loadRuntime(); + + signal(); + + expect(messages).toHaveLength(0); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain('*.devexpress.com'); + }); + + test('gives up for good after a dropped message', () => { + embedIn('https://evil.example'); + const { signal } = loadRuntime(); + + signal(); + embedIn(ALLOWED_PARENT); + signal(); + + expect(messages).toHaveLength(0); + }); +}); + +describe('resolveTargetOrigin', () => { + test('takes the embedding origin from ancestorOrigins', () => { + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test.each([ + 'https://az-jsserver.corp.devexpress.com', + 'https://js-stage.devexpress.com', + 'https://js.devexpress.com', + 'https://js.devexpress.devx', + 'http://localhost:44332', + 'http://localhost:8080', + ])('allows the %s sandbox', (origin) => { + embedIn(origin); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(origin); + }); + + test('allows any scheme for an entry that omits one', () => { + embedIn('http://js.devexpress.com'); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe('http://js.devexpress.com'); + }); + + test.each([ + ['the apex domain, which no sandbox uses', 'https://devexpress.com'], + ['a host that only ends with the wildcard suffix', 'https://evil-devexpress.com'], + ['a host shorter than the wildcard suffix', 'https://dx.com'], + ['a host that merely starts with an allowed one', 'https://localhost.evil.example'], + ['an opaque origin', 'null'], + ])('rejects %s', (_, origin) => { + embedIn(origin); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('pins the scheme for an entry that specifies one', () => { + embedIn('http://js.devexpress.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://js.devexpress.com']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('pins the port for an entry that specifies one', () => { + embedIn('http://localhost:9999'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['localhost:44332']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('matches a pinned port', () => { + embedIn('http://localhost:44332'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['http://localhost:44332']); + + expect(resolveTargetOrigin()).toBe('http://localhost:44332'); + }); + + test('ignores an entry that is not a bare origin', () => { + embedIn('https://js.devexpress.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://js.devexpress.com/Demos/']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('allows an embedder on the demo own origin, allowlisted or not', () => { + global.window.location.origin = 'https://unlisted.example'; + embedIn('https://unlisted.example'); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe('https://unlisted.example'); + }); + + test('targets the own origin when the demo is not framed', () => { + global.window.parent = global.window; + embedIn(null); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(OWN_ORIGIN); + }); + + test('falls back to the referrer origin without ancestorOrigins', () => { + delete global.window.location.ancestorOrigins; + global.document.referrer = `${ALLOWED_PARENT}/Demos/DataGrid/Overview/React/Light/`; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test('falls back to the referrer origin with empty ancestorOrigins', () => { + embedIn(null); + global.document.referrer = `${ALLOWED_PARENT}/`; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test('resolves nothing without ancestorOrigins or a referrer', () => { + embedIn(null); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('resolves nothing from an unparsable referrer', () => { + embedIn(null); + global.document.referrer = 'not-a-url'; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); +}); + +describe('setAllowedOrigins', () => { + test('replaces the default allowlist', () => { + embedIn('https://sandbox.example'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://sandbox.example']); + + expect(resolveTargetOrigin()).toBe('https://sandbox.example'); + }); + + test('keeps the default allowlist when the build injects nothing', () => { + const { setAllowedOrigins, resolveTargetOrigin, DEFAULT_ALLOWED_ORIGINS } = loadRuntime(); + + setAllowedOrigins(null); + setAllowedOrigins([]); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + expect(DEFAULT_ALLOWED_ORIGINS).toContain('*.devexpress.com'); + }); }); describe('signalWhenRendered', () => { From 2dc7b76502562b685f78f4a7ae0560e76d74d8a5 Mon Sep 17 00:00:00 2001 From: Anna Shakhova <68295572+anna-shakhova@users.noreply.github.com> Date: Tue, 8 Sep 2026 10:53:51 +0000 Subject: [PATCH 25/45] Grids: retarget `dataController.dataSource()` method (#35093) --- .../ai_assistant/commands/selection.ts | 3 +- .../controllers/m_ai_column_controller.ts | 8 +- .../__tests__/data_controller.paging.test.ts | 10 +- .../data_controller/data_controller.ts | 30 ++--- ...data_source_controller.integration.test.ts | 22 ++-- .../data_source/data_source_controller.ts | 19 ++- .../grids/grid_core/editing/m_editing.ts | 15 ++- .../grid_core/filter_panel/m_filter_panel.ts | 6 +- .../grid_core/filter_row/m_filter_row.ts | 16 ++- .../grids/grid_core/focus/m_focus.ts | 19 ++- .../header_filter/m_header_filter.ts | 17 +-- .../m_keyboard_navigation.ts | 9 +- .../js/__internal/grids/grid_core/m_utils.ts | 2 +- .../grids/grid_core/selection/m_selection.ts | 11 +- .../virtual_scrolling_data_controller.ts | 2 +- .../grid_core/data_source/compatibility.ts | 15 +++ .../js/__internal/grids/new/grid_core/di.ts | 2 + .../__internal/grids/new/grid_core/widget.ts | 2 + .../grids/new/grid_core/widget_mock.ts | 3 + .../data_controller/m_data_controller.ts | 4 - .../data_source/data_source_controller.ts | 5 + .../grids/tree_list/editing/m_editing.ts | 11 +- .../testing/helpers/gridBaseMocks.js | 117 +++++++++++------- .../dataController.tests.js | 88 ++++++------- .../dataGrid.tests.js | 4 +- .../focus.integration.tests.js | 2 +- .../headerFilter.tests.js | 5 +- .../selection.tests.js | 10 +- .../virtualScrolling.integration.tests.js | 2 +- .../dataController.tests.js | 4 +- 30 files changed, 282 insertions(+), 181 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts index 76105e91357c..2cd56c186cb9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts @@ -187,8 +187,7 @@ const resolveKeysFromAllPages = ( component: InternalGrid, indexes: number[], ): Promise => { - const dataController = component.getController('data'); - const isRemotePaging = !!dataController.dataSource()?.remoteOperations()?.paging; + const isRemotePaging = !!component.getController('dataSource').remoteOperations().paging; return isRemotePaging ? resolveKeysFromAllPagesRemote(component, indexes) diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts index 94ac23fecd5f..75a7bdf788c8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts @@ -5,6 +5,7 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsController } from '../../columns_controller/m_columns_controller'; import type { DataController } from '../../data_controller/data_controller'; +import type { DataSourceController } from '../../data_source/data_source_controller'; import type { ChangedEvent, RawItemData } from '../../data_source_adapter/types'; import { Controller } from '../../m_modules'; import type { RowKey } from '../../m_types'; @@ -16,6 +17,8 @@ import { AIColumnIntegrationController } from './m_ai_column_integration_control export class AIColumnController extends Controller { private dataController!: DataController; + private dataSourceController!: DataSourceController; + private columnsController!: ColumnsController; private aiColumnIntegrationController!: AIColumnIntegrationController; @@ -73,7 +76,7 @@ export class AIColumnController extends Controller { private subscribeToDataSourceChanged(): void { this.dataSourceChangedHandler = this.handleDataSourceChanged.bind(this); - this.dataController.dataSource()?.changed.add(this.dataSourceChangedHandler); + this.dataSourceController.getAdapter()?.changed.add(this.dataSourceChangedHandler); } private unsubscribeFromDataControllerChanged(): void { @@ -204,6 +207,7 @@ export class AIColumnController extends Controller { public init(): void { this.columnsController = this.getController('columns'); this.dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.aiColumnIntegrationController = new AIColumnIntegrationController(this.component); this.aiColumnIntegrationController.init(); @@ -326,7 +330,7 @@ export class AIColumnController extends Controller { public dispose(): void { super.dispose(); - this.dataController.dataSource()?.changed.remove(this.dataSourceChangedHandler); + this.dataSourceController.getAdapter()?.changed.remove(this.dataSourceChangedHandler); this.unsubscribeFromStoreEvents(); this.unsubscribeFromDataControllerChanged(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts index 1294df3428d5..d2550e54c791 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts @@ -38,13 +38,9 @@ const getIsPaging = (instance: DataGridInstance): unknown => { return dataController._isPaging as unknown; }; -// The controller's `dataSource()` is the DataSourceAdapter, not the raw DataSource. -const getAdapter = (instance: DataGridInstance): PagingAdapter => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dataController = instance.getController('data') as any; - - return dataController.dataSource() as PagingAdapter; -}; +const getAdapter = (instance: DataGridInstance): PagingAdapter => instance + .getController('dataSource') + .getAdapter() as unknown as PagingAdapter; describe('DataController paging', () => { beforeEach(beforeTest); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 989298f6f505..9683aeda22f6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -281,16 +281,16 @@ export class DataController extends modules.Controller { this.reset(); break; case 'paging': { - const dataSource = this.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource) { - const changedPagingOptions = this.applyPagingOptions(dataSource); + if (dataSourceAdapter) { + const changedPagingOptions = this.applyPagingOptions(dataSourceAdapter); if (changedPagingOptions.hasChanges) { - const pageIndex = dataSource.pageIndex(); + const pageIndex = dataSourceAdapter.pageIndex(); this._isPaging = changedPagingOptions.isPageIndexChanged; - dataSource.load().done(() => { + dataSourceAdapter.load().done(() => { this._isPaging = false; this.pageChanged.fire(pageIndex); }); @@ -303,11 +303,11 @@ export class DataController extends modules.Controller { this.reset(); break; case 'columns': { - const dataSource = this.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource?.isLoading() && args.name === args.fullName) { + if (dataSourceAdapter?.isLoading() && args.name === args.fullName) { this._useSortingGroupingFromColumns = true; - dataSource.load(); + dataSourceAdapter.load(); } break; } @@ -1114,9 +1114,7 @@ export class DataController extends modules.Controller { } private readonly changingHandler = (e: ChangingEvent): void => { - const dataSource = this.dataSource(); - - if (!dataSource) { + if (!this.dataSourceController.hasAdapter()) { return; } @@ -1165,7 +1163,7 @@ export class DataController extends modules.Controller { return; } - const operationTypes = this.dataSource()?.operationTypes() ?? undefined; + const operationTypes = this.dataSourceController.operationTypes() ?? undefined; change.isDataChanged = true; change.repaintChangesOnly = resolveRepaintChangesOnly( @@ -1188,8 +1186,8 @@ export class DataController extends modules.Controller { } public loadingOperationTypes(): OperationTypes { - const dataSource = this.dataSource(); - const operationTypes: OperationTypes | undefined = dataSource?.loadingOperationTypes(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); + const operationTypes: OperationTypes | undefined = dataSourceAdapter?.loadingOperationTypes(); return operationTypes ?? {}; } @@ -1372,10 +1370,6 @@ export class DataController extends modules.Controller { return this._dataSource ? this._dataSource.pageCount() : 1; } - public dataSource(): DataSourceAdapter | undefined { - return this._dataSource ?? undefined; - } - public store(): Store | undefined { return this._dataSource?.store(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts index 96eefe8ae70f..1ce4adbd61d0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts @@ -33,6 +33,12 @@ const OTHER_DATA = [ { id: 3, parentId: 0, value: 'c' }, ]; +// These assertions pin the _dataSource mirror, so they have to read the protected field rather +// than a delegating method. +const getMirroredAdapter = ( + instance: { getController: (name: 'data') => unknown }, +): unknown => (instance.getController('data') as { _dataSource?: unknown })._dataSource; + interface TreeListInstance extends TreeList { getController: (name: T) => Controllers[T]; } @@ -103,7 +109,7 @@ describe('dataSource controller holds the adapter', () => { it('holds the same adapter object as DataController', async () => { const { instance } = await createDataGrid({ dataSource: DATA }); const dataSourceController = instance.getController('dataSource'); - const adapter = instance.getController('data').dataSource(); + const adapter = getMirroredAdapter(instance); expect(adapter).toBeTruthy(); expect(dataSourceController.hasAdapter()).toBe(true); @@ -113,14 +119,13 @@ describe('dataSource controller holds the adapter', () => { it('follows the rebuilt adapter when the dataSource option changes', async () => { const { instance } = await createDataGrid({ dataSource: DATA }); const dataSourceController = instance.getController('dataSource'); - const dataController = instance.getController('data'); const firstAdapter = dataSourceController.getAdapter(); instance.option('dataSource', OTHER_DATA); await flushAsync(); expect(dataSourceController.getAdapter()).not.toBe(firstAdapter); - expect(dataSourceController.getAdapter()).toBe(dataController.dataSource()); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); }); it('releases the adapter when the dataSource option is cleared', async () => { @@ -132,7 +137,7 @@ describe('dataSource controller holds the adapter', () => { expect(dataSourceController.hasAdapter()).toBe(false); expect(dataSourceController.getAdapter()).toBeNull(); - expect(instance.getController('data').dataSource()).toBeUndefined(); + expect(getMirroredAdapter(instance)).toBeNull(); expect(dataSourceController.getDataSource()).toBeNull(); expect(dataSourceController.store()).toBeUndefined(); }); @@ -147,7 +152,7 @@ describe('dataSource controller holds the adapter', () => { await flushAsync(); expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); }); it('still holds the same adapter after a refresh', async () => { @@ -159,18 +164,17 @@ describe('dataSource controller holds the adapter', () => { await refreshed; expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); }); it('releases the adapter on dispose', async () => { const { $container, instance } = await createDataGrid({ dataSource: DATA }); const dataSourceController = instance.getController('dataSource'); - const dataController = instance.getController('data'); instance.dispose(); $container.remove(); - expect(dataController.dataSource()).toBeUndefined(); + expect(getMirroredAdapter(instance)).toBeNull(); expect(dataSourceController.hasAdapter()).toBe(false); }); @@ -181,7 +185,7 @@ describe('dataSource controller holds the adapter', () => { const dataSourceController = instance.getController('dataSource'); expect(dataSourceController.hasAdapter()).toBe(true); - expect(dataSourceController.getAdapter()).toBe(instance.getController('data').dataSource()); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); } finally { disposeTreeList($container); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts index 7a70ce031ba9..e371afe6f7ee 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts @@ -4,9 +4,10 @@ import { extend } from '@js/core/utils/extend'; import type Store from '@ts/data/abstract_store'; import type { StoreKey } from '@ts/data/abstract_store'; import type { DataSource } from '@ts/data/data_source/data_source'; +import type { StoreLoadOptions } from '@ts/data/data_source/types'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { - DataSourceAdapterProvider, RawItemData, RemoteOperationsOptions, + DataSourceAdapterProvider, LoadOperation, OperationTypes, RawItemData, RemoteOperationsOptions, } from '@ts/grids/grid_core/data_source_adapter/types'; import modules from '@ts/grids/grid_core/m_modules'; @@ -102,4 +103,20 @@ export class DataSourceController extends modules.Controller { public getDataIndexGetter(): ((data: RawItemData) => number) | undefined { return this.adapter?.getDataIndexGetter(); } + + public operationTypes(): OperationTypes | null | undefined { + return this.adapter?.operationTypes(); + } + + public isLoading(): boolean { + return this.adapter?.isLoading() ?? false; + } + + public select(): StoreLoadOptions['select'] { + return this.adapter?.select(); + } + + public lastLoadOptions(): Partial> { + return this.adapter?.lastLoadOptions() ?? {}; + } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts index 92dcc54ab619..7717bc4aabad 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts @@ -117,6 +117,8 @@ class EditingControllerImpl extends modules.ViewController { protected _dataController!: Controllers['data']; + protected dataSourceController!: Controllers['dataSource']; + protected adaptiveColumnsController!: Controllers['adaptiveColumns']; protected _validatingController!: Controllers['validating']; @@ -170,6 +172,7 @@ class EditingControllerImpl extends modules.ViewController { public init() { this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.adaptiveColumnsController = this.getController('adaptiveColumns'); this._validatingController = this.getController('validating'); this._editorFactoryController = this.getController('editorFactory'); @@ -1812,7 +1815,7 @@ class EditingControllerImpl extends modules.ViewController { const results = []; const deferreds = []; const dataChanges = []; - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); when(this._fireOnSaving()) .done(({ cancel, changes }) => { @@ -1824,17 +1827,17 @@ class EditingControllerImpl extends modules.ViewController { if (deferreds.length) { this._refocusEditCell = true; - dataSource?.beginLoading(); + dataSourceAdapter?.beginLoading(); when(...deferreds).done(() => { if (this._processSaveEditDataResult(results)) { this._endSaving(dataChanges, changes, result); } else { - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); result.resolve(); } }).fail((error) => { - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); result.resolve(error); }); @@ -1855,11 +1858,11 @@ class EditingControllerImpl extends modules.ViewController { } private _endSaving(dataChanges, changes, deferred) { - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); this._beforeEndSaving(changes); - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); this._refreshDataAfterSave(dataChanges, changes, deferred); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts index c25f156bd2df..7b472e65d6c6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts @@ -14,6 +14,7 @@ import { } from '@ts/filter_builder/m_utils'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; import { registerKeyboardAction } from '@ts/grids/grid_core/m_accessibility'; import modules from '@ts/grids/grid_core/m_modules'; @@ -33,12 +34,15 @@ export class FilterPanelView extends modules.View { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _filterSyncController!: FilterSyncController; private readonly _filterValueBuffer: any; public init() { this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._filterSyncController = this.getController('filterSync'); @@ -46,7 +50,7 @@ export class FilterPanelView extends modules.View { } public isVisible() { - return !!(this.option('filterPanel.visible') && this._dataController.dataSource()); + return !!this.option('filterPanel.visible') && this.dataSourceController.hasAdapter(); } protected _renderCore() { diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts index b4a100906344..0c977a8c22ff 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts @@ -17,6 +17,7 @@ import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsResizerViewController } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import modules from '@ts/grids/grid_core/m_modules'; @@ -204,7 +205,11 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH private _applyFilterViewController!: ApplyFilterViewController; + private dataSourceController!: DataSourceController; + public init() { + this.dataSourceController = this.getController('dataSource'); + super.init(); this._applyFilterViewController = this.getController('applyFilter'); } @@ -545,12 +550,12 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH private _renderEditor($editorContainer, options) { $editorContainer.empty(); const $element = $('
').appendTo($editorContainer); - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); if (options.lookup && this.option('syncLookupFilterValues')) { const filter = this._dataController.getCombinedFilterWithExcludedColumn(options); - const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(options, dataSource, filter); + const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(options, dataSourceAdapter, filter); const lookupOptions = { ...options, lookup: { @@ -739,8 +744,7 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH } protected _handleDataChanged(e) { - const dataSource = this._dataController?.dataSource?.(); - const lastLoadOptions = dataSource?.lastLoadOptions?.(); + const lastLoadOptions = this.dataSourceController.lastLoadOptions(); // @ts-expect-error super._handleDataChanged.apply(this, arguments); @@ -760,7 +764,7 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH } const columns = this._columnsController.getVisibleColumns(); - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const rowIndex = this.element().find(`.${this.addWidgetPrefix(FILTER_ROW_CLASS)}`).index(); if (rowIndex === -1) { @@ -789,7 +793,7 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH editor.option('items', [...items, selectedItem]); } - const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSource, filter); + const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSourceAdapter, filter); editor.option('dataSource', lookupDataSource); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index ae40935ea655..fee97a227e9e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -5,11 +5,11 @@ import { Deferred, type DeferredObj, when } from '@js/core/utils/deferred'; import { each } from '@js/core/utils/iterator'; import { isBoolean, isDefined } from '@js/core/utils/type'; import type { StoreChange } from '@js/data/store'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { Key } from '@ts/grids/new/grid_core/data_controller/types'; import type { ColumnsController } from '../columns_controller/m_columns_controller'; import type { DataController } from '../data_controller/data_controller'; -import type { DataSourceController } from '../data_source/data_source_controller'; import type { EditingController } from '../editing/m_editing'; import { isNewRowTempKey } from '../editing/m_editing_utils'; import type { EditorFactory } from '../editor_factory/m_editor_factory'; @@ -40,6 +40,10 @@ export class FocusController extends core.ViewController { return this.getController('data'); } + private getDataSourceController(): DataSourceController { + return this.getController('dataSource'); + } + public init() { this.component._optionsByReference.focusedRowKey = true; } @@ -214,7 +218,7 @@ export class FocusController extends core.ViewController { const d = new Deferred(); const rowsView = this.getView('rowsView'); - if (key === undefined || !this.getDataController().dataSource()) { + if (key === undefined || !this.getDataSourceController().hasAdapter()) { return d.reject().promise(); } @@ -915,6 +919,14 @@ const editing = (Base: ModuleType) => class FocusEditingContr const rowsView = (Base: ModuleType) => class RowsViewFocusController extends Base { private _scrollToFocusOnResize: any; + private dataSourceController!: DataSourceController; + + public init(): void { + this.dataSourceController = this.getController('dataSource'); + + super.init(); + } + protected _createRow(row) { // @ts-expect-error const $row = super._createRow.apply(this, arguments); @@ -968,8 +980,7 @@ const rowsView = (Base: ModuleType) => class RowsViewFocusController e let columnIndex = this.option('focusedColumnIndex')!; const $row = this._findRowElementForTabIndex(); - const dataSource = this._dataController.dataSource(); - const operationTypes = dataSource?.operationTypes(); + const operationTypes = this.dataSourceController.operationTypes(); const isPaging = !operationTypes || operationTypes.paging; if (!isDefined(this._scrollToFocusOnResize)) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 5a11f96b354a..5ef75a4f093f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -16,6 +16,7 @@ import { restoreFocus, saveFocusedElementInfo } from '@js/ui/shared/accessibilit import filterUtils from '@js/ui/shared/filtering'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import Modules from '@ts/grids/grid_core/m_modules'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -120,11 +121,14 @@ export class HeaderFilterController extends Modules.ViewController { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _headerFilterView!: HeaderFilterView; public init() { this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._headerFilterView = this.getView('headerFilterView'); } @@ -230,8 +234,8 @@ export class HeaderFilterController extends Modules.ViewController { } private getDataSource(column) { - const dataSource = this._dataController.dataSource(); - const remoteGrouping = dataSource?.remoteOperations().grouping; + const dataSourceAdapter = this.dataSourceController.getAdapter(); + const remoteGrouping = this.dataSourceController.remoteOperations().grouping; const group = gridCoreUtils.getHeaderFilterGroupParameters(column, remoteGrouping); const headerFilterDataSource = column.headerFilter?.dataSource; const headerFilterOptions = this.option('headerFilter'); @@ -240,7 +244,7 @@ export class HeaderFilterController extends Modules.ViewController { component: this.component, }; - if (!dataSource) return; + if (!dataSourceAdapter) return; if (isDefined(headerFilterDataSource) && !isFunction(headerFilterDataSource)) { options.dataSource = normalizeDataSourceOptions(headerFilterDataSource); @@ -250,7 +254,7 @@ export class HeaderFilterController extends Modules.ViewController { if (this.option('syncLookupFilterValues')) { const filter = this._dataController.getCombinedFilterWithExcludedColumn(column); - options.dataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSource, filter); + options.dataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSourceAdapter, filter); } else { options.dataSource = gridCoreUtils.normalizeLookupDataSource(column.lookup); } @@ -269,7 +273,7 @@ export class HeaderFilterController extends Modules.ViewController { // TODO remove in 16.1 options.dataField = column.dataField || column.name; - dataSource.customLoader.load(options).done(({ data }) => { + dataSourceAdapter.customLoader.load(options).done(({ data }) => { const convertUTCDates = remoteGrouping && isUTCFormat(column.serializationFormat) && cutoffLevel > 3; if (convertUTCDates) { // @ts-expect-error queryByOptions().toArray() is typed as unknown[] @@ -349,8 +353,7 @@ export class HeaderFilterController extends Modules.ViewController { if (column) { const groupInterval = filterUtils.getGroupInterval(column); - const dataSource = that._dataController.dataSource(); - const remoteFiltering = dataSource && dataSource.remoteOperations().filtering; + const remoteFiltering = that.dataSourceController.remoteOperations().filtering; const previousOnHidden = options.onHidden; extend(options, column, { diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts index cb1e1d542bdb..37d84665cbb4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts @@ -28,6 +28,7 @@ import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { RowIndexCorrection } from '@ts/grids/grid_core/data_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; import type { RowsViewScrollEvent } from '@ts/grids/grid_core/views/types'; @@ -131,6 +132,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo protected _dataController!: DataController & Partial; + protected dataSourceController!: DataSourceController; + private _selectionController!: Controllers['selection']; protected _editingController!: Controllers['editing']; @@ -152,6 +155,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo // #region Initialization public init() { this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._selectionController = this.getController('selection'); this._editingController = this.getController('editing'); this._editorFactory = this.getController('editorFactory'); @@ -704,7 +708,6 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const $row = this._focusedView && this._focusedView.getRow(visibleRowIndex); const $event = eventArgs.originalEvent; const isUpArrow = eventArgs.keyName === 'upArrow'; - const dataSource = this._dataController.dataSource(); const isRowEditingInCurrentRow = this._editingController?.isEditRowByIndex?.(visibleRowIndex); const isEditingNavigationMode = this._isFastEditingStarted(); const isInsideMasterDetail = this.isInsideMasterDetail($($event?.target)); @@ -719,8 +722,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if ( this._isVirtualRowRender() && isUpArrow - && dataSource - && !dataSource.isLoading() + && this.dataSourceController.hasAdapter() + && !this.dataSourceController.isLoading() ) { const rowHeight = getOuterHeight($row); const rowIndex = this._focusedCellPosition.rowIndex - 1; diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index abdc6d0299b8..971de5eadb7f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -677,7 +677,7 @@ export default { return normalizeDataSourceOptions(lookupDataSourceOptions); }, - getWrappedLookupDataSource(column, dataSource: DataSourceAdapter | undefined, filter) { + getWrappedLookupDataSource(column, dataSource: DataSourceAdapter | null | undefined, filter) { if (!dataSource) { return []; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts index 3af9873058e0..3a5167dc6066 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts @@ -27,6 +27,7 @@ import type { SelectionChangeEvent, SelectionFilter, SelectionOptions } from '@t import type { DataController } from '../data_controller/data_controller'; import type { DataChange } from '../data_controller/types'; +import type { DataSourceController } from '../data_source/data_source_controller'; import { isEditRow } from '../keyboard_navigation/utils'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; @@ -120,6 +121,8 @@ const selectionHeaderTemplate = (container, options) => { export class SelectionController extends modules.Controller { protected _dataController!: DataController; + protected dataSourceController!: DataSourceController; + private _columnsController!: ColumnsController; protected _stateStoringController!: StateStoringController; @@ -144,6 +147,7 @@ export class SelectionController extends modules.Controller { } this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._stateStoringController = this.getController('stateStoring'); // mode has a default value @@ -206,6 +210,7 @@ export class SelectionController extends modules.Controller { */ protected _getSelectionConfig(): SelectionOptions { const dataController = this._dataController; + const { dataSourceController } = this; const columnsController = this._columnsController; const selectionOptions: any = this.option('selection') ?? {}; const { deferred } = selectionOptions; @@ -236,11 +241,11 @@ export class SelectionController extends modules.Controller { return dataController?.keyOf(item); }, dataFields() { - return dataController.dataSource()?.select(); + return dataSourceController.select(); }, load(options) { // @ts-expect-error - return dataController.dataSource()?.customLoader.load(options) || new Deferred().resolve([]); + return dataSourceController.getAdapter()?.customLoader.load(options) || new Deferred().resolve([]); }, // eslint-disable-next-line plainItems(cached?) { @@ -261,7 +266,7 @@ export class SelectionController extends modules.Controller { }, totalCount: () => dataController.totalCount(), getLoadOptions(loadItemIndex, focusedItemIndex, shiftItemIndex) { - const { sort, filter } = dataController.dataSource()?.lastLoadOptions() ?? {}; + const { sort, filter } = dataSourceController.lastLoadOptions(); let minIndex = Math.min(loadItemIndex, focusedItemIndex); let maxIndex = Math.max(loadItemIndex, focusedItemIndex); diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts index ccef9dd99ab3..d85f9fa8473a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts @@ -123,7 +123,7 @@ export const virtualScrollingDataControllerExtender = ( const rowIndex = Math.floor(itemIndex) - rowIndexOffset; const { component } = this; const scrollable = component.getScrollable && component.getScrollable(); - const isSortingOperation = this.dataSource()?.operationTypes()?.sorting; + const isSortingOperation = this.dataSourceController.operationTypes()?.sorting; if (scrollable && !isSortingOperation && rowIndex >= 0) { const rowElement = component.getRowElement(rowIndex); diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts new file mode 100644 index 000000000000..f618d194e289 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts @@ -0,0 +1,15 @@ +import { DataController } from '../data_controller/data_controller'; + +// Reused grid_core views reach the adapter through the dataSource controller. The new +// architecture has no adapter, so the mock answers from the DataSource itself. +export class CompatibilityDataSourceController { + public static dependencies = [DataController] as const; + + constructor( + private readonly realDataController: DataController, + ) {} + + public hasAdapter(): boolean { + return !!this.realDataController.dataSource.peek(); + } +} diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/di.ts b/packages/devextreme/js/__internal/grids/new/grid_core/di.ts index 56871436920a..4755ccb7a770 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/di.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/di.ts @@ -6,6 +6,7 @@ import { AccessibilityController } from './accessibility/controller'; import * as ColumnChooserModule from './column_chooser/index'; import * as ColumnsControllerModule from './columns_controller/index'; import * as DataControllerModule from './data_controller/index'; +import { CompatibilityDataSourceController } from './data_source/compatibility'; import { ConfirmController } from './editing/confirm_controller'; import { EditingController } from './editing/controller'; import { EditPopupView } from './editing/popup/view'; @@ -33,6 +34,7 @@ import { ToolbarView } from './toolbar/view'; export function register(diContext: DIContext): void { diContext.register(DataControllerModule.DataController); diContext.register(DataControllerModule.CompatibilityDataController); + diContext.register(CompatibilityDataSourceController); diContext.register(ItemsController); diContext.register(ColumnsControllerModule.ColumnsController); diContext.register(SelectionControllerModule.Controller); diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts index 76725b7d6086..cb045d2e63b8 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts @@ -16,6 +16,7 @@ import * as ColumnChooserModule from './column_chooser/index'; import { CompatibilityColumnsController } from './columns_controller/compatibility'; import * as ColumnsControllerModule from './columns_controller/index'; import * as DataControllerModule from './data_controller/index'; +import { CompatibilityDataSourceController } from './data_source/compatibility'; import * as di from './di'; import * as EditingModule from './editing/index'; import { EditPopupView } from './editing/popup/view'; @@ -100,6 +101,7 @@ export class GridCoreNewBase< this.diContext.registerInstance(WidgetMock, new WidgetMock( this, this.diContext.get(DataControllerModule.CompatibilityDataController), + this.diContext.get(CompatibilityDataSourceController), this.diContext.get(CompatibilityColumnsController), this.diContext.get(CompatibilityHeaderFilterController), this.diContext.get(CompatibilityFilterSyncController), diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts index 5c99d89670cd..6eeca8104fd3 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts @@ -1,5 +1,6 @@ import type { CompatibilityColumnsController } from './columns_controller/compatibility'; import type { CompatibilityDataController } from './data_controller'; +import type { CompatibilityDataSourceController } from './data_source/compatibility'; import type { CompatibilityFilterSyncController } from './filtering/filter_sync/compatibility'; import type { CompatibilityHeaderFilterController } from './filtering/header_filter/compatibility'; import type { GridCoreNewBase } from './widget'; @@ -9,6 +10,7 @@ export class WidgetMock { private readonly _controllers = { data: this.data, + dataSource: this.dataSource, columns: this.columns, headerFilter: this.headerFilter, filterSync: this.filterSync, @@ -18,6 +20,7 @@ export class WidgetMock { // eslint-disable-next-line @typescript-eslint/no-explicit-any private readonly widget: GridCoreNewBase, private readonly data: CompatibilityDataController, + private readonly dataSource: CompatibilityDataSourceController, private readonly columns: CompatibilityColumnsController, private readonly headerFilter: CompatibilityHeaderFilterController, private readonly filterSync: CompatibilityFilterSyncController, diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index 309f50bd4481..ff079c7767d0 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -10,10 +10,6 @@ import treeListCore from '../m_core'; export class TreeListDataController extends DataController { public declare _dataSource?: DataSourceAdapterTreeList | null; - public dataSource(): DataSourceAdapterTreeList | undefined { - return this._dataSource ?? undefined; - } - private _getNodeLevel(node) { let level = -1; while (node.parent) { diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts index 70f64d785c20..31877b804a4f 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts @@ -1,9 +1,14 @@ import { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; +import type { DataSourceAdapterTreeList } from '@ts/grids/tree_list/data_source_adapter/m_data_source_adapter'; import dataSourceAdapterProvider from '@ts/grids/tree_list/data_source_adapter/m_data_source_adapter'; export class TreeListDataSourceController extends DataSourceController { protected getAdapterProvider(): DataSourceAdapterProvider { return dataSourceAdapterProvider; } + + public getAdapter(): DataSourceAdapterTreeList | null { + return super.getAdapter() as DataSourceAdapterTreeList | null; + } } diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts index 7c23b3a932b7..7f4f4bc1c79c 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts @@ -15,6 +15,7 @@ import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import type { RowsView } from '../../grid_core/views/m_rows_view'; import type { TreeListDataController } from '../data_controller/m_data_controller'; +import type { TreeListDataSourceController } from '../data_source/data_source_controller'; import treeListCore from '../m_core'; const TREELIST_EXPAND_ICON_CONTAINER_CLASS = 'dx-treelist-icon-container'; @@ -25,6 +26,8 @@ const DATA_EDIT_DATA_INSERT_TYPE = 'insert'; class EditingController extends editingModule.controllers.editing { protected declare _dataController: TreeListDataController; + protected declare dataSourceController: TreeListDataSourceController; + protected _generateNewItem(key) { const item: any = super._generateNewItem(key); @@ -43,7 +46,7 @@ class EditingController extends editingModule.controllers.editing { } protected _setInsertAfterOrBeforeKey(change, parentKey) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const key = parentKey || dataSourceAdapter?.parentKeyOf(change.data); if (key !== undefined && key !== this.option('rootValue')) { @@ -55,7 +58,7 @@ class EditingController extends editingModule.controllers.editing { } protected _getLoadedRowIndex(items, change, isProcessedItems?) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const insertParentKey = this._getInternalData(change.key)?.insertInfo?.parentKey; const parentKey = insertParentKey !== undefined ? insertParentKey @@ -135,7 +138,7 @@ class EditingController extends editingModule.controllers.editing { protected _addRowCore(data, parentKey, oldEditRowIndex) { const rootValue = this.option('rootValue'); - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const parentKeyGetter = dataSourceAdapter?.createParentIdGetter(); parentKey = parentKeyGetter ? parentKeyGetter(data) : parentKey; @@ -159,7 +162,7 @@ class EditingController extends editingModule.controllers.editing { } protected _initNewRow(options, parentKey?) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const parentIdSetter = dataSourceAdapter?.createParentIdSetter(); parentIdSetter?.(options.data, parentKey); diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 27c1041868bd..41f2f2da46dc 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -1,6 +1,71 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commonUtils, typeUtils, ArrayStore, nameWidget) { const exports = {}; + exports.MockDataSourceAdapter = function(options) { + const store = function() { + return new ArrayStore(options.items); + }; + + const loadCustomResult = (loadOptions) => { + const d = $.Deferred(); + + store().load(loadOptions) + .done((data, extra) => { + d.resolve({ data: data, extra: extra }); + }) + .fail(d.reject); + + return d; + }; + + return { + beginLoading: function() { + }, + endLoading: function() { + }, + remoteOperations: function() { + return {}; + }, + loadOptions: function() { + return {}; + }, + key: function() { + return options.key; + }, + isLoading: function() { + return false; + }, + lastLoadOptions: function() { + return {}; + }, + operationTypes: function() { + return undefined; + }, + select: function() { + return undefined; + }, + getDataIndexGetter: function() { + return undefined; + }, + dispose: function() { + }, + store: store, + load: function(loadOptions) { + return store().load(loadOptions); + }, + customLoader: { + load: loadCustomResult, + loadFromStore: loadCustomResult, + isLoading: function() { + return false; + }, + isLoadingAll: function() { + return false; + } + } + }; + }; + exports.MockDataController = function(options) { if(!typeUtils.isDefined(options.itemsCount)) { @@ -37,50 +102,7 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo changedArgs: [], - dataSource: function() { - const store = function() { - return new ArrayStore(options.items); - }; - - const loadCustomResult = (loadOptions) => { - const d = $.Deferred(); - - store().load(loadOptions) - .done((data, extra) => { - d.resolve({ data: data, extra: extra }); - }) - .fail(d.reject); - - return d; - }; - - return { - beginLoading: function() { - }, - endLoading: function() { - }, - remoteOperations: function() { - return {}; - }, - loadOptions: function() { - return {}; - }, - store: store, - load: function(loadOptions) { - return store().load(loadOptions); - }, - customLoader: { - load: loadCustomResult, - loadFromStore: loadCustomResult, - isLoading: function() { - return false; - }, - isLoadingAll: function() { - return false; - } - } - }; - }, + mockOptions: options, pageSize: function(value) { if(value === undefined) { @@ -1214,6 +1236,13 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo options && options.controllers && $.extend(that._controllers, options.controllers); options && options.views && $.extend(that._views, options.views); + const mockedDataController = options && options.controllers && options.controllers.data; + if(mockedDataController && mockedDataController.mockOptions && that._controllers.dataSource) { + that._controllers.dataSource.adapter = new exports.MockDataSourceAdapter( + mockedDataController.mockOptions + ); + } + $.each(that._controllers, function(name) { that[name + 'Controller'] = this; this.init && this.init(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js index ddf33f7e262b..f9e784373cba 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js @@ -241,7 +241,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.dataPushedHandlerProxy = dataPushedHandlerSpy; this.dataController.setDataSource(dataSource); - dataSource = this.dataController.dataSource(); + dataSource = this.dataSourceController.getAdapter(); dataSource.load(); // assert @@ -319,7 +319,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.option('dataSource', []); - const dataSource = this.dataController.dataSource()._dataSource; + const dataSource = this.dataSourceController.getDataSource(); this.dataController.changed.add(function(args) { changedCount++; @@ -873,7 +873,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act this.dataController.resetDataSource(); - const dataIndexGetter = this.dataSourceController.getAdapter().getDataIndexGetter(); + const dataIndexGetter = this.dataSourceController.getDataIndexGetter(); assert.deepEqual(this.dataController._columnsController.getSortDataSourceParameters(), [{ desc: false, selector: dataIndexGetter }], 'Sort parameters'); // assert assert.equal(dataSource.items()[0].name, 'Alex', 'Item0'); @@ -1997,13 +1997,13 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController.dataSource().sort(), 'sort parameters'); + assert.ok(this.dataSourceController.getAdapter().sort(), 'sort parameters'); // act this.columnsController.changeSortOrder(0, 'none'); // assert - assert.ok(!this.dataController.dataSource().sort(), 'no sort parameters'); + assert.ok(!this.dataSourceController.getAdapter().sort(), 'no sort parameters'); assert.deepEqual(this.dataController.items()[0].values, ['Alex', 30]); assert.deepEqual(this.dataController.items()[1].values, ['Dan', 25]); @@ -2098,12 +2098,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController.dataSource().group()); + assert.ok(this.dataSourceController.getAdapter().group()); // act this.columnsController.moveColumn(0, 0, 'group', 'header'); // assert - assert.ok(!this.dataController.dataSource().group()); + assert.ok(!this.dataSourceController.getAdapter().group()); assert.equal(this.dataController.items()[0].rowType, 'data'); }); @@ -2709,7 +2709,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), {}, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), {}, 'remote operations set correct'); }); QUnit.test('Enable all remote operations if groupPaging is true', function(assert) { @@ -2727,7 +2727,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: true, paging: true, @@ -2754,7 +2754,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: false, paging: true, @@ -2777,7 +2777,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), {}, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), {}, 'remote operations set correct'); }); QUnit.test('Set remoteOperations option to true', function(assert) { @@ -2794,7 +2794,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { filtering: true, sorting: true, paging: true, grouping: true, summary: true }, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: true, paging: true, grouping: true, summary: true }, 'remote operations set correct'); }); // T541798 @@ -3578,7 +3578,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft // B233350 QUnit.test('virtual items on end when visibleRowsCount < pageSize and last page size greater than half page size', function(assert) { const dataController = this.dataController; - this.dataSourceController.getAdapter().store().remove(999); + this.dataSourceController.store().remove(999); dataController.refresh(); dataController.setViewportItemIndex(998); assert.strictEqual(this.dataController.pageIndex(), 49); @@ -3615,7 +3615,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft const that = this; let countCallChanged = 0; let countCallDataSourceChanged = 0; - const dataSource = that.dataController.dataSource(); + const dataSource = that.dataSourceController.getAdapter(); that.dataController.changed.add(function() { that.columnOption('id', 'sortOrder', 'desc'); @@ -3641,7 +3641,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft const firstRow = rows[0]; // act - this.dataController.dataSource().changed.fire({ changeType: 'pageIndex' }); + this.dataSourceController.getAdapter().changed.fire({ changeType: 'pageIndex' }); // assert assert.strictEqual(this.getVisibleRows(), rows, 'rows are not changed'); @@ -4964,19 +4964,19 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { this.dataController.viewportSize(15); // assert - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.dataController.items().length, 10, 'initial visible items count'); // act this.dataController.setViewportPosition(500); this.clock.tick(10); const visibleItems = this.dataController.items(); - const loadedItems = this.dataController.dataSource().items(); + const loadedItems = this.dataSourceController.getAdapter().items(); // assert assert.deepEqual(this.dataController.getLoadPageParams(), { pageIndex: 2, loadPageCount: 4, skipForCurrentPage: 5 }, 'load page params after scrolling'); assert.deepEqual(this.dataController.pageIndex(), 2, 'page index after scrolling'); - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 4, 'load page count after scrolling'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 4, 'load page count after scrolling'); assert.equal(loadedItems.length, 40, 'loaded items count'); assert.deepEqual(loadedItems[0], { id: 21, name: 'Name 21' }, 'first loaded item'); assert.deepEqual(loadedItems[39], { id: 60, name: 'Name 60' }, 'last loaded item'); @@ -5149,7 +5149,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const viewportSizeSpy = sinon.spy(this.dataController.dataSource(), 'viewportSize'); + const viewportSizeSpy = sinon.spy(this.dataSourceController.getAdapter(), 'viewportSize'); try { this.dataController.viewportSize(); @@ -5176,7 +5176,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const viewportItemSizeSpy = sinon.spy(this.dataController.dataSource(), 'viewportItemSize'); + const viewportItemSizeSpy = sinon.spy(this.dataSourceController.getAdapter(), 'viewportItemSize'); try { this.dataController.viewportItemSize(); @@ -5203,7 +5203,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const setContentItemSizesSpy = sinon.spy(this.dataController.dataSource(), 'setContentItemSizes'); + const setContentItemSizesSpy = sinon.spy(this.dataSourceController.getAdapter(), 'setContentItemSizes'); try { this.dataController.setContentItemSizes([30]); @@ -5451,7 +5451,7 @@ QUnit.module('Virtual scrolling preload', { }, () => { QUnit.test('New mode. One page should be loaded on start', function(assert) { // arrange - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.getVisibleRows().length, 20, 'initial visible items count'); }); @@ -5459,7 +5459,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5476,7 +5476,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5493,7 +5493,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5510,7 +5510,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5526,7 +5526,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1000); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5543,7 +5543,7 @@ QUnit.module('Virtual scrolling preload', { this.dataController.setViewportPosition(1000); this.dataController.setViewportPosition(999); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5561,7 +5561,7 @@ QUnit.module('Virtual scrolling preload', { this.dataController.setViewportPosition(1000); this.dataController.setViewportPosition(999); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -6067,19 +6067,19 @@ QUnit.module('Infinite scrolling (ScrollingDataSource)', { this.dataController.viewportSize(15); // assert - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.dataController.items().length, 10, 'initial visible items count'); // act this.dataController.setViewportPosition(500); this.clock.tick(10); const visibleItems = this.dataController.items(); - const loadedItems = this.dataController.dataSource().items(); + const loadedItems = this.dataSourceController.getAdapter().items(); // assert assert.deepEqual(this.dataController.getLoadPageParams(), { pageIndex: 2, loadPageCount: 4, skipForCurrentPage: 5 }, 'load page params after scrolling'); assert.deepEqual(this.dataController.pageIndex(), 2, 'page index after scrolling'); - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 4, 'load page count after scrolling'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 4, 'load page count after scrolling'); assert.equal(loadedItems.length, 40, 'loaded items count'); assert.deepEqual(loadedItems[0], { id: 21, name: 'Name 21' }, 'first loaded item'); assert.deepEqual(loadedItems[39], { id: 60, name: 'Name 60' }, 'last loaded item'); @@ -12979,7 +12979,7 @@ QUnit.module('Partial update', { QUnit.test('update one row', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13011,7 +13011,7 @@ QUnit.module('Partial update', { QUnit.test('update several rows', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13048,7 +13048,7 @@ QUnit.module('Partial update', { QUnit.test('update with incorrect rowIndices', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13083,7 +13083,7 @@ QUnit.module('Partial update', { // T266696 QUnit.test('insert item with editing of the first item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); let changedArgs; this.dataController.changed.add(function(args) { @@ -13111,7 +13111,7 @@ QUnit.module('Partial update', { QUnit.test('insert one item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13148,7 +13148,7 @@ QUnit.module('Partial update', { QUnit.test('update and insert', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13218,7 +13218,7 @@ QUnit.module('Partial update', { QUnit.test('remove one item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13254,7 +13254,7 @@ QUnit.module('Partial update', { QUnit.test('update and remove', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13291,7 +13291,7 @@ QUnit.module('Partial update', { // T186415 QUnit.test('remove and update next row', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13327,7 +13327,7 @@ QUnit.module('Partial update', { QUnit.test('insert and remove', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -14457,7 +14457,7 @@ QUnit.module('Using DataSource instance', { try { // act this.dataSource.dispose(); - this.dataController.dataSource().dispose(true); + this.dataSourceController.getAdapter().dispose(true); // assert assert.ok(true, 'No exception'); @@ -15437,7 +15437,7 @@ QUnit.module('onOptionChanged', { that.option.restore(); sinon.stub(that, 'option').callsFake(function(optionName, value) { if(optionName === 'paging.pageSize' && value === 3) { - pageSize = that.dataController.dataSource().pageSize(); + pageSize = that.dataSourceController.getAdapter().pageSize(); } if(optionName === 'editing.changes') { return that.options.editing.changes; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js index 49bc940d87f1..074b5e6b1d11 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js @@ -1577,7 +1577,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { // assert assert.ok(dataGrid.getController('data').isEmpty(), 'no data'); - assert.ok(!dataGrid.getController('data').dataSource(), 'no dataSource'); + assert.ok(!dataGrid.getController('dataSource').getAdapter(), 'no dataSource'); assert.strictEqual(dataGrid.getController('data')._cachedProcessedItems, null, 'cached processed items are cleared'); // T1045202 assert.strictEqual(dataGrid.getController('columns')._dataSource, null, 'no dataSource inside columnsController'); // T1045202 assert.equal(dataGrid.getController('data').items().length, 0, 'items count'); @@ -2101,7 +2101,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { }); // assert - const dataSource = dataGrid.getController('data').dataSource(); + const dataSource = dataGrid.getController('dataSource').getAdapter(); assert.ok(!errorMessage, 'No error messages'); assert.ok(dataSource, 'dataSource assigned'); assert.ok(dataSource.requireTotalCount(), 'requireTotalCount assigned'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js index 1ff1c6b8d421..ac80de2240c1 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js @@ -1286,7 +1286,7 @@ QUnit.module('Initialization', baseModuleConfig, () => { }); const rowsView = dataGrid.getView('rowsView'); const $lastRow = rowsView.getRow(2); - const dataSource = dataGrid.getController('data').dataSource(); + const dataSource = dataGrid.getController('dataSource').getAdapter(); sinon.spy(dataSource, 'load'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js index 9924268baf7d..9a2b1631b03d 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js @@ -5,7 +5,6 @@ import gridCoreUtils from '__internal/grids/grid_core/m_utils'; import $ from 'jquery'; import ArrayStore from 'common/data/array_store'; -import { noop } from 'core/utils/common'; import ODataStore from 'common/data/odata/store'; import devices from '__internal/core/m_devices'; import { DataSource } from 'common/data/data_source/data_source'; @@ -536,10 +535,10 @@ QUnit.module('Header Filter', { const that = this; const testElement = $('#container'); + that.generateItems(5); that.setupDataGrid(); - that.dataController.dataSource = noop; - + that.dataSourceController.disposeAdapter(); that.columnHeadersView.render(testElement); that.headerFilterView.render(testElement); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js index 115ab7f16195..dad71f4cf670 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js @@ -2120,7 +2120,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); this.selectionController.selectRows([2]); @@ -2146,7 +2146,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true }, }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); // act this.selectionController.selectRows([1]); @@ -2177,7 +2177,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); // act this.selectionController.selectAll(); @@ -2206,7 +2206,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec mode: 'multiple', } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); sinon.spy(this.selectionController, '_updateSelectedOnPush'); sinon.spy(this.selectionController, 'getSelectedRowsData'); @@ -2227,7 +2227,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec deferred: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); sinon.spy(this.selectionController, '_updateSelectedOnPush'); sinon.spy(this.selectionController, 'getSelectedRowsData'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js index ff4057a2e1ca..fcf55d210597 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js @@ -6916,7 +6916,7 @@ QUnit.module('Infinite Scrolling', baseModuleConfig, () => { this.clock.tick(10); // assert assert.ok(dataGrid.getController('data').viewportSize() > 0); - assert.ok(!dataGrid.getController('data').dataSource().requireTotalCount()); + assert.ok(!dataGrid.getController('dataSource').getAdapter().requireTotalCount()); }); QUnit.test('New mode. Load panel should not be displayed at the bottom when all items are loaded', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js index 5f2f56fb3699..7084f5faf6d2 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js @@ -72,7 +72,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.notOk(this.dataController.dataSource().paginate(), 'paginate is disabled'); + assert.notOk(this.dataSourceController.getAdapter().paginate(), 'paginate is disabled'); }); QUnit.test('Paginate should be enabled when virtual scrolling is enabled', function(assert) { @@ -85,7 +85,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.ok(this.dataController.dataSource().paginate(), 'paginate is enabled'); + assert.ok(this.dataSourceController.getAdapter().paginate(), 'paginate is enabled'); }); QUnit.test('Initialize from dataSource with plain structure', function(assert) { From 742b7d42b79352f5314099b408e8488ae629d59f Mon Sep 17 00:00:00 2001 From: Andrey Vorobev <738482+vorobey@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:46:42 +0000 Subject: [PATCH 26/45] T1334644: Themebuilder swatch fix (#35075) Signed-off-by: Andrey Vorobev <738482+vorobey@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/metadata/generator.ts | 2 +- .../src/modules/post-compiler.ts | 2 + .../data/scss/widgets/generic/_colors.scss | 2 +- .../widgets/generic/accordion/_colors.scss | 4 +- .../tests/metadata/generator.test.ts | 2 +- .../tests/modules/builder.test.ts | 18 ++++ .../tests/modules/post-compiler.test.ts | 90 +++++++++++++++++++ 7 files changed, 114 insertions(+), 6 deletions(-) diff --git a/packages/devextreme-themebuilder/src/metadata/generator.ts b/packages/devextreme-themebuilder/src/metadata/generator.ts index 2a328f6a1451..446da5b8fb70 100644 --- a/packages/devextreme-themebuilder/src/metadata/generator.ts +++ b/packages/devextreme-themebuilder/src/metadata/generator.ts @@ -112,7 +112,7 @@ export default class MetadataGenerator { }); const overriddenVariables = MetadataGenerator.getOverriddenVariables(metaItems); - const collector = `$never-used: collector(${MetadataGenerator.getMapFromMeta(metaItems)});\n`; + const collector = `$_never-used: collector(${MetadataGenerator.getMapFromMeta(metaItems)});\n`; modifiedContent = overriddenVariables + '\n\n' + modifiedContent + collector; return modifiedContent; diff --git a/packages/devextreme-themebuilder/src/modules/post-compiler.ts b/packages/devextreme-themebuilder/src/modules/post-compiler.ts index b0357609a572..a88720fdc708 100644 --- a/packages/devextreme-themebuilder/src/modules/post-compiler.ts +++ b/packages/devextreme-themebuilder/src/modules/post-compiler.ts @@ -68,8 +68,10 @@ export function fixSwatchCss(css: string | Buffer, swatchClass: string, colorSch const swatchOrderRegex = new RegExp(`([ \\t]*)([\\w\\.#:\\*][\\w\\.#:\\*\\->()\\s]*)(${escapedSelector}\\s)([^,{+~]*)`, 'gm'); const changeTypographyRulesOrderRegex = /(\.dx-swatch-.*?)\s(\.dx-theme-.*?-typography)(.*?)(\s{|,)/g; const themeMarkerRegex = /(\.dx-theme-marker\s*{\s*font-family:\s*['"]dx\..*?\.)(.*)(['"])/g; + const rootSelectorRegex = new RegExp(`${escapedSelector}\\s+:root\\b`, 'g'); result = result + .replace(rootSelectorRegex, swatchClass) .replace(swatchOrderRegex, '$1$3$2$4') .replace(changeTypographyRulesOrderRegex, '$2 $1$3,$2$1$3$4') .replace(themeMarkerRegex, `$1${colorScheme}$3`); diff --git a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss index e15f6dbe91cd..7d115ebee381 100644 --- a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss +++ b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss @@ -18,7 +18,7 @@ $float-channel-color: null !default; } -$never-used: collector( +$_never-used: collector( ( "$base-font-family": $base-font-family, "$base-accent": $base-accent, diff --git a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss index d671f1015680..e08a47d95169 100644 --- a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss +++ b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss @@ -5,8 +5,6 @@ $accordion-item-title-opened-bg: getCustomVar(("$accordion-item-title-opened-bg" @use "../sizes" as *; @use "../colors" as *; -// adduse - $accordion-title-color: null !default; $accordion-item-title-opened-bg: null !default; @@ -15,7 +13,7 @@ $accordion-item-title-opened-bg: null !default; $accordion-item-title-opened-bg: transparent !default; } -$never-used: collector( +$_never-used: collector( ( "$accordion-title-color": $accordion-title-color, "$accordion-item-title-opened-bg": $accordion-item-title-opened-bg, diff --git a/packages/devextreme-themebuilder/tests/metadata/generator.test.ts b/packages/devextreme-themebuilder/tests/metadata/generator.test.ts index ee6b3aa83eb0..4b0cb63f446f 100644 --- a/packages/devextreme-themebuilder/tests/metadata/generator.test.ts +++ b/packages/devextreme-themebuilder/tests/metadata/generator.test.ts @@ -200,7 +200,7 @@ $slideout-background: #000 !default; * $type color */ $slideout-background: #000 !default; -$never-used: collector(( +$_never-used: collector(( "$slideout-background": $slideout-background, )); `; diff --git a/packages/devextreme-themebuilder/tests/modules/builder.test.ts b/packages/devextreme-themebuilder/tests/modules/builder.test.ts index b4f0f8ac7620..8adeb9deb6d3 100644 --- a/packages/devextreme-themebuilder/tests/modules/builder.test.ts +++ b/packages/devextreme-themebuilder/tests/modules/builder.test.ts @@ -38,6 +38,8 @@ describe('Builder integration tests', () => { return buildTheme(config).then((result) => { expect(result.css).not.toBe(''); expect(result.swatchSelector).toBe('.dx-swatch-custom-scheme'); + expect(result.css).not.toMatch(/\.dx-swatch-custom-scheme\s*:root/); + expect(result.css).toMatch(/\.dx-swatch-custom-scheme\s*\{[^}]*--dx-color-primary:/); }); }, buildTimeout); @@ -122,6 +124,22 @@ describe('Builder integration tests', () => { }); }, buildTimeout); + test('Theme built without parameters is the same that in distribution (fluent)', async () => { + const config: ConfigSettings = { + command: commands.BUILD_THEME, + outputColorScheme: 'custom-scheme', + baseTheme: 'fluent.blue.light', + items: [], + }; + + return buildTheme(config).then((result) => { + const themeBuilderCss = normalizeCss(result.css); + const cssPath = path.resolve(__dirname, '../../../devextreme/artifacts/css/dx.fluent.blue.light.css'); + const distributionCss = normalizeCss(readFileSync(cssPath, 'utf8')); + expect(themeBuilderCss).toBe(distributionCss); + }); + }, buildTimeout); + test('Theme built without parameters is the same that in distribution (material)', async () => { const config: ConfigSettings = { command: commands.BUILD_THEME, diff --git a/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts b/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts index 41717ff21294..de51a0368c62 100644 --- a/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts +++ b/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts @@ -163,6 +163,96 @@ describe('PostCompiler - swatch features (fixSwatchCss)', () => { expect(result).toBe(expectedCss); }); + test('fixSwatchCss - apply custom properties to the swatch class instead of :root (T1334644)', () => { + const compilerCss = ` +.dx-swatch-c :root { + --dx-color-primary: #fff; + --dx-color-text: #000; +}`; + + const expectedCss = ` +.dx-swatch-c { + --dx-color-primary: #fff; + --dx-color-text: #000; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - do not change the order of cascade\'s classes in rules next to :root', () => { + const compilerCss = ` +.dx-swatch-c .dx-widget { + color: #fff; +} +.dx-swatch-c :root { + --dx-color-primary: #fff; +} +.dx-swatch-c .dx-checkbox-checked.dx-checkbox-icon { + background-color: #fff; +}`; + + const expectedCss = ` +.dx-swatch-c .dx-widget { + color: #fff; +} +.dx-swatch-c { + --dx-color-primary: #fff; +} +.dx-swatch-c .dx-checkbox-checked.dx-checkbox-icon { + background-color: #fff; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - apply compound :root selectors to the swatch class itself', () => { + const compilerCss = ` +.dx-swatch-c :root.dx-foo { + --dx-color-primary: #fff; +} +.dx-swatch-c :root:not(.dx-bar) { + --dx-color-text: #000; +} +.dx-swatch-c :root .dx-widget { + color: #fff; +}`; + + const expectedCss = ` +.dx-swatch-c.dx-foo { + --dx-color-primary: #fff; +} +.dx-swatch-c:not(.dx-bar) { + --dx-color-text: #000; +} +.dx-swatch-c .dx-widget { + color: #fff; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - apply custom properties to the swatch class inside media queries', () => { + const compilerCss = ` +@media (max-width: 600px) { + .dx-swatch-c :root { + --dx-color-primary: #fff; + } +}`; + + const expectedCss = ` +@media (max-width: 600px) { + .dx-swatch-c { + --dx-color-primary: #fff; + } +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + test('fixSwatchCss - fix theme marker in swatch css', () => { const compilerCss = '.dx-swatch-c .dx-theme-marker { font-family: \'dx.generic.light\'; }'; From fde2943cb20dc9d92c3f0892fc3fa677b2cf6462 Mon Sep 17 00:00:00 2001 From: Alyar Date: Wed, 9 Sep 2026 06:48:23 +0000 Subject: [PATCH 27/45] Grids - FilterController: Move the filter build into FilterController (#35097) Co-authored-by: Alyar <> --- .../grids/data_grid/m_widget_base.ts | 2 + .../data_grid/module_not_extended/filter.ts | 5 ++ .../data_controller/data_controller.ts | 31 ++++------ .../grid_core/filter/filter_controller.ts | 28 +++++++++ .../grids/grid_core/filter/filter_module.ts | 9 +++ .../grid_core/filter_row/m_filter_row.ts | 19 +++--- .../extenders/filter_sync_data_controller.ts | 51 +--------------- .../filter_sync_filter_controller.ts | 59 +++++++++++++++++++ .../filter_sync/filter_sync_module.ts | 2 + .../grids/grid_core/filter_sync/utils.ts | 11 ++++ .../header_filter/m_header_filter.ts | 19 +++--- .../js/__internal/grids/grid_core/m_types.ts | 1 + .../grids/grid_core/search/m_search.ts | 26 ++++---- .../grids/tree_list/m_widget_base.ts | 2 + .../tree_list/module_not_extended/filter.ts | 5 ++ .../testing/helpers/gridBaseMocks.js | 8 +-- 16 files changed, 176 insertions(+), 102 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts index 546d0b54c089..fa9cba25a7fe 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts @@ -1,6 +1,7 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; import './data_source/data_source_module'; +import './module_not_extended/filter'; import './module_not_extended/data_controller'; import './module_not_extended/sorting'; import './module_not_extended/rows'; @@ -39,6 +40,7 @@ gridCore.registerModulesOrder([ 'masterDetail', 'validating', 'adaptivity', + 'filter', 'data', 'virtualScrolling', 'aiColumn', diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts new file mode 100644 index 000000000000..842edf76f69d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts @@ -0,0 +1,5 @@ +import { filterModule } from '@ts/grids/grid_core/filter/filter_module'; + +import gridCore from '../m_core'; + +gridCore.registerModule('filter', filterModule); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 9683aeda22f6..92fe5c4143a4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -125,7 +125,7 @@ export class DataController extends modules.Controller { // TODO public controller public _columnsController!: Controllers['columns']; - private _filterExcludedColumn: Column | null = null; + protected filterController!: Controllers['filter']; private loadErrorHandlerProxy!: (e: Error | string) => void; @@ -138,6 +138,7 @@ export class DataController extends modules.Controller { this._cachedProcessedItems = null; this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); + this.filterController = this.getController('filter'); this._isPaging = false; this._currentOperationTypes = null; @@ -328,23 +329,18 @@ export class DataController extends modules.Controller { return this.combinedFilter(undefined, returnDataField); } - public getFilterExcludedColumn(): Column | null { - return this._filterExcludedColumn; - } - public getCombinedFilterWithExcludedColumn( excludedColumn: Column | null, returnDataField?: boolean, ): DataFilter { - this._filterExcludedColumn = excludedColumn; - try { - return this.getCombinedFilter(returnDataField); - } finally { - this._filterExcludedColumn = null; - } + return this.combinedFilter(undefined, returnDataField, excludedColumn); } - private combinedFilter(filter: DataFilter, returnDataField?: boolean): DataFilter { + private combinedFilter( + filter: DataFilter, + returnDataField?: boolean, + excludedColumn: Column | null = null, + ): DataFilter { if (!this._dataSource) { return filter; } @@ -355,7 +351,7 @@ export class DataController extends modules.Controller { || this._columnsController.isAllDataTypesDefined(); if (isColumnsTypesDefined) { - const additionalFilter = this.calculateAdditionalFilter(); + const additionalFilter = this.filterController.getAdditionalFilter(excludedColumn); combined = additionalFilter ? gridCoreUtils.combineFilters([additionalFilter, combined]) @@ -569,7 +565,7 @@ export class DataController extends modules.Controller { this._isDataSourceApplying = false; const hasAdditionalFilter = (): boolean => { - const additionalFilter = this.calculateAdditionalFilter(); + const additionalFilter = this.filterController.getAdditionalFilter(); return Boolean(additionalFilter?.length); }; @@ -1213,13 +1209,6 @@ export class DataController extends modules.Controller { this.loadingChanged.fire(this.isLoading(), this._loadingText); } - /** - * @extended: filter_row, filter_sync, header_filter, search - */ - protected calculateAdditionalFilter(): DataFilter { - return null; - } - /** * @extended: filter_sync, virtual_scrolling */ diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts new file mode 100644 index 000000000000..450bdbc8688d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts @@ -0,0 +1,28 @@ +import type { LangParams } from '@js/common/data'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import modules from '@ts/grids/grid_core/m_modules'; +import type { Controllers } from '@ts/grids/grid_core/m_types'; + +export class FilterController extends modules.Controller { + protected columnsController!: Controllers['columns']; + + protected dataSourceController!: Controllers['dataSource']; + + public init(): void { + this.columnsController = this.getController('columns'); + this.dataSourceController = this.getController('dataSource'); + } + + protected getLangParams(): LangParams | undefined { + return this.dataSourceController.getDataSource()?.loadOptions?.()?.langParams; + } + + /** + * @extended: filter_row, filter_sync, header_filter, search + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + return null; + } +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts new file mode 100644 index 000000000000..942a85e95802 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts @@ -0,0 +1,9 @@ +import type { Module } from '@ts/grids/grid_core/m_types'; + +import { FilterController } from './filter_controller'; + +export const filterModule: Module = { + controllers: { + filter: FilterController, + }, +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts index 0c977a8c22ff..318f6a928d94 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts @@ -16,9 +16,10 @@ import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_col import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsResizerViewController } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; -import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import modules from '@ts/grids/grid_core/m_modules'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -815,20 +816,20 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH } }; -const data = (Base: ModuleType) => class DataControllerFilterRowExtender extends Base { +const filterController = ( + Base: ModuleType, +) => class FilterControllerFilterRowExtender extends Base { private skipCalculateColumnFilters() { return false; } - protected calculateAdditionalFilter() { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { if (this.skipCalculateColumnFilters()) { - return super.calculateAdditionalFilter(); + return super.getAdditionalFilter(excludedColumn); } - const filters = [super.calculateAdditionalFilter()]; - const columns = this._columnsController.getVisibleColumns(null, true); - - const excludedColumn = this.getFilterExcludedColumn(); + const filters = [super.getAdditionalFilter(excludedColumn)]; + const columns = this.columnsController.getVisibleColumns(null, true); each(columns, function () { const shouldSkip = excludedColumn?.index === this.index; @@ -1050,7 +1051,7 @@ export const filterRowModule = { }, extenders: { controllers: { - data, + filter: filterController, columnsResizer, editing, }, diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts index f55b1c37f880..6d8bbfdeab4e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts @@ -1,18 +1,11 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; -import { isDefined } from '@js/core/utils/type'; -import { - getFilterExpression, - removeFieldConditionsFromFilter, -} from '@ts/filter_builder/m_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import { FILTER_TYPES_EXCLUDE } from '@ts/grids/grid_core/filter_sync/const'; import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; -import { getColumnIdentifier } from '@ts/grids/grid_core/filter_sync/utils'; +import { isFilterSyncActive } from '@ts/grids/grid_core/filter_sync/utils'; import type { ModuleType, OptionChanged } from '@ts/grids/grid_core/m_types'; -import gridCoreUtils from '@ts/grids/grid_core/m_utils'; export interface FilterSyncDataControllerExtension { isFilterSyncActive: () => boolean | undefined; @@ -67,47 +60,7 @@ export const filterSyncDataControllerExtender = ( } public isFilterSyncActive(): boolean | undefined { - const filterSyncEnabledValue = this.option('filterSyncEnabled'); - return filterSyncEnabledValue === 'auto' ? this.option('filterPanel.visible') : filterSyncEnabledValue; - } - - private skipCalculateColumnFilters(): boolean | undefined { - const hasFilterValueOrSyncing = isDefined(this.option('filterValue')) - || this.filterSyncController.isSyncingColumnOptions(); - - return hasFilterValueOrSyncing && this.isFilterSyncActive(); - } - - protected calculateAdditionalFilter(): DataFilter { - const columns = this._columnsController.getFilteringColumns(); - const isFilterValueDisabled = this.option('filterPanel.filterEnabled') === false; - - if (!columns?.length || isFilterValueDisabled) { - return super.calculateAdditionalFilter(); - } - - const filters = [super.calculateAdditionalFilter()]; - let filterValue = this.option('filterValue'); - - if (this.isFilterSyncActive()) { - const excludedColumn = this.getFilterExcludedColumn(); - - if (isDefined(excludedColumn) && filterValue) { - filterValue = removeFieldConditionsFromFilter( - filterValue, - getColumnIdentifier(excludedColumn), - ); - } - } - - const customOperations = this.filterSyncController.getCustomFilterOperations(); - const calculatedFilterValue: DataFilter = getFilterExpression(filterValue, columns, customOperations, 'filterBuilder'); - - if (calculatedFilterValue) { - filters.push(calculatedFilterValue); - } - - return gridCoreUtils.combineFilters(filters); + return isFilterSyncActive(this); } private parseColumnPropertyName(fullName: string): string | null { diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts new file mode 100644 index 000000000000..e1c7e39c5e56 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts @@ -0,0 +1,59 @@ +import { isDefined } from '@js/core/utils/type'; +import { + getFilterExpression, + removeFieldConditionsFromFilter, +} from '@ts/filter_builder/m_utils'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; +import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; +import { getColumnIdentifier, isFilterSyncActive } from '@ts/grids/grid_core/filter_sync/utils'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import gridCoreUtils from '@ts/grids/grid_core/m_utils'; + +export const filterSyncFilterControllerExtender = ( + Base: ModuleType, +): ModuleType => class FilterControllerFilterSyncExtender extends Base { + private filterSyncController!: FilterSyncController; + + public init(): void { + this.filterSyncController = this.getController('filterSync'); + + super.init(); + } + + private skipCalculateColumnFilters(): boolean | undefined { + const hasFilterValueOrSyncing = isDefined(this.option('filterValue')) + || this.filterSyncController.isSyncingColumnOptions(); + + return hasFilterValueOrSyncing && isFilterSyncActive(this); + } + + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + const columns = this.columnsController.getFilteringColumns(); + const isFilterValueDisabled = this.option('filterPanel.filterEnabled') === false; + + if (!columns?.length || isFilterValueDisabled) { + return super.getAdditionalFilter(excludedColumn); + } + + const filters = [super.getAdditionalFilter(excludedColumn)]; + let filterValue = this.option('filterValue'); + + if (isFilterSyncActive(this) && isDefined(excludedColumn) && filterValue) { + filterValue = removeFieldConditionsFromFilter( + filterValue, + getColumnIdentifier(excludedColumn), + ); + } + + const customOperations = this.filterSyncController.getCustomFilterOperations(); + const calculatedFilterValue: DataFilter = getFilterExpression(filterValue, columns, customOperations, 'filterBuilder'); + + if (calculatedFilterValue) { + filters.push(calculatedFilterValue); + } + + return gridCoreUtils.combineFilters(filters); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts index dc3ee7dd096f..64b53f5f3a5d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts @@ -2,6 +2,7 @@ import type { InternalGridOptions } from '@ts/grids/grid_core/m_types'; import { filterSyncColumnHeadersViewExtender } from './extenders/filter_sync_column_headers_view'; import { filterSyncDataControllerExtender } from './extenders/filter_sync_data_controller'; +import { filterSyncFilterControllerExtender } from './extenders/filter_sync_filter_controller'; import { FilterSyncController } from './m_filter_sync'; export const filterSyncModule = { @@ -17,6 +18,7 @@ export const filterSyncModule = { extenders: { controllers: { data: filterSyncDataControllerExtender, + filter: filterSyncFilterControllerExtender, }, views: { columnHeadersView: filterSyncColumnHeadersViewExtender, diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts index 6a7e68056905..759ae671b25b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts @@ -8,6 +8,7 @@ import { } from '@ts/filter_builder/m_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { FilterValue, FilterValueCondition } from '@ts/grids/grid_core/data_controller/types'; +import type { ModuleItem } from '@ts/grids/grid_core/m_modules'; import { FILTER_ROW_OPERATIONS, FILTER_TYPES_EXCLUDE, FILTER_TYPES_INCLUDE } from './const'; import type { @@ -29,6 +30,16 @@ const getFilterTypeByOperation = (operation: unknown): Column['filterType'] | un } }; +// A free function so that both the `data` and the `filter` extender can read the flag +// without one of them reaching for the other controller. +export const isFilterSyncActive = ( + moduleItem: Pick, +): boolean | undefined => { + const filterSyncEnabled = moduleItem.option('filterSyncEnabled'); + + return filterSyncEnabled === 'auto' ? moduleItem.option('filterPanel.visible') : filterSyncEnabled; +}; + export const getColumnIdentifier = ( column: FilterSyncColumn, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 5ef75a4f093f..b0d69199395c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -15,8 +15,11 @@ import { isDefined, isFunction, isObject } from '@js/core/utils/type'; import { restoreFocus, saveFocusedElementInfo } from '@js/ui/shared/accessibility'; import filterUtils from '@js/ui/shared/filtering'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import Modules from '@ts/grids/grid_core/m_modules'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -494,20 +497,20 @@ export function invertFilterExpression(filter) { return ['!', filter]; } -const data = (Base: ModuleType) => class DataControllerFilterRowExtender extends Base { +const filterController = ( + Base: ModuleType, +) => class FilterControllerHeaderFilterExtender extends Base { private skipCalculateColumnFilters() { return false; } - protected calculateAdditionalFilter() { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { if (this.skipCalculateColumnFilters()) { - return super.calculateAdditionalFilter(); + return super.getAdditionalFilter(excludedColumn); } - const that = this; - const filters = [super.calculateAdditionalFilter()]; - const columns = that._columnsController.getVisibleColumns(null, true); - const excludedColumn = this.getFilterExcludedColumn(); + const filters = [super.getAdditionalFilter(excludedColumn)]; + const columns = this.columnsController.getVisibleColumns(null, true); each(columns, (_, column) => { let filter; @@ -577,7 +580,7 @@ export const headerFilterModule = { }, extenders: { controllers: { - data, + filter: filterController, }, views: { columnHeadersView, diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts index d056690762f3..9204705e55f0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts @@ -210,6 +210,7 @@ export interface Controllers { editorFactory: import('./editor_factory/m_editor_factory').EditorFactory; errorHandling: import('./error_handling/error_handling_view_controller').ErrorHandlingViewController; export: import('../data_grid/export/m_export').ExportController; + filter: import('./filter/filter_controller').FilterController; filterSync: import('./filter_sync/m_filter_sync').FilterSyncController; focus: import('./focus/m_focus').FocusController; headerFilter: import('./header_filter/m_header_filter').HeaderFilterController; diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index 30ae6e25a9b7..08bad15c6edd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -13,6 +13,7 @@ import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { DataController } from '../data_controller/data_controller'; import type { DataFilter, UserState } from '../data_controller/types'; +import type { FilterController } from '../filter/filter_controller'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import modules from '../m_modules'; import type { ModuleType, OptionChanged } from '../m_types'; @@ -69,23 +70,27 @@ const dataController = ( }; } - protected calculateAdditionalFilter(): DataFilter { - const dataSource = this.getDataSource(); - const langParams = dataSource?.loadOptions?.()?.langParams; + public searchByText(text: string | undefined): void { + this.option('searchPanel.text', text); + } +}; - const filter = super.calculateAdditionalFilter(); - const searchFilter = this.calculateSearchFilter(this.option('searchPanel.text'), langParams); +const filterController = ( + base: ModuleType, +) => class FilterControllerSearchExtender extends base { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + const filter = super.getAdditionalFilter(excludedColumn); + const searchFilter = this.calculateSearchFilter( + this.option('searchPanel.text'), + this.getLangParams(), + ); return gridCoreUtils.combineFilters([filter, searchFilter]); } - public searchByText(text: string | undefined): void { - this.option('searchPanel.text', text); - } - private calculateSearchFilter(text: string | undefined, langParams?: LangParams): DataFilter { let column; - const columns = this._columnsController.getColumns(); + const columns = this.columnsController.getColumns(); const searchVisibleColumnsOnly = this.option('searchPanel.searchVisibleColumnsOnly'); let lookup; const filters: any[] = []; @@ -426,6 +431,7 @@ export const searchModule = { extenders: { controllers: { data: dataController, + filter: filterController, }, views: { rowsView, diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts index 41ccc3606941..e226743ad372 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts @@ -1,6 +1,7 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; import './data_source/data_source_module'; +import './module_not_extended/filter'; import './data_controller/m_data_controller'; import './module_not_extended/sorting'; import './rows/m_rows'; @@ -34,6 +35,7 @@ treeListCore.registerModulesOrder([ 'masterDetail', 'validating', 'adaptivity', + 'filter', 'data', 'virtualScrolling', 'aiColumn', diff --git a/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts new file mode 100644 index 000000000000..dc4b83b8545c --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts @@ -0,0 +1,5 @@ +import { filterModule } from '@ts/grids/grid_core/filter/filter_module'; + +import treeListCore from '../m_core'; + +treeListCore.registerModule('filter', filterModule); diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 41f2f2da46dc..baa77c72496e 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -282,8 +282,6 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo getCombinedFilter: commonUtils.noop, - getFilterExcludedColumn: commonUtils.noop, - getCombinedFilterWithExcludedColumn: commonUtils.noop, getRowIndexByKey: function(key) { @@ -1016,9 +1014,9 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo _subscribeToEvents(rootElement) { } }; - // The dataSource controller is a leaf that the data controller resolves in init(), - // so it is always included rather than listed by every caller. - const ALWAYS_INCLUDED_MODULES = ['dataSource']; + // The dataSource and filter controllers are leaves that the data controller resolves + // in init(), so they are always included rather than listed by every caller. + const ALWAYS_INCLUDED_MODULES = ['dataSource', 'filter']; exports['setup' + nameWidget + 'Modules'] = function(that, moduleNames, options) { const modules = []; From 5b08774cf22a4082d5dfd534260d1d1f207ffbc0 Mon Sep 17 00:00:00 2001 From: "ilya.kharchenko" <14272298+IlyaKhD@users.noreply.github.com> Date: Wed, 9 Sep 2026 06:48:31 +0000 Subject: [PATCH 28/45] feat(ci): build flaky test report for the repository monitor (#35100) --- .github/workflows/repository-check.yml | 89 ++++++ .github/workflows/testcafe_tests.yml | 7 +- pnpm-lock.yaml | 28 ++ pnpm-workspace.yaml | 1 + tools/.prettierignore | 3 + tools/.prettierrc.yaml | 12 + tools/flaky-test-reporter/jest.config.js | 4 + tools/flaky-test-reporter/package.json | 27 ++ tools/flaky-test-reporter/src/main.test.ts | 44 +++ tools/flaky-test-reporter/src/main.ts | 102 +++++++ .../flaky-test-reporter/src/markdown.test.ts | 237 +++++++++++++++ tools/flaky-test-reporter/src/markdown.ts | 114 ++++++++ .../src/primitives/github.test.ts | 181 ++++++++++++ .../src/primitives/github.ts | 134 +++++++++ .../src/primitives/zip.test.ts | 50 ++++ .../src/primitives/zip.test.utils.ts | 52 ++++ .../flaky-test-reporter/src/primitives/zip.ts | 74 +++++ tools/flaky-test-reporter/src/report.test.ts | 275 ++++++++++++++++++ tools/flaky-test-reporter/src/report.ts | 274 +++++++++++++++++ tools/flaky-test-reporter/src/types.ts | 72 +++++ tools/flaky-test-reporter/src/utils.test.ts | 44 +++ tools/flaky-test-reporter/src/utils.ts | 30 ++ tools/flaky-test-reporter/tsconfig.json | 9 + tools/flaky-test-reporter/tsconfig.test.json | 8 + tools/jest.config.base.js | 10 + tools/scripts/tsconfig.json | 18 +- tools/tsconfig.base.json | 17 ++ 27 files changed, 1898 insertions(+), 18 deletions(-) create mode 100644 tools/.prettierignore create mode 100644 tools/.prettierrc.yaml create mode 100644 tools/flaky-test-reporter/jest.config.js create mode 100644 tools/flaky-test-reporter/package.json create mode 100644 tools/flaky-test-reporter/src/main.test.ts create mode 100644 tools/flaky-test-reporter/src/main.ts create mode 100644 tools/flaky-test-reporter/src/markdown.test.ts create mode 100644 tools/flaky-test-reporter/src/markdown.ts create mode 100644 tools/flaky-test-reporter/src/primitives/github.test.ts create mode 100644 tools/flaky-test-reporter/src/primitives/github.ts create mode 100644 tools/flaky-test-reporter/src/primitives/zip.test.ts create mode 100644 tools/flaky-test-reporter/src/primitives/zip.test.utils.ts create mode 100644 tools/flaky-test-reporter/src/primitives/zip.ts create mode 100644 tools/flaky-test-reporter/src/report.test.ts create mode 100644 tools/flaky-test-reporter/src/report.ts create mode 100644 tools/flaky-test-reporter/src/types.ts create mode 100644 tools/flaky-test-reporter/src/utils.test.ts create mode 100644 tools/flaky-test-reporter/src/utils.ts create mode 100644 tools/flaky-test-reporter/tsconfig.json create mode 100644 tools/flaky-test-reporter/tsconfig.test.json create mode 100644 tools/jest.config.base.js create mode 100644 tools/tsconfig.base.json diff --git a/.github/workflows/repository-check.yml b/.github/workflows/repository-check.yml index 095674fa2d4e..2eae3e61ae00 100644 --- a/.github/workflows/repository-check.yml +++ b/.github/workflows/repository-check.yml @@ -8,9 +8,95 @@ on: branches: - main +permissions: {} + jobs: + collect-flaky-reports: + name: Collect flaky reports + runs-on: ubuntu-slim + permissions: + contents: read # checkout + actions: read # list workflow runs and download their artifacts + # Flaky collection must never break the repository check it feeds. + continue-on-error: true + outputs: + details: ${{ steps.collect.outputs.details }} + + steps: + - name: Get sources + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Use Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + with: + run_install: false + + - name: Install dependencies + run: pnpm install --frozen-lockfile --filter flaky-test-reporter... + + - name: Collect flaky test candidates + id: collect + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WINDOW_HOURS: 72 + run: | + # tee opens the log before the tool runs, so the directory must already exist. + mkdir -p "$GITHUB_WORKSPACE/artifacts" + report="$GITHUB_WORKSPACE/artifacts/flaky-tests-report.json" + markdown="$GITHUB_WORKSPACE/artifacts/flaky-tests-report.md" + log="$GITHUB_WORKSPACE/artifacts/collect.log" + + # --silent keeps pnpm's own banner out of the summary; 2>&1 keeps the tool's warnings in. + # tee stores the plain text for the summary, then sed turns the tool's warnings into + # GitHub annotations so they surface on the run page. + pnpm --silent --filter flaky-test-reporter run collect -- \ + --repo "${{ github.repository }}" \ + --workflow testcafe_tests.yml \ + --artifact flaky-candidates \ + --branch "${{ github.ref_name }}" \ + --window-hours "$WINDOW_HOURS" \ + --out-json "$report" \ + --out-markdown "$markdown" 2>&1 \ + | tee "$log" \ + | sed -e 's/^warning: /::warning::/' -e 's/^Collection failed: /::error::/' + + { + echo "## Flaky test candidates" + echo "" + echo '```' + cat "$log" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + # Multiline output form, so the markdown crosses the job boundary as-is. The delimiter + # must not occur in the content, which markdown headings and tables never produce. + { + echo 'details<> "$GITHUB_OUTPUT" + + - name: Upload flaky test report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flaky-tests-report + path: | + ${{ github.workspace }}/artifacts/flaky-tests-report.json + ${{ github.workspace }}/artifacts/collect.log + retention-days: 7 + if-no-files-found: ignore + invoke: name: Invoke + needs: [collect-flaky-reports] + permissions: + contents: read # the reusable workflow checks this repository out + if: ${{ !cancelled() }} uses: DevExpress/github-actions/.github/workflows/repository-check.yml@repository-check with: config: | @@ -20,3 +106,6 @@ jobs: 'GHSA-848j-6mx2-7j84', // elliptic ] } + # Markdown appended to this repo's monitor issue body. Empty when the collect job + # produced nothing, which makes the shared workflow skip publishing it. + extraDetails: ${{ needs.collect-flaky-reports.outputs.details }} diff --git a/.github/workflows/testcafe_tests.yml b/.github/workflows/testcafe_tests.yml index 45f067d17332..432134804233 100644 --- a/.github/workflows/testcafe_tests.yml +++ b/.github/workflows/testcafe_tests.yml @@ -472,6 +472,9 @@ jobs: new_flakes_summary=./artifacts/new-flakes-summary.txt new_flakes=./artifacts/flaky-candidates.jsonl report_json=./artifacts/flaky-candidates.json + # base_ref is empty for push and merge_group, which carry the target elsewhere. + base_branch="${{ github.base_ref || github.event.merge_group.base_ref || github.ref_name }}" + base_branch="${base_branch#refs/heads/}" mkdir -p ./artifacts : > "$new_flakes_summary" : > "$new_flakes" @@ -560,8 +563,8 @@ jobs: fi jq -cn --arg test "$name" --arg sourceJob "$job_name" --arg theme "$theme" \ --arg componentFolder "$componentFolder" --arg timezone "$timezone" \ - --arg platform "$platform" --arg verdict "$verdict" \ - '{ test: $test, sourceJob: $sourceJob, theme: $theme, componentFolder: $componentFolder, timezone: $timezone, platform: $platform, verdict: $verdict }' \ + --arg platform "$platform" --arg verdict "$verdict" --arg baseBranch "$base_branch" \ + '{ test: $test, sourceJob: $sourceJob, theme: $theme, componentFolder: $componentFolder, timezone: $timezone, platform: $platform, verdict: $verdict, baseBranch: $baseBranch }' \ >> "$new_flakes" done < <(jq -r '.tests[]' "$report") done diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38ad9bbed3fe..9853abf7f4fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2192,6 +2192,34 @@ importers: specifier: 'catalog:' version: 3.7.5(supports-color@7.2.0) + tools/flaky-test-reporter: + dependencies: + yargs: + specifier: 17.7.2 + version: 17.7.2 + devDependencies: + '@types/jest': + specifier: 30.0.0 + version: 30.0.0 + '@types/node': + specifier: catalog:tools + version: 20.19.37 + '@types/yargs': + specifier: 17.0.35 + version: 17.0.35 + prettier: + specifier: catalog:tools + version: 3.5.3 + ts-jest: + specifier: 29.4.12 + version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) + ts-node: + specifier: catalog:tools + version: 10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3) + typescript: + specifier: catalog:tools + version: 5.9.3 + packages: '@adobe/css-tools@4.4.4': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 005b76c8b0a8..32f077326d00 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,7 @@ packages: - 'e2e/*' - 'packages/*' - '!packages/sbom' + - 'tools/*' linkWorkspacePackages: true preferWorkspacePackages: true diff --git a/tools/.prettierignore b/tools/.prettierignore new file mode 100644 index 000000000000..090fc6536af2 --- /dev/null +++ b/tools/.prettierignore @@ -0,0 +1,3 @@ +# Shared by every tools/* package. Prettier does not search parent directories for this file, +# so each package passes it explicitly via --ignore-path. +*.json diff --git a/tools/.prettierrc.yaml b/tools/.prettierrc.yaml new file mode 100644 index 000000000000..08e20ffc0b60 --- /dev/null +++ b/tools/.prettierrc.yaml @@ -0,0 +1,12 @@ +arrowParens: always +bracketSameLine: true +bracketSpacing: true +endOfLine: lf +experimentalOperatorPosition: start +objectWrap: preserve +printWidth: 100 +quoteProps: preserve +semi: true +singleQuote: true +tabWidth: 2 +trailingComma: all diff --git a/tools/flaky-test-reporter/jest.config.js b/tools/flaky-test-reporter/jest.config.js new file mode 100644 index 000000000000..ae286b8ce8ad --- /dev/null +++ b/tools/flaky-test-reporter/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + displayName: 'flaky-test-reporter', + preset: '../jest.config.base.js', +}; diff --git a/tools/flaky-test-reporter/package.json b/tools/flaky-test-reporter/package.json new file mode 100644 index 000000000000..6a5ef539078c --- /dev/null +++ b/tools/flaky-test-reporter/package.json @@ -0,0 +1,27 @@ +{ + "name": "flaky-test-reporter", + "version": "26.2.0", + "type": "commonjs", + "private": true, + "author": "Developer Express Inc.", + "license": "MIT", + "scripts": { + "collect": "ts-node src/main.ts", + "format": "prettier --write . --ignore-path ../.prettierignore", + "format:check": "prettier --check . --ignore-path ../.prettierignore", + "lint": "pnpm run format:check", + "test": "jest" + }, + "dependencies": { + "yargs": "17.7.2" + }, + "devDependencies": { + "@types/jest": "30.0.0", + "@types/node": "catalog:tools", + "@types/yargs": "17.0.35", + "prettier": "catalog:tools", + "ts-jest": "29.4.12", + "ts-node": "catalog:tools", + "typescript": "catalog:tools" + } +} diff --git a/tools/flaky-test-reporter/src/main.test.ts b/tools/flaky-test-reporter/src/main.test.ts new file mode 100644 index 000000000000..7d09cbcaffeb --- /dev/null +++ b/tools/flaky-test-reporter/src/main.test.ts @@ -0,0 +1,44 @@ +import { parseArgs } from './main'; + +const flags = [ + '--repo', + 'o/r', + '--workflow', + 'testcafe_tests.yml', + '--artifact', + 'flaky-candidates', + '--branch', + 'main', + '--window-hours', + '48', + '--out-json', + 'report.json', + '--out-markdown', + 'report.md', +]; + +describe('parseArgs', () => { + it('parses the flags', () => { + expect(parseArgs(flags)).toMatchObject({ + repo: 'o/r', + workflow: 'testcafe_tests.yml', + artifact: 'flaky-candidates', + branch: 'main', + windowHours: 48, + outJson: 'report.json', + outMarkdown: 'report.md', + }); + }); + + it('parses identically when invoked through `pnpm run