diff --git a/.vscode/settings.json b/.vscode/settings.json index 57b4ac204..8e59c5ac8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,11 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, - "eslint.validate": [ - "javascript", - "typescript" - ], + "eslint.validate": ["javascript", "typescript"], + "typescript.experimental.useTsgo": true, "typescript.format.semicolons": "insert", "vitest.rootConfig": "./test/vitest.config.mts", "typescript.tsdk": "node_modules\\typescript\\lib", "prettier.prettierPath": "node_modules/prettier/index.cjs", "prettier.configPath": ".prettierrc" -} \ No newline at end of file +} diff --git a/packages/common/src/core/slickCore.ts b/packages/common/src/core/slickCore.ts index d38b030e2..53354c6f5 100644 --- a/packages/common/src/core/slickCore.ts +++ b/packages/common/src/core/slickCore.ts @@ -5,6 +5,7 @@ * @module Core * @namespace Slick */ +import { setStyles } from '@slickgrid-universal/utils'; import type { MergeTypes } from '../enums/index.js'; import type { DragRange, EditController } from '../interfaces/index.js'; import type { SlickGrid } from './slickGrid.js'; @@ -734,11 +735,15 @@ export class Utils { Utils.setStyleSize(el, 'width', value); } - public static setStyleSize(el: HTMLElement, style: string, val?: number | string | Function): void { + public static setStyleSize

>( + el: HTMLElement, + style: keyof P, + val?: number | string | Function + ): void { if (typeof val === 'function') { - val = val(); + val = val() as number | string; } - el.style.setProperty(style, typeof val === 'string' ? val : `${val}px`); + setStyles(el, { [style]: typeof val === 'string' ? val : `${val}px` } as P); } public static isHidden(el: HTMLElement): boolean { diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index fec87f846..2b518f9fa 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -12,6 +12,7 @@ import { isDefinedNumber, isPrimitiveOrHTML, queueMicrotaskPolyfill, + type CSSStyleDeclarationWritable, } from '@slickgrid-universal/utils'; import type { SortableEvent, Options as SortableOptions } from 'sortablejs'; import Sortable from 'sortablejs/modular/sortable.core.esm.js'; @@ -1102,23 +1103,20 @@ export class SlickGrid = Column, O e }); } - restoreCssFromHiddenInit(): void { - // Finish handling display:none on container or container parents - // - Put values back the way they were - if (this._hiddenParents && this.oldProps) { - this._hiddenParents.forEach((el: HTMLElement, index: number) => { - const old: CSSStyleDeclaration = this.oldProps[index] as CSSStyleDeclaration; - + restoreCssFromHiddenInit

>(): void { + // finish handle display:none on container or container parents + // - put values back the way they were + let i = 0; + if (this._hiddenParents) { + this._hiddenParents.forEach((el) => { + const old = this.oldProps[i++]; Object.keys(this.cssShow).forEach((name) => { if (this.cssShow) { - const value = old[name as keyof CSSStyleDeclaration]; - el.style.setProperty(name, value != null ? String(value) : ''); + (el.style as unknown as P)[name as keyof P] = (old as any)[name]; } }); }); - - // Clear the hidden parents array - this._hiddenParents.length = 0; // Correct way to clear the array + this._hiddenParents.length = 0; } } diff --git a/packages/common/src/extensions/__tests__/slickCellRangeDecorator.spec.ts b/packages/common/src/extensions/__tests__/slickCellRangeDecorator.spec.ts index 7722f8cb7..05c2f6751 100644 --- a/packages/common/src/extensions/__tests__/slickCellRangeDecorator.spec.ts +++ b/packages/common/src/extensions/__tests__/slickCellRangeDecorator.spec.ts @@ -56,6 +56,8 @@ describe('CellRangeDecorator Plugin', () => { expect(plugin.addonElement!.style.left).toEqual(''); expect(plugin.addonElement!.style.height).toEqual(''); expect(plugin.addonElement!.style.width).toEqual(''); + expect(plugin.addonElement?.style.border).toBe('2px dashed red'); + expect(plugin.addonElement?.style.zIndex).toBe('9999'); }); it('should Show range when called and calculate new position when getCellNodeBox returns a cell position', () => { @@ -70,12 +72,39 @@ describe('CellRangeDecorator Plugin', () => { expect(plugin.addonElement!.style.left).toEqual('31px'); // 26 + 5px expect(plugin.addonElement!.style.height).toEqual('20px'); // 12 - 25 + 33px expect(plugin.addonElement!.style.width).toEqual('13px'); // 27 - 26 + 12px + expect(plugin.addonElement?.style.border).toBe('2px dashed red'); + expect(plugin.addonElement?.style.zIndex).toBe('9999'); }); it('should be able to set selection CSS', () => { + const setSelectionCss = { border: '2px solid green', zIndex: '9998' } as CSSStyleDeclaration; plugin = new SlickCellRangeDecorator(gridStub, { offset: { top: 20, left: 5, width: 12, height: 33 } }); - plugin.setSelectionCss({ border: '2px solid green', zIndex: '9998' } as CSSStyleDeclaration); + plugin.setSelectionCss(setSelectionCss); expect(plugin.getSelectionCss()).toEqual({ border: '2px solid green', zIndex: '9998' } as CSSStyleDeclaration); }); + + it('should be able to define different selection CSS and then override them with setSelectionCss()', () => { + const ctorSelectionCss = { border: '3px solid purple', zIndex: '9997' }; + const setSelectionCss = { border: '2px solid green', zIndex: '9998' } as CSSStyleDeclaration; + plugin = new SlickCellRangeDecorator(gridStub, { + offset: { top: 20, left: 5, width: 12, height: 33 }, + selectionCss: ctorSelectionCss, + }); + plugin.setSelectionCss(setSelectionCss); + + expect(plugin.getSelectionCss()).toEqual(setSelectionCss); + }); + + it('should be able to define different selection CSS', () => { + const ctorSelectionCss = { border: '3px solid purple', zIndex: '9997' }; + plugin = new SlickCellRangeDecorator(gridStub, { + offset: { top: 20, left: 5, width: 12, height: 33 }, + selectionCss: ctorSelectionCss, + }); + plugin.show({ fromCell: 1, fromRow: 2, toCell: 3, toRow: 4 } as SlickRange); + + expect(plugin.addonElement?.style.border).toBe(ctorSelectionCss.border); + expect(plugin.addonElement?.style.zIndex).toBe(ctorSelectionCss.zIndex); + }); }); diff --git a/packages/common/src/extensions/slickCellRangeDecorator.ts b/packages/common/src/extensions/slickCellRangeDecorator.ts index 3a60a8f33..ad4250aa1 100644 --- a/packages/common/src/extensions/slickCellRangeDecorator.ts +++ b/packages/common/src/extensions/slickCellRangeDecorator.ts @@ -1,4 +1,4 @@ -import { createDomElement, deepMerge } from '@slickgrid-universal/utils'; +import { createDomElement, deepMerge, setStyles } from '@slickgrid-universal/utils'; import type { SlickGrid, SlickRange } from '../core/index.js'; import type { CellRangeDecoratorOption } from '../interfaces/index.js'; @@ -16,7 +16,7 @@ export class SlickCellRangeDecorator { protected _options: CellRangeDecoratorOption; protected _elem?: HTMLDivElement | null; - protected _selectionCss: CSSStyleDeclaration; + protected _selectionCss: Partial; protected _defaults = { selectionCssClass: 'slick-range-decorator', selectionCss: { @@ -35,7 +35,7 @@ export class SlickCellRangeDecorator { options?: Partial ) { this._options = deepMerge(this._defaults, options); - this._selectionCss = options?.selectionCss || ({} as CSSStyleDeclaration); + this._selectionCss = this._options?.selectionCss || ({} as Partial); } get addonOptions(): CellRangeDecoratorOption { @@ -53,11 +53,11 @@ export class SlickCellRangeDecorator { init(): void {} - getSelectionCss(): CSSStyleDeclaration { + getSelectionCss(): Partial { return this._selectionCss; } - setSelectionCss(cssProps: CSSStyleDeclaration): void { + setSelectionCss(cssProps: Partial): void { this._selectionCss = cssProps; } @@ -74,15 +74,10 @@ export class SlickCellRangeDecorator { } // Determine which CSS style to use - const css = isCopyTo && this._options.copyToSelectionCss ? this._options.copyToSelectionCss : this._options.selectionCss; + const css = isCopyTo && this._options.copyToSelectionCss ? this._options.copyToSelectionCss : this._selectionCss; // Apply styles to the element - Object.keys(css).forEach((cssStyleKey: string) => { - const value = css[cssStyleKey as keyof CSSStyleDeclaration]; - if (this._elem!.style[cssStyleKey as keyof CSSStyleDeclaration] !== value) { - this._elem!.style.setProperty(cssStyleKey, String(value)); - } - }); + setStyles(this._elem, css); // Get the boxes for the selected cells const from = this.grid.getCellNodeBox(range.fromRow, range.fromCell); diff --git a/packages/common/src/interfaces/cellRange.interface.ts b/packages/common/src/interfaces/cellRange.interface.ts index bcf66913a..56bc4e4ad 100644 --- a/packages/common/src/interfaces/cellRange.interface.ts +++ b/packages/common/src/interfaces/cellRange.interface.ts @@ -5,10 +5,10 @@ export interface CellRangeDecoratorOption { selectionCssClass: string; /** CSS styling to add to decorator (for example blue background on cell) */ - selectionCss: CSSStyleDeclaration; + selectionCss: Partial; /** CSS styling for drag-fill rangel marker (optional) */ - copyToSelectionCss: CSSStyleDeclaration; + copyToSelectionCss: Partial; /** offset to add to the cell range outer box size calculation */ offset: { top: number; left: number; height: number; width: number }; diff --git a/packages/common/src/interfaces/cssDeclaration.interface.ts b/packages/common/src/interfaces/cssDeclaration.interface.ts deleted file mode 100644 index 7b9ec7df0..000000000 --- a/packages/common/src/interfaces/cssDeclaration.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type CSSStyleDeclarationReadonly = - | 'length' - | 'parentRule' - | 'getPropertyPriority' - | 'getPropertyValue' - | 'item' - | 'removeProperty' - | 'setProperty'; -export type CSSStyleDeclarationWritable = keyof Omit; diff --git a/packages/common/src/interfaces/index.ts b/packages/common/src/interfaces/index.ts index cc509d1f9..26bec24bf 100644 --- a/packages/common/src/interfaces/index.ts +++ b/packages/common/src/interfaces/index.ts @@ -32,7 +32,6 @@ export type * from './compositeEditorOption.interface.js'; export type * from './contextMenu.interface.js'; export type * from './contextMenuLabel.interface.js'; export type * from './contextMenuOption.interface.js'; -export type * from './cssDeclaration.interface.js'; export type * from './currentColumn.interface.js'; export type * from './currentFilter.interface.js'; export type * from './currentPagination.interface.js'; diff --git a/packages/utils/src/__tests__/domUtils.spec.ts b/packages/utils/src/__tests__/domUtils.spec.ts index 4fa3558e6..36e4e4e39 100644 --- a/packages/utils/src/__tests__/domUtils.spec.ts +++ b/packages/utils/src/__tests__/domUtils.spec.ts @@ -16,6 +16,7 @@ import { htmlEncode, htmlEncodeWithPadding, insertAfterElement, + setStyles, } from '../domUtils.js'; describe('Service/domUtilies', () => { @@ -419,4 +420,22 @@ describe('Service/domUtilies', () => { expect(div.outerHTML).toBe(`

`); }); }); + + describe('setStyles() method', () => { + it('should assign CSS style properties', () => { + const div = document.createElement('div'); + setStyles(div, { backgroundColor: 'red', border: '1px solid blue' }); + + expect(div.style.backgroundColor).toEqual('red'); + expect(div.style.border).toEqual('1px solid blue'); + }); + + it('should not assign anything when value is undefined', () => { + const div = document.createElement('div'); + setStyles(div, { backgroundColor: null as any, border: '1px solid blue' }); + + expect(div.style.backgroundColor).toEqual(''); + expect(div.style.border).toEqual('1px solid blue'); + }); + }); }); diff --git a/packages/utils/src/domUtils.ts b/packages/utils/src/domUtils.ts index 8cba6b35c..d7b52d3c5 100644 --- a/packages/utils/src/domUtils.ts +++ b/packages/utils/src/domUtils.ts @@ -2,6 +2,16 @@ import type { HtmlElementPosition } from './models/interfaces.js'; import type { InferDOMType } from './models/types.js'; import { isDefined } from './utils.js'; +export type CSSStyleDeclarationReadonly = + | 'length' + | 'parentRule' + | 'getPropertyPriority' + | 'getPropertyValue' + | 'item' + | 'removeProperty' + | 'setProperty'; +export type CSSStyleDeclarationWritable = Omit; + /** Calculate available space for each side of the DOM element */ export function calculateAvailableSpace(element: HTMLElement): { top: number; bottom: number; left: number; right: number } { const vh = window.innerHeight || 0; @@ -54,6 +64,18 @@ export function createDomElement>(element: T, styles: P): void { + Object.keys(styles).forEach((key) => { + const camelStyleKey = key as keyof P; + const value = styles[camelStyleKey]; + + // Ensure value is valid and assignable to the style property + if (value !== undefined && value !== null) { + (element.style as unknown as P)[camelStyleKey] = value; + } + }); +} + /** * Accepts string containing the class or space-separated list of classes, and * returns list of individual classes.