diff --git a/examples/vite-demo-vanilla-bundle/public/i18n/en.json b/examples/vite-demo-vanilla-bundle/public/i18n/en.json index 12757b0ad..3d50e5b88 100644 --- a/examples/vite-demo-vanilla-bundle/public/i18n/en.json +++ b/examples/vite-demo-vanilla-bundle/public/i18n/en.json @@ -61,6 +61,7 @@ "STARTS_WITH": "Starts With", "SYNCHRONOUS_RESIZE": "Synchronous resize", "TOGGLE_ALL_GROUPS": "Toggle all Groups", + "TOGGLE_DARK_MODE": "Toggle Dark Mode", "TOGGLE_FILTER_ROW": "Toggle Filter Row", "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", "UNFREEZE_COLUMNS": "Unfreeze Columns", diff --git a/examples/vite-demo-vanilla-bundle/public/i18n/fr.json b/examples/vite-demo-vanilla-bundle/public/i18n/fr.json index dd81ad3c9..800ddf788 100644 --- a/examples/vite-demo-vanilla-bundle/public/i18n/fr.json +++ b/examples/vite-demo-vanilla-bundle/public/i18n/fr.json @@ -61,6 +61,7 @@ "STARTS_WITH": "Commence par", "SYNCHRONOUS_RESIZE": "Redimension synchrone", "TOGGLE_ALL_GROUPS": "Basculer tous les groupes", + "TOGGLE_DARK_MODE": "Basculer le mode clair/sombre", "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", "UNFREEZE_COLUMNS": "Dégeler les colonnes", diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example01.ts b/examples/vite-demo-vanilla-bundle/src/examples/example01.ts index fdf8eb4b0..93b13154d 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example01.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example01.ts @@ -66,6 +66,14 @@ export default class Example01 { gridHeight: 225, gridWidth: 800, rowHeight: 33, + gridMenu: { + hideToggleDarkModeCommand: false, // disabled command by default + onCommand: (_, args) => { + if (args.command === 'toggle-dark-mode') { + this._darkModeGrid1 = !this._darkModeGrid1; // keep local toggle var in sync + } + } + } }; // copy the same Grid Options and Column Definitions to 2nd grid diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example12.scss b/examples/vite-demo-vanilla-bundle/src/examples/example12.scss index f969f27b6..ad1053419 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example12.scss +++ b/examples/vite-demo-vanilla-bundle/src/examples/example12.scss @@ -3,3 +3,7 @@ $control-height: 2.4em; // @import '@slickgrid-universal/common/dist/styles/sass/slickgrid-theme-salesforce.scss'; @import 'bulma/bulma'; + +.slick-dark-mode .slick-pagination { + background-color: #2d2d2d; +} \ No newline at end of file diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example12.ts b/examples/vite-demo-vanilla-bundle/src/examples/example12.ts index 8acdb47c0..2a3378769 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example12.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example12.ts @@ -475,6 +475,15 @@ export default class Example12 { }, // when using the cellMenu, you can change some of the default options and all use some of the callback methods enableCellMenu: true, + gridMenu: { + hideToggleDarkModeCommand: false, // disabled command by default + onCommand: (_, args) => { + if (args.command === 'toggle-dark-mode') { + this._darkMode = !this._darkMode; // keep local toggle var in sync + this.toggleBodyBackground(); + } + } + } }; } @@ -749,13 +758,17 @@ export default class Example12 { toggleDarkMode() { this._darkMode = !this._darkMode; + this.toggleBodyBackground(); + this.sgb.gridOptions = { ...this.sgb.gridOptions, darkMode: this._darkMode }; + this.sgb.slickGrid?.setOptions({ darkMode: this._darkMode }); + } + + toggleBodyBackground() { if (this._darkMode) { document.querySelector('.demo-container')?.classList.add('dark-mode'); } else { document.querySelector('.demo-container')?.classList.remove('dark-mode'); } - this.sgb.gridOptions = { ...this.sgb.gridOptions, darkMode: this._darkMode }; - this.sgb.slickGrid?.setOptions({ darkMode: this._darkMode }); } mockProducts() { diff --git a/examples/vite-demo-vanilla-bundle/src/material-styles.scss b/examples/vite-demo-vanilla-bundle/src/material-styles.scss index 38a824419..abd8889eb 100644 --- a/examples/vite-demo-vanilla-bundle/src/material-styles.scss +++ b/examples/vite-demo-vanilla-bundle/src/material-styles.scss @@ -118,7 +118,7 @@ .slick-dark-mode .ms-dark-mode, .slick-dark-mode { --slick-base-dark-menu-bg-color: #212121; - --slick-primary-color: #4caf50; + --slick-primary-color: #66bb6a; --slick-button-primary-bg-color: var(--slick-primary-color); --slick-cell-box-shadow: none; --slick-column-picker-checkbox-color: #49a54e; @@ -130,10 +130,10 @@ --slick-container-border-left: 1px solid #505050; --slick-container-border-bottom: 1px solid #505050; --slick-pane-top-border-top: 1px solid #505050; - --slick-filled-filter-color: #00ab3b; + --slick-filled-filter-color: #66bb6a; --slick-highlight-color: #49a54e; --slick-pagination-icon-color: #49a54e; - --slick-checkbox-selector-checked-color: #4caf50; + --slick-checkbox-selector-checked-color: #66bb6a; --slick-row-mouse-hover-box-shadow: none; --slick-row-mouse-hover-color: #505050; --slick-row-selected-color: #474747; @@ -141,7 +141,7 @@ --slick-checkbox-selector-icon-height: 22px; --slick-checkbox-selector-icon-bg-color: transparent; --slick-checkbox-selector-icon-border: none; - --ms-ok-button-text-color: #4caf50; + --ms-ok-button-text-color: #66bb6a; } } } diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index b444137d7..12247e399 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -64,6 +64,7 @@ export class Constants { TEXT_SORT_DESCENDING: 'Sort Descending', TEXT_STARTS_WITH: 'Starts With', TEXT_TOGGLE_ALL_GROUPS: 'Toggle all Groups', + TEXT_TOGGLE_DARK_MODE: 'Toggle Dark Mode', TEXT_TOGGLE_FILTER_ROW: 'Toggle Filter Row', TEXT_TOGGLE_PRE_HEADER_ROW: 'Toggle Pre-Header Row', TEXT_UNFREEZE_COLUMNS: 'Unfreeze Columns', diff --git a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts index 6be9a0209..e7e41d699 100644 --- a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts @@ -116,6 +116,7 @@ describe('GridMenuControl', () => { hideClearFrozenColumnsCommand: true, hideForceFitButton: false, hideSyncResizeButton: true, + hideToggleDarkModeCommand: true, onExtensionRegistered: jest.fn(), onCommand: () => { }, onColumnsChanged: () => { }, @@ -1088,7 +1089,7 @@ describe('GridMenuControl', () => { }); it('should expect menu related to "Unfreeze Columns/Rows"', () => { - const copyGridOptionsMock = { ...gridOptionsMock, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: false, } } as unknown as GridOption; + const copyGridOptionsMock = { ...gridOptionsMock, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: false, hideToggleDarkModeCommand: true, } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); @@ -1107,12 +1108,16 @@ describe('GridMenuControl', () => { expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ { iconCssClass: 'fa fa-filter text-danger', titleKey: 'CLEAR_ALL_FILTERS', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 }, { iconCssClass: 'fa fa-random', titleKey: 'TOGGLE_FILTER_ROW', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, - { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } + { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 58 } ]); }); it('should have only 1 menu "clear-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideToggleFilterCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideToggleFilterCommand: true, hideRefreshDatasetCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1124,7 +1129,11 @@ describe('GridMenuControl', () => { }); it('should have only 1 menu "toggle-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideToggleDarkModeCommand: true, hideRefreshDatasetCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1135,15 +1144,36 @@ describe('GridMenuControl', () => { ]); }); + it('should have only 1 menu "toggle-dark-mode" when all other menus are defined as hidden', () => { + const copyGridOptionsMock = { + ...gridOptionsMock, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, + hideClearAllFiltersCommand: true, hideToggleFilterCommand: true, hideToggleDarkModeCommand: false, hideRefreshDatasetCommand: true + } + } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); + control.columns = columnsMock; + control.init(); + control.init(); // calling 2x register to make sure it doesn't duplicate commands + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-random', titleKey: 'TOGGLE_DARK_MODE', title: 'Basculer le mode clair/sombre', disabled: false, command: 'toggle-dark-mode', positionOrder: 54 }, + ]); + }); + it('should have only 1 menu "refresh-dataset" when all other menus are defined as hidden & when "enableFilering" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideToggleFilterCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideToggleDarkModeCommand: true, hideToggleFilterCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ - { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } + { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 58 } ]); }); @@ -1160,7 +1190,11 @@ describe('GridMenuControl', () => { }); it('should not have the "toggle-preheader" menu command when "showPreHeaderPanel" and "hideTogglePreHeaderCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideTogglePreHeaderCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, showPreHeaderPanel: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideTogglePreHeaderCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1182,7 +1216,11 @@ describe('GridMenuControl', () => { }); it('should not have the "clear-sorting" menu command when "enableSorting" and "hideClearAllSortingCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllSortingCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableSorting: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllSortingCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1192,19 +1230,27 @@ describe('GridMenuControl', () => { }); it('should have the "export-csv" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableTextExport: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportTextDelimitedCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ - { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_CSV', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 54 } + { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_CSV', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 55 } ]); }); it('should not have the "export-csv" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableTextExport: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1214,31 +1260,43 @@ describe('GridMenuControl', () => { }); it('should have the "export-excel" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ - { iconCssClass: 'fa fa-file-excel-o text-success', titleKey: 'EXPORT_TO_EXCEL', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 55 } + { iconCssClass: 'fa fa-file-excel-o text-success', titleKey: 'EXPORT_TO_EXCEL', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 56 } ]); }); it('should have the "export-text-delimited" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableTextExport: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ - { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_TAB_DELIMITED', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 56 } + { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_TAB_DELIMITED', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 57 } ]); }); it('should not have the "export-text-delimited" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; + const copyGridOptionsMock = { + ...gridOptionsMock, enableTextExport: true, gridMenu: { + commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true, hideToggleDarkModeCommand: true + } + } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; @@ -1411,6 +1469,20 @@ describe('GridMenuControl', () => { expect(exportSpy).toHaveBeenCalledWith({ delimiter: DelimiterType.tab, format: FileType.txt }); }); + it('should toggle the darkMode grid option when the command triggered is "toggle-dark-mode"', () => { + let copyGridOptionsMock = { ...gridOptionsMock, darkMode: false, gridMenu: { hideToggleDarkModeCommand: false } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); + + control.init(); + control.columns = columnsMock; + const clickEvent = new Event('click', { bubbles: true, cancelable: true, composed: false }); + document.querySelector('.slick-grid-menu-button')!.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); + control.menuElement!.querySelector('.slick-menu-item[data-command=toggle-dark-mode]')!.dispatchEvent(clickEvent); + + expect(copyGridOptionsMock.darkMode).toBeTruthy(); + }); + it('should call the grid "setHeaderRowVisibility" method when the command triggered is "toggle-filter"', () => { let copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: false, hideToggleFilterCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); diff --git a/packages/common/src/extensions/slickGridMenu.ts b/packages/common/src/extensions/slickGridMenu.ts index e4bb28f33..89e67c883 100644 --- a/packages/common/src/extensions/slickGridMenu.ts +++ b/packages/common/src/extensions/slickGridMenu.ts @@ -565,160 +565,158 @@ export class SlickGridMenu extends MenuBaseClass { const translationPrefix = getTranslationPrefix(gridOptions); const commandLabels = this._addonOptions?.commandLabels; - // show grid menu: Unfreeze Columns/Rows - if (this.gridOptions && this._addonOptions && !this._addonOptions.hideClearFrozenColumnsCommand) { - const commandName = 'clear-pinning'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + if (this._addonOptions && this.gridOptions) { + // show grid menu: Unfreeze Columns/Rows + if (!this._addonOptions.hideClearFrozenColumnsCommand) { + const commandName = 'clear-pinning'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconClearFrozenColumnsCommand || 'fa fa-times', titleKey: `${translationPrefix}${commandLabels?.clearFrozenColumnsCommandKey ?? 'CLEAR_PINNING'}`, disabled: false, command: commandName, positionOrder: 52 - } - ); + }); + } } - } - if (this.gridOptions && (this.gridOptions.enableFiltering && !this.sharedService.hideHeaderRowAfterPageLoad)) { - // show grid menu: Clear all Filters - if (this.gridOptions && this._addonOptions && !this._addonOptions.hideClearAllFiltersCommand) { - const commandName = 'clear-filter'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + if (this.gridOptions.enableFiltering && !this.sharedService.hideHeaderRowAfterPageLoad) { + // show grid menu: Clear all Filters + if (!this._addonOptions.hideClearAllFiltersCommand) { + const commandName = 'clear-filter'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconClearAllFiltersCommand || 'fa fa-filter text-danger', titleKey: `${translationPrefix}${commandLabels?.clearAllFiltersCommandKey ?? 'CLEAR_ALL_FILTERS'}`, disabled: false, command: commandName, positionOrder: 50 - } - ); + }); + } } - } - // show grid menu: toggle filter row - if (this.gridOptions && this._addonOptions && !this._addonOptions.hideToggleFilterCommand) { - const commandName = 'toggle-filter'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + // show grid menu: toggle filter row + if (!this._addonOptions.hideToggleFilterCommand) { + const commandName = 'toggle-filter'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconToggleFilterCommand || 'fa fa-random', titleKey: `${translationPrefix}${commandLabels?.toggleFilterCommandKey ?? 'TOGGLE_FILTER_ROW'}`, disabled: false, command: commandName, positionOrder: 53 - } - ); + }); + } } - } - // show grid menu: refresh dataset - if (backendApi && this.gridOptions && this._addonOptions && !this._addonOptions.hideRefreshDatasetCommand) { - const commandName = 'refresh-dataset'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + // show grid menu: refresh dataset + if (backendApi && !this._addonOptions.hideRefreshDatasetCommand) { + const commandName = 'refresh-dataset'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconRefreshDatasetCommand || 'fa fa-refresh', titleKey: `${translationPrefix}${commandLabels?.refreshDatasetCommandKey ?? 'REFRESH_DATASET'}`, disabled: false, command: commandName, - positionOrder: 57 - } - ); + positionOrder: 58 + }); + } } } - } - if (this.gridOptions.showPreHeaderPanel) { - // show grid menu: toggle pre-header row - if (this.gridOptions && this._addonOptions && !this._addonOptions.hideTogglePreHeaderCommand) { - const commandName = 'toggle-preheader'; + // show grid menu: toggle dark mode + if (!this._addonOptions.hideToggleDarkModeCommand) { + const commandName = 'toggle-dark-mode'; if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + gridMenuCommandItems.push({ + iconCssClass: this._addonOptions.iconToggleDarkModeCommand || 'fa fa-random', + titleKey: `${translationPrefix}${commandLabels?.toggleDarkModeCommandKey ?? 'TOGGLE_DARK_MODE'}`, + disabled: false, + command: commandName, + positionOrder: 54 + }); + } + } + + if (this.gridOptions.showPreHeaderPanel) { + // show grid menu: toggle pre-header row + if (!this._addonOptions.hideTogglePreHeaderCommand) { + const commandName = 'toggle-preheader'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconTogglePreHeaderCommand || 'fa fa-random', titleKey: `${translationPrefix}${commandLabels?.togglePreHeaderCommandKey ?? 'TOGGLE_PRE_HEADER_ROW'}`, disabled: false, command: commandName, positionOrder: 53 - } - ); + }); + } } } - } - if (this.gridOptions.enableSorting) { - // show grid menu: Clear all Sorting - if (this.gridOptions && this._addonOptions && !this._addonOptions.hideClearAllSortingCommand) { - const commandName = 'clear-sorting'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + if (this.gridOptions.enableSorting) { + // show grid menu: Clear all Sorting + if (!this._addonOptions.hideClearAllSortingCommand) { + const commandName = 'clear-sorting'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconClearAllSortingCommand || 'fa fa-unsorted text-danger', titleKey: `${translationPrefix}${commandLabels?.clearAllSortingCommandKey ?? 'CLEAR_ALL_SORTING'}`, disabled: false, command: commandName, positionOrder: 51 - } - ); + }); + } } } - } - // show grid menu: Export to file - if (this.gridOptions?.enableTextExport && this._addonOptions && !this._addonOptions.hideExportCsvCommand) { - const commandName = 'export-csv'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + // show grid menu: Export to file + if (this.gridOptions.enableTextExport && !this._addonOptions.hideExportCsvCommand) { + const commandName = 'export-csv'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconExportCsvCommand || 'fa fa-download', titleKey: `${translationPrefix}${commandLabels?.exportCsvCommandKey ?? 'EXPORT_TO_CSV'}`, disabled: false, command: commandName, - positionOrder: 54 - } - ); + positionOrder: 55 + }); + } } - } - // show grid menu: Export to Excel - if (this.gridOptions && this.gridOptions.enableExcelExport && this._addonOptions && !this._addonOptions.hideExportExcelCommand) { - const commandName = 'export-excel'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + // show grid menu: Export to Excel + if (this.gridOptions.enableExcelExport && !this._addonOptions.hideExportExcelCommand) { + const commandName = 'export-excel'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconExportExcelCommand || 'fa fa-file-excel-o text-success', titleKey: `${translationPrefix}${commandLabels?.exportExcelCommandKey ?? 'EXPORT_TO_EXCEL'}`, disabled: false, command: commandName, - positionOrder: 55 - } - ); + positionOrder: 56 + }); + } } - } - // show grid menu: export to text file as tab delimited - if (this.gridOptions?.enableTextExport && this._addonOptions && !this._addonOptions.hideExportTextDelimitedCommand) { - const commandName = 'export-text-delimited'; - if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCommandItems.push( - { + // show grid menu: export to text file as tab delimited + if (this.gridOptions.enableTextExport && !this._addonOptions.hideExportTextDelimitedCommand) { + const commandName = 'export-text-delimited'; + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push({ iconCssClass: this._addonOptions.iconExportTextDelimitedCommand || 'fa fa-download', titleKey: `${translationPrefix}${commandLabels?.exportTextDelimitedCommandKey ?? 'EXPORT_TO_TAB_DELIMITED'}`, disabled: false, command: commandName, - positionOrder: 56 - } - ); + positionOrder: 57 + }); + } } - } - // add the custom "Commands" title if there are any commands - const commandItems = this._addonOptions?.commandItems || []; - if (this.gridOptions && this._addonOptions && (Array.isArray(gridMenuCommandItems) && gridMenuCommandItems.length > 0 || (Array.isArray(commandItems) && commandItems.length > 0))) { - this._addonOptions.commandTitle = this._addonOptions.commandTitle || this.extensionUtility.getPickerTitleOutputString('commandTitle', 'gridMenu'); + // add the custom "Commands" title if there are any commands + const commandItems = this._addonOptions?.commandItems || []; + if ((Array.isArray(gridMenuCommandItems) && gridMenuCommandItems.length > 0 || (Array.isArray(commandItems) && commandItems.length > 0))) { + this._addonOptions.commandTitle = this._addonOptions.commandTitle || this.extensionUtility.getPickerTitleOutputString('commandTitle', 'gridMenu'); + } } return gridMenuCommandItems; @@ -797,6 +795,11 @@ export class SlickGridMenu extends MenuBaseClass { console.error(`[Slickgrid-Universal] You must register the TextExportService to properly use Export to File in the Grid Menu. Example:: this.gridOptions = { enableTextExport: true, externalResources: [new TextExportService()] };`); } break; + case 'toggle-dark-mode': + const currentDarkMode = this.sharedService.gridOptions.darkMode; + this.grid.setOptions({ darkMode: !currentDarkMode }); + this.sharedService.gridOptions.darkMode = !currentDarkMode; + break; case 'toggle-filter': let showHeaderRow = this.gridOptions?.showHeaderRow ?? false; showHeaderRow = !showHeaderRow; // inverse show header flag diff --git a/packages/common/src/global-grid-options.ts b/packages/common/src/global-grid-options.ts index dacae5f36..882da733b 100644 --- a/packages/common/src/global-grid-options.ts +++ b/packages/common/src/global-grid-options.ts @@ -182,6 +182,7 @@ export const GlobalGridOptions: Partial = { exportExcelCommandKey: 'EXPORT_TO_EXCEL', exportTextDelimitedCommandKey: 'EXPORT_TO_TAB_DELIMITED', refreshDatasetCommandKey: 'REFRESH_DATASET', + toggleDarkModeCommandKey: 'TOGGLE_DARK_MODE', toggleFilterCommandKey: 'TOGGLE_FILTER_ROW', togglePreHeaderCommandKey: 'TOGGLE_PRE_HEADER_ROW', }, @@ -195,6 +196,7 @@ export const GlobalGridOptions: Partial = { hideRefreshDatasetCommand: false, hideSyncResizeButton: true, hideToggleFilterCommand: false, + hideToggleDarkModeCommand: true, hideTogglePreHeaderCommand: false, iconCssClass: 'fa fa-bars mdi mdi-menu', iconClearAllFiltersCommand: 'fa fa-filter mdi mdi-filter-remove-outline', @@ -204,6 +206,7 @@ export const GlobalGridOptions: Partial = { iconExportExcelCommand: 'fa fa-file-excel-o mdi mdi-file-excel-outline', iconExportTextDelimitedCommand: 'fa fa-download mdi mdi-download', iconRefreshDatasetCommand: 'fa fa-refresh mdi mdi-sync', + iconToggleDarkModeCommand: 'fa fa-random mdi mdi-brightness-4', iconToggleFilterCommand: 'fa fa-random mdi mdi-flip-vertical', iconTogglePreHeaderCommand: 'fa fa-random mdi mdi-flip-vertical', menuWidth: 16, diff --git a/packages/common/src/interfaces/gridMenuLabel.interface.ts b/packages/common/src/interfaces/gridMenuLabel.interface.ts index 62b6b5bd2..6796c07e6 100644 --- a/packages/common/src/interfaces/gridMenuLabel.interface.ts +++ b/packages/common/src/interfaces/gridMenuLabel.interface.ts @@ -41,6 +41,12 @@ export interface GridMenuLabel { /** Defaults to "REFRESH_DATASET" translation key */ refreshDatasetCommandKey?: string; + /** Defaults to "Toggle Dark Mode" */ + toggleDarkModeCommand?: string; + + /** Defaults to "TOGGLE_DARK_MODE" translation key */ + toggleDarkModeCommandKey?: string; + /** Defaults to "Toggle Filter Row" */ toggleFilterCommand?: string; diff --git a/packages/common/src/interfaces/gridMenuOption.interface.ts b/packages/common/src/interfaces/gridMenuOption.interface.ts index 0681023a4..91357f286 100644 --- a/packages/common/src/interfaces/gridMenuOption.interface.ts +++ b/packages/common/src/interfaces/gridMenuOption.interface.ts @@ -70,6 +70,9 @@ export interface GridMenuOption { /** Defaults to false, show/hide 1 of the last 2 checkbox at the end of the picker list */ hideSyncResizeButton?: boolean; + /** Defaults to true, which will hide the "Toggle Dark Mode" command in the Grid Menu. */ + hideToggleDarkModeCommand?: boolean; + /** Defaults to false, which will hide the "Toggle Filter Row" command in the Grid Menu (Grid Option "enableFiltering: true" has to be enabled) */ hideToggleFilterCommand?: boolean; @@ -100,6 +103,9 @@ export interface GridMenuOption { /** icon for the "Refresh Dataset" command */ iconRefreshDatasetCommand?: string; + /** icon for the "Toggle Dark Mode" command */ + iconToggleDarkModeCommand?: string; + /** icon for the "Toggle Filter Row" command */ iconToggleFilterCommand?: string; diff --git a/packages/common/src/interfaces/locale.interface.ts b/packages/common/src/interfaces/locale.interface.ts index fde9f362c..8d3c01796 100644 --- a/packages/common/src/interfaces/locale.interface.ts +++ b/packages/common/src/interfaces/locale.interface.ts @@ -191,6 +191,9 @@ export interface Locale { /** Text "Toggle all Groups" which can optionally show in a button inside the Draggable Grouping pre-header row */ TEXT_TOGGLE_ALL_GROUPS?: string; + /** Text "Toggle Dark Mode" shown in Grid Menu (when enabled) */ + TEXT_TOGGLE_DARK_MODE?: string; + /** Text "Toggle Filter Row" shown in Grid Menu (when enabled) */ TEXT_TOGGLE_FILTER_ROW?: string; diff --git a/packages/common/src/styles/_variables-theme-material.scss b/packages/common/src/styles/_variables-theme-material.scss index 0bea091bb..990535985 100644 --- a/packages/common/src/styles/_variables-theme-material.scss +++ b/packages/common/src/styles/_variables-theme-material.scss @@ -163,7 +163,7 @@ $slick-pagination-page-input-border-radius: 3px !default; .slick-dark-mode .ms-dark-mode, .slick-dark-mode { --slick-base-dark-menu-bg-color: #212121; - --slick-primary-color: #4caf50; + --slick-primary-color: #66bb6a; --slick-button-primary-bg-color: var(--slick-primary-color); --slick-cell-box-shadow: none; --slick-column-picker-checkbox-color: #49a54e; @@ -175,11 +175,11 @@ $slick-pagination-page-input-border-radius: 3px !default; --slick-container-border-left: 1px solid #505050; --slick-container-border-bottom: 1px solid #505050; --slick-pane-top-border-top: 1px solid #505050; - --slick-filled-filter-color: #00ab3b; + --slick-filled-filter-color: #66bb6a; --slick-highlight-color: #49a54e; --slick-pagination-icon-color: #49a54e; --slick-checkbox-selector-opacity: 1; - --slick-checkbox-selector-checked-color: #4caf50; + --slick-checkbox-selector-checked-color: #66bb6a; --slick-row-mouse-hover-box-shadow: none; --slick-row-mouse-hover-color: #575757; --slick-row-selected-color: #4b4b4b; diff --git a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts index a762d92d1..21c8fa6ee 100644 --- a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts +++ b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts @@ -55,6 +55,7 @@ export const SalesforceGlobalGridOptions = { commandLabels: { clearFrozenColumnsCommandKey: 'UNFREEZE_COLUMNS', }, + hideToggleDarkModeCommand: false, hideTogglePreHeaderCommand: true, hideRefreshDatasetCommand: true, hideClearFrozenColumnsCommand: false, diff --git a/test/translateServiceStub.ts b/test/translateServiceStub.ts index 3a7bfc9aa..6c5396183 100644 --- a/test/translateServiceStub.ts +++ b/test/translateServiceStub.ts @@ -82,6 +82,7 @@ export class TranslateServiceStub implements TranslaterService { case 'STARTS_WITH': output = this._locale === 'en' ? 'Starts With' : 'Commence par'; break; case 'SYNCHRONOUS_RESIZE': output = this._locale === 'en' ? 'Synchronous resize' : 'Redimension synchrone'; break; case 'TITLE': output = this._locale === 'en' ? 'Title' : 'Titre'; break; + case 'TOGGLE_DARK_MODE': output = this._locale === 'en' ? 'Toggle Dark Mode' : 'Basculer le mode clair/sombre'; break; case 'TOGGLE_FILTER_ROW': output = this._locale === 'en' ? 'Toggle Filter Row' : 'Basculer la ligne des filtres'; break; case 'TOGGLE_PRE_HEADER_ROW': output = this._locale === 'en' ? 'Toggle Pre-Header Row' : 'Basculer la ligne de pré-en-tête'; break; case 'TRUE': output = this._locale === 'en' ? 'True' : 'Vrai'; break;