From 8bd346b1461e303e3f5edc26edcead7c57d4ee39 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Sun, 5 Nov 2023 23:20:10 -0500 Subject: [PATCH] chore: add cssStyler to both regular and optgroup option list (#149) --- demo/src/options/options26.html | 24 +++++++++++++++++++ demo/src/options/options26.ts | 18 +++++++++++--- lib/src/MultipleSelectInstance.ts | 15 ++++++------ lib/src/constants.ts | 1 + .../multipleSelectOption.interface.ts | 2 +- lib/src/utils/domUtils.ts | 9 +++++++ playwright/e2e/options26.spec.ts | 17 ++++++++++--- 7 files changed, 71 insertions(+), 15 deletions(-) diff --git a/demo/src/options/options26.html b/demo/src/options/options26.html index fa0bb9ac..ac862d8d 100644 --- a/demo/src/options/options26.html +++ b/demo/src/options/options26.html @@ -87,4 +87,28 @@

+ +
+ + +
+ +
+
diff --git a/demo/src/options/options26.ts b/demo/src/options/options26.ts index 089529df..2dd8e021 100644 --- a/demo/src/options/options26.ts +++ b/demo/src/options/options26.ts @@ -6,7 +6,7 @@ export default class Example { ms3?: MultipleSelectInstance; mount() { - this.ms1 = multipleSelect('#basic', { + this.ms2 = multipleSelect('[data-test="select1"]', { styler: (row: OptionRowData | OptGroupRowData) => { if (+(row?.value ?? 0) === 1) { return 'background-color: #ffee00; color: #ff0000;'; @@ -18,7 +18,7 @@ export default class Example { }, }) as MultipleSelectInstance; - this.ms2 = multipleSelect('#group', { + this.ms2 = multipleSelect('[data-test="select2"]', { styler: (row: OptionRowData | OptGroupRowData) => { if (row?.type === 'optgroup') { return 'color: #777; font-weight: normal;'; @@ -30,7 +30,7 @@ export default class Example { }, }) as MultipleSelectInstance; - this.ms3 = multipleSelect('#select3', { + this.ms2 = multipleSelect('[data-test="select3"]', { cssStyler: (row: OptionRowData | OptGroupRowData) => { if (+(row?.value ?? 0) === 2) { return { backgroundColor: '#6fbeff', color: '#0014ff', fontStyle: 'italic' } as CSSStyleDeclaration; @@ -41,6 +41,18 @@ export default class Example { return null; }, }) as MultipleSelectInstance; + + this.ms2 = multipleSelect('[data-test="select4"]', { + cssStyler: (row: OptionRowData | OptGroupRowData) => { + if (row?.type === 'optgroup') { + return { color: '#787878', fontWeight: 'normal' } as CSSStyleDeclaration; + } + if (+(row?.value ?? 0) === 3) { + return { color: 'purple', textDecoration: 'underline' } as CSSStyleDeclaration; + } + return null; + }, + }) as MultipleSelectInstance; } unmount() { diff --git a/lib/src/MultipleSelectInstance.ts b/lib/src/MultipleSelectInstance.ts index de6cddc4..ec330013 100644 --- a/lib/src/MultipleSelectInstance.ts +++ b/lib/src/MultipleSelectInstance.ts @@ -4,6 +4,7 @@ import Constants from './constants'; import { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys, stripScripts } from './utils'; import { + applyCssRules, applyParsedStyleToElement, calculateAvailableSpace, createDomElement, @@ -15,7 +16,7 @@ import { toggleElement, } from './utils/domUtils'; import type { HtmlElementPosition } from './utils/domUtils'; -import type { CSSStyleDeclarationWritable, MultipleSelectOption } from './interfaces/multipleSelectOption.interface'; +import type { MultipleSelectOption } from './interfaces/multipleSelectOption.interface'; import type { MultipleSelectLocales, OptGroupRowData, OptionDataObject, OptionRowData } from './interfaces'; import { BindingEventService, VirtualScroll } from './services'; @@ -516,6 +517,7 @@ export class MultipleSelectInstance { } if (row.type === 'optgroup') { + const customStyleRules = this.options.cssStyler(row); const customStyle = this.options.styler(row); const styleStr = String(customStyle || ''); const htmlElms: HTMLElement[] = []; @@ -544,6 +546,7 @@ export class MultipleSelectInstance { labelElm.appendChild(spanElm); const liElm = createDomElement('li', { className: `group ${classes}`.trim() }); applyParsedStyleToElement(liElm, styleStr); + applyCssRules(liElm, customStyleRules); liElm.appendChild(labelElm); htmlElms.push(liElm); @@ -554,6 +557,7 @@ export class MultipleSelectInstance { return htmlElms; } + const customStyleRules = this.options.cssStyler(row); const customStyle = this.options.styler(row); const style = String(customStyle || ''); classes += row.classes || ''; @@ -574,14 +578,9 @@ export class MultipleSelectInstance { if (title) { liElm.title = title; } - applyParsedStyleToElement(liElm, style); - const customStyleCss = this.options.cssStyler?.(row); - if (customStyleCss) { - for (const styleProp of Object.keys(customStyleCss)) { - liElm.style[styleProp as CSSStyleDeclarationWritable] = customStyleCss[styleProp as CSSStyleDeclarationWritable]; - } - } + applyParsedStyleToElement(liElm, style); + applyCssRules(liElm, customStyleRules); const labelClasses = `${row.disabled ? 'disabled' : ''}`; const labelElm = document.createElement('label'); if (labelClasses) { diff --git a/lib/src/constants.ts b/lib/src/constants.ts index 947c1baa..63520bd7 100644 --- a/lib/src/constants.ts +++ b/lib/src/constants.ts @@ -55,6 +55,7 @@ const DEFAULTS: Partial = { useSelectOptionLabel: false, useSelectOptionLabelToHtml: false, + cssStyler: () => null, styler: () => false, textTemplate: (elm: HTMLOptionElement) => elm.innerHTML.trim(), labelTemplate: (elm: HTMLOptionElement) => elm.label, diff --git a/lib/src/interfaces/multipleSelectOption.interface.ts b/lib/src/interfaces/multipleSelectOption.interface.ts index eecf684c..68124fdd 100644 --- a/lib/src/interfaces/multipleSelectOption.interface.ts +++ b/lib/src/interfaces/multipleSelectOption.interface.ts @@ -187,7 +187,7 @@ export interface MultipleSelectOption extends MultipleSelectLocale { customFilter(options: LabelFilter | TextFilter): boolean; /** The item styler function, return style string to customize the item style such as background: red. The function take one parameter: value. */ - cssStyler?: (value: OptionRowData | OptGroupRowData) => CSSStyleDeclaration | null; + cssStyler: (value: OptionRowData | OptGroupRowData) => CSSStyleDeclaration | null; /** @deprecated @use `cssStyler`. The item styler function, return style string to customize the item style such as background: red. The function take one parameter: value. */ styler: (value: OptionRowData | OptGroupRowData) => string | boolean | null; diff --git a/lib/src/utils/domUtils.ts b/lib/src/utils/domUtils.ts index 05357ed1..4cdd02d7 100644 --- a/lib/src/utils/domUtils.ts +++ b/lib/src/utils/domUtils.ts @@ -1,3 +1,4 @@ +import { CSSStyleDeclarationWritable } from '../interfaces'; import { toCamelCase } from './utils'; export type InferType = T extends infer R ? R : any; @@ -13,6 +14,14 @@ export interface HtmlElementPosition { right: number; } +export function applyCssRules(elm: HTMLElement, rules: CSSStyleDeclaration | null) { + if (elm && rules) { + for (const styleProp of Object.keys(rules)) { + elm.style[styleProp as CSSStyleDeclarationWritable] = rules[styleProp as CSSStyleDeclarationWritable]; + } + } +} + export function applyParsedStyleToElement(elm: HTMLElement, styleStr: string) { if (styleStr) { const cstyles = styleStr.replace(/\s/g, '').split(';'); diff --git a/playwright/e2e/options26.spec.ts b/playwright/e2e/options26.spec.ts index 8613a44c..b845f506 100644 --- a/playwright/e2e/options26.spec.ts +++ b/playwright/e2e/options26.spec.ts @@ -5,7 +5,7 @@ test.describe('Options 26 - The Styler / CSS Styler', () => { await page.goto('#/options26'); }); - test('first select has January & June with custom styling', async ({ page }) => { + test('1st select has January & June with custom styling', async ({ page }) => { await page.locator('[data-test=select1].ms-parent').click(); const optionLoc1 = await page.locator('[data-test=select1] .ms-drop ul li').nth(0); optionLoc1.click(); @@ -22,7 +22,7 @@ test.describe('Options 26 - The Styler / CSS Styler', () => { await selectedText1.waitFor(); }); - test('second select has January & June with custom styling', async ({ page }) => { + test('2nd select has January & June with custom styling', async ({ page }) => { await page.locator('[data-test=select2].ms-parent').click(); const optionLoc1 = await page.locator('[data-test=select2] .ms-drop ul li').nth(1); optionLoc1.click(); @@ -33,7 +33,7 @@ test.describe('Options 26 - The Styler / CSS Styler', () => { await dropLoc2.waitFor(); }); - test('third select has February & April with custom CSS styler', async ({ page }) => { + test('3rd select has February & April with custom CSS styler', async ({ page }) => { await page.locator('[data-test=select3].ms-parent').click(); const optionLoc2 = await page.locator('[data-test=select3] .ms-drop ul li').nth(1); optionLoc2.click(); @@ -49,4 +49,15 @@ test.describe('Options 26 - The Styler / CSS Styler', () => { const selectedText3 = page.locator('[data-test=select3] .ms-choice span', { hasText: 'February, April' }); await selectedText3.waitFor(); }); + + test('4th select has January & June with custom styling', async ({ page }) => { + await page.locator('[data-test=select4].ms-parent').click(); + const optionLoc3 = await page.locator('[data-test=select4] .ms-drop ul li').nth(3); + optionLoc3.click(); + expect(optionLoc3).toHaveText('Option 3'); + await expect(optionLoc3).toHaveCSS('color', 'rgb(128, 0, 128)'); + await expect(optionLoc3).toHaveCSS('text-decoration', 'underline solid rgb(128, 0, 128)'); + const dropLoc2 = await page.locator('[data-test=select4] .ms-choice span', { hasText: '[Group 1: Option 3]' }); + await dropLoc2.waitFor(); + }); });