diff --git a/cypress/e2e/example-colspan.cy.ts b/cypress/e2e/example-colspan.cy.ts
index 85aebc02..2b2fbf11 100644
--- a/cypress/e2e/example-colspan.cy.ts
+++ b/cypress/e2e/example-colspan.cy.ts
@@ -7,8 +7,9 @@ describe('Example - Column Span & Header Grouping', { retries: 1 }, () => {
}
it('should display Example title', () => {
- cy.visit(`${Cypress.config('baseUrl')}/examples/example-colspan.html`);
- cy.get('h2').contains('Demonstrates');
+ cy.visit(`${Cypress.config('baseUrl')}/examples/example-colspan.html`);
+ cy.get('h2').contains('Demonstrates');
+ cy.get('h2 + ul > li').first().contains('column span');
});
it('should have exact column titles', () => {
diff --git a/cypress/e2e/example-csp-header.cy.ts b/cypress/e2e/example-csp-header.cy.ts
new file mode 100644
index 00000000..4c2bcc66
--- /dev/null
+++ b/cypress/e2e/example-csp-header.cy.ts
@@ -0,0 +1,52 @@
+describe('Example CSP Header - with Column Span & Header Grouping', () => {
+ // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows
+ const GRID_ROW_HEIGHT = 25;
+ const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven'];
+ for (let i = 0; i < 30; i++) {
+ fullTitles.push(`Mock${i}`);
+ }
+
+ beforeEach(() => {
+ // create a console.log spy for later use
+ cy.window().then((win) => {
+ cy.spy(win.console, "log");
+ });
+ });
+
+ it('should display Example title', () => {
+ cy.visit(`${Cypress.config('baseUrl')}/examples/example-csp-header.html`);
+ cy.get('h2').contains('Demonstrates');
+ cy.get('h2 + ul > li').first().contains('column span');
+ });
+
+ it('should have exact column titles', () => {
+ cy.get('#myGrid')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
+ });
+
+ it('should expect 1st row to be 1 column spanned to the entire width', () => {
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(0)`).should('contain', 'Task 0');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(1)`).should('not.exist');
+ });
+
+ it('should expect 2nd row to be 4 columns and not be spanned', () => {
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0)`).should('contain', 'Task 1');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(1)`).should('contain', '5 days');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(2)`).should('contain', '01/05/2009');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(3)`).contains(/(true|false)/);
+ });
+
+ it('should expect 3rd row to be 1 column spanned to the entire width', () => {
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0)`).should('contain', 'Task 2');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(1)`).should('not.exist');
+ });
+
+ it('should expect 4th row to be 4 columns and not be spanned', () => {
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('contain', 'Task 3');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(1)`).should('contain', '5 days');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(2)`).should('contain', '01/05/2009');
+ cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(3)`).contains(/(true|false)/);
+ });
+});
diff --git a/examples/example-csp-header.html b/examples/example-csp-header.html
index c125e19b..f5c0d991 100644
--- a/examples/example-csp-header.html
+++ b/examples/example-csp-header.html
@@ -4,7 +4,8 @@
-
+
+
@@ -30,6 +31,7 @@ View Source:
+
diff --git a/examples/example-csp-header.js b/examples/example-csp-header.js
index 504ab559..ddcd200a 100644
--- a/examples/example-csp-header.js
+++ b/examples/example-csp-header.js
@@ -11,7 +11,7 @@ var columns = [
var options = {
enableCellNavigation: true,
enableColumnReorder: false,
- sanitizer: (dirtyHtml) => DOMPurify.sanitize(dirtyHtml, { RETURN_TRUSTED_TYPE: true })
+ sanitizer: (html) => DOMPurify.sanitize(html, { RETURN_TRUSTED_TYPE: true })
};
document.addEventListener("DOMContentLoaded", function () {
diff --git a/examples/example-csp-policy.js b/examples/example-csp-policy.js
new file mode 100644
index 00000000..5d9d6f70
--- /dev/null
+++ b/examples/example-csp-policy.js
@@ -0,0 +1,5 @@
+if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
+ trustedTypes.createPolicy('sanitizeWithDomPurify', {
+ createHTML: string => DOMPurify.sanitize(string, { RETURN_TRUSTED_TYPE: true })
+ });
+}
\ No newline at end of file
diff --git a/src/slick.core.ts b/src/slick.core.ts
index f538d7bd..59548bed 100644
--- a/src/slick.core.ts
+++ b/src/slick.core.ts
@@ -726,6 +726,11 @@ export class Utils {
if (elementOptions) {
Object.keys(elementOptions).forEach((elmOptionKey) => {
+ if (elmOptionKey === 'innerHTML') {
+ console.warn(`[SlickGrid] For better CSP (Content Security Policy) support, do not use "innerHTML" directly in "createDomElement('${tagName}', { innerHTML: 'some html'})"` +
+ `, it is better as separate assignment: "const elm = createDomElement('span'); elm.innerHTML = 'some html';"`);
+ }
+
const elmValue = elementOptions[elmOptionKey as keyof typeof elementOptions];
if (typeof elmValue === 'object') {
Object.assign(elm[elmOptionKey as K] as object, elmValue);
diff --git a/src/slick.grid.ts b/src/slick.grid.ts
index fd0727d9..891d7d23 100644
--- a/src/slick.grid.ts
+++ b/src/slick.grid.ts
@@ -1537,7 +1537,9 @@ export class SlickGrid = Column, O e
const headerRowTarget = this.hasFrozenColumns() ? ((i <= this._options.frozenColumn!) ? this._headerRowL : this._headerRowR) : this._headerRowL;
const header = Utils.createDomElement('div', { id: `${this.uid + m.id}`, dataset: { id: String(m.id) }, className: 'ui-state-default slick-state-default slick-header-column', title: m.toolTip || '' }, headerTarget);
- Utils.createDomElement('span', { className: 'slick-column-name', innerHTML: this.sanitizeHtmlString(m.name as string) }, header);
+ const colNameElm = Utils.createDomElement('span', { className: 'slick-column-name' }, header);
+ colNameElm.innerHTML = this.sanitizeHtmlString(m.name as string);
+
Utils.width(header, m.width! - this.headerColumnWidthDiff);
let classname = m.headerCssClass || null;
@@ -3017,7 +3019,8 @@ export class SlickGrid = Column, O e
// headers have not yet been created, create a new node
const header = this.getHeader(columnDef) as HTMLElement;
headerColEl = Utils.createDomElement('div', { id: dummyHeaderColElId, className: 'ui-state-default slick-state-default slick-header-column' }, header);
- Utils.createDomElement('span', { className: 'slick-column-name', innerHTML: this.sanitizeHtmlString(String(columnDef.name)) }, headerColEl);
+ const colNameElm = Utils.createDomElement('span', { className: 'slick-column-name' }, headerColEl);
+ colNameElm.innerHTML = this.sanitizeHtmlString(String(columnDef.name));
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
if (columnDef.headerCssClass) {
headerColEl.classList.add(...(columnDef.headerCssClass || '').split(' '));
@@ -4552,7 +4555,9 @@ export class SlickGrid = Column, O e
return;
}
- const x = Utils.createDomElement('div', { innerHTML: this.sanitizeHtmlString(stringArray.join('')) });
+ const x = document.createElement('div');
+ x.innerHTML = this.sanitizeHtmlString(stringArray.join(''));
+
let processedRow: number | null | undefined;
let node: HTMLElement;
while (Utils.isDefined(processedRow = processedRows.pop())) {
@@ -4612,8 +4617,10 @@ export class SlickGrid = Column, O e
if (!rows.length) { return; }
- const x = Utils.createDomElement('div', { innerHTML: this.sanitizeHtmlString(stringArrayL.join('')) });
- const xRight = Utils.createDomElement('div', { innerHTML: this.sanitizeHtmlString(stringArrayR.join('')) });
+ const x = document.createElement('div');
+ const xRight = document.createElement('div');
+ x.innerHTML = this.sanitizeHtmlString(stringArrayL.join(''));
+ xRight.innerHTML = this.sanitizeHtmlString(stringArrayR.join(''));
for (let i = 0, ii = rows.length; i < ii; i++) {
if ((this.hasFrozenRows) && (rows[i] >= this.actualFrozenRow)) {