Skip to content

Commit a10cba6

Browse files
authored
Merge pull request #2997 from gridstack/feat/static-css
- Removed dynamic stylesheet and migrated to CSS vars
2 parents 38d336d + 9daee64 commit a10cba6

File tree

8 files changed

+80
-194
lines changed

8 files changed

+80
-194
lines changed

doc/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ GridStack will add it to the `<style>` elements it creates.
127127
- `row` - fix grid number of rows. This is a shortcut of writing `minRow:N, maxRow:N`. (default `0` no constrain)
128128
- `rtl` - if `true` turns grid to RTL. Possible values are `true`, `false`, `'auto'` (default: `'auto'`) See [example](https://gridstackjs.com/demo/right-to-left(rtl).html)
129129
- `staticGrid` - removes drag|drop|resize (default `false`). If `true` widgets are not movable/resizable by the user, but code can still move and oneColumnMode will still work. You can use the smaller gridstack-static.js lib. A CSS class `grid-stack-static` is also added to the container.
130-
- `styleInHead` - if `true` will add style element to `<head>` otherwise will add it to element's parent node (default `false`).
131130

132131
### Responsive
133132
v10.x supports a much richer responsive behavior, you can have breakpoints of width:column, or auto column sizing, where no code is longer needed.

spec/gridstack-spec.ts

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,61 +1419,16 @@ describe('gridstack >', function() {
14191419
grid = GridStack.init(options);
14201420
expect(grid.el.classList.contains('grid-stack-rtl')).toBe(false);
14211421
});
1422-
});
1423-
1424-
describe('grid.opts.styleInHead >', function() {
1425-
beforeEach(function() {
1426-
document.body.insertAdjacentHTML('afterbegin', gridstackHTML);
1427-
});
1428-
afterEach(function() {
1429-
document.body.removeChild(document.getElementById('gs-cont'));
1430-
});
1431-
it('should add STYLE to parent node as a default >', function() {
1432-
var options = {
1433-
cellHeight: 80,
1434-
verticalMargin: 10,
1435-
float: false,
1436-
};
1437-
var grid = GridStack.init(options);
1438-
expect((grid as any)._styles.parentElement.tagName).toBe('DIV'); // any to access private _styles
1439-
});
1440-
it('should add STYLE to HEAD if styleInHead === true >', function() {
1441-
var options = {
1442-
cellHeight: 80,
1443-
verticalMargin: 10,
1444-
float: false,
1445-
styleInHead: true
1446-
};
1447-
var grid = GridStack.init(options);
1448-
expect((grid as any)._styles.parentElement.tagName).toBe('HEAD'); // any to access private _styles
1449-
});
1450-
});
1451-
1452-
describe('grid.opts.styleInHead >', function() {
1453-
beforeEach(function() {
1454-
document.body.insertAdjacentHTML('afterbegin', gridstackHTML);
1455-
});
1456-
afterEach(function() {
1457-
document.body.removeChild(document.getElementById('gs-cont'));
1458-
});
1459-
it('should add STYLE to parent node as a default >', function() {
1460-
var grid = GridStack.init();
1461-
expect((grid as any)._styles.parentElement.tagName).toBe('DIV');
1462-
});
1463-
it('should add STYLE to HEAD if styleInHead === true >', function() {
1464-
var grid = GridStack.init({styleInHead: true});
1465-
expect((grid as any)._styles.parentElement.tagName).toBe('HEAD');
1466-
});
1467-
});
1422+
});
14681423

1469-
describe('grid.enableMove >', function() {
1424+
describe('grid.enableMove', function() {
14701425
beforeEach(function() {
14711426
document.body.insertAdjacentHTML('afterbegin', gridstackHTML);
14721427
});
14731428
afterEach(function() {
14741429
document.body.removeChild(document.getElementById('gs-cont'));
14751430
});
1476-
it('should enable move for future also >', function() {
1431+
it('should enable move for future also', function() {
14771432
let options = {
14781433
cellHeight: 80,
14791434
margin: 5,

spec/utils-spec.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,6 @@ describe('gridstack utils', function() {
4848
});
4949
});
5050

51-
describe('test createStylesheet/removeStylesheet', function() {
52-
53-
it('should create/remove style DOM', function() {
54-
let _id = 'test-123';
55-
Utils.createStylesheet(_id);
56-
57-
let style = document.querySelector('STYLE[gs-style-id=' + _id + ']');
58-
expect(style).not.toBe(null);
59-
// expect(style.prop('tagName')).toEqual('STYLE');
60-
61-
Utils.removeStylesheet(_id)
62-
style = document.querySelector('STYLE[gs-style-id=' + _id + ']');
63-
expect(style).toBe(null);
64-
});
65-
66-
});
67-
6851
describe('test parseHeight', function() {
6952

7053
it('should parse height value', function() {

src/dd-resizable.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,13 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
201201
/** @internal */
202202
protected _resizeStop(event: MouseEvent): DDResizable {
203203
const ev = Utils.initEvent<MouseEvent>(event, { type: 'resizestop', target: this.el });
204+
// Remove style attr now, so the stop handler can rebuild style attrs
205+
this._cleanHelper();
204206
if (this.option.stop) {
205207
this.option.stop(ev); // Note: ui() not used by gridstack so don't pass
206208
}
207209
this.el.classList.remove('ui-resizable-resizing');
208210
this.triggerEvent('resizestop', ev);
209-
this._cleanHelper();
210211
delete this.startEvent;
211212
delete this.originalRect;
212213
delete this.temporalRect;

src/gridstack.scss

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ $animation_speed: .3s !default;
4141
.grid-stack > .grid-stack-item {
4242
position: absolute;
4343
padding: 0;
44+
height: var(--gs-cell-height);
4445

4546
> .grid-stack-item-content {
4647
margin: 0;
@@ -98,6 +99,25 @@ $animation_speed: .3s !default;
9899
> .ui-resizable-sw { cursor: sw-resize; width: 20px; height: 20px;}
99100
> .ui-resizable-w { cursor: w-resize; width: 10px; top: 15px; bottom: 15px; }
100101

102+
> .ui-resizable-n {
103+
top: var(--gs-item-margin-top);
104+
}
105+
> .ui-resizable-s
106+
, > .ui-resizable-se
107+
, > .ui-resizable-sw {
108+
bottom: var(--gs-item-margin-bottom);
109+
}
110+
> .ui-resizable-ne,
111+
> .ui-resizable-e,
112+
> .ui-resizable-se {
113+
right: var(--gs-item-margin-right);
114+
}
115+
> .ui-resizable-nw,
116+
> .ui-resizable-w,
117+
> .ui-resizable-sw {
118+
left: var(--gs-item-margin-left);
119+
}
120+
101121
&.ui-draggable-dragging {
102122
&> .ui-resizable-handle {
103123
display: none !important;
@@ -155,3 +175,14 @@ $animation_speed: .3s !default;
155175
.gs-1 > .grid-stack-item {
156176
width: 100%;
157177
}
178+
179+
.grid-stack {
180+
> .grid-stack-item > .grid-stack-item-content,
181+
> .grid-stack-placeholder > .placeholder-content {
182+
top: var(--gs-item-margin-top);
183+
right: var(--gs-item-margin-right);
184+
bottom: var(--gs-item-margin-bottom);
185+
left: var(--gs-item-margin-left);
186+
}
187+
}
188+

src/gridstack.ts

Lines changed: 39 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ export interface CellPosition {
5151
y: number;
5252
}
5353

54-
interface GridHTMLStyleElement extends HTMLStyleElement {
55-
_max?: number; // internal tracker of the max # of rows we created
56-
}
57-
5854
// extend with internal fields we need - TODO: move other items in here
5955
interface InternalGridStackOptions extends GridStackOptions {
6056
_alwaysShowResizeHandle?: true | false | 'mobile'; // so we can restore for save
@@ -247,8 +243,6 @@ export class GridStack {
247243
protected _ignoreLayoutsNodeChange: boolean;
248244
/** @internal */
249245
public _gsEventHandler = {};
250-
/** @internal */
251-
protected _styles: GridHTMLStyleElement;
252246
/** @internal flag to keep cells square during resize */
253247
protected _isAutoCellHeight: boolean;
254248
/** @internal limit auto cell resizing method */
@@ -396,8 +390,6 @@ export class GridStack {
396390
float: opts.float,
397391
maxRow: opts.maxRow,
398392
onChange: (cbNodes) => {
399-
let maxH = 0;
400-
this.engine.nodes.forEach(n => { maxH = Math.max(maxH, n.y + n.h) });
401393
cbNodes.forEach(n => {
402394
const el = n.el;
403395
if (!el) return;
@@ -408,12 +400,12 @@ export class GridStack {
408400
this._writePosAttr(el, n);
409401
}
410402
});
411-
this._updateStyles(false, maxH); // false = don't recreate, just append if need be
403+
this._updateStyles();
412404
}
413405
});
414406

415407
// create initial global styles BEFORE loading children so resizeToContent margin can be calculated correctly
416-
this._updateStyles(false, 0);
408+
this._updateStyles();
417409

418410
if (opts.auto) {
419411
this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
@@ -894,7 +886,7 @@ export class GridStack {
894886
this.resizeToContentCheck();
895887

896888
if (update) {
897-
this._updateStyles(true); // true = force re-create for current # of rows
889+
this._updateStyles();
898890
}
899891
return this;
900892
}
@@ -1014,8 +1006,7 @@ export class GridStack {
10141006
} else {
10151007
this.el.parentNode.removeChild(this.el);
10161008
}
1017-
this._removeStylesheet();
1018-
delete this.parentGridNode?.subGrid;
1009+
if (this.parentGridNode) delete this.parentGridNode.subGrid;
10191010
delete this.parentGridNode;
10201011
delete this.opts;
10211012
delete this._placeholder?.gridstackNode;
@@ -1369,7 +1360,7 @@ export class GridStack {
13691360
// restore any sub-grid back
13701361
if (n.subGrid?.el) {
13711362
itemContent.appendChild(n.subGrid.el);
1372-
if (!n.subGrid.opts.styleInHead) n.subGrid._updateStyles(true); // force create
1363+
n.subGrid._updateStyles();
13731364
}
13741365
}
13751366
delete w.content;
@@ -1527,7 +1518,7 @@ export class GridStack {
15271518
this.opts.marginTop = this.opts.marginBottom = this.opts.marginLeft = this.opts.marginRight = undefined;
15281519
this._initMargin();
15291520

1530-
this._updateStyles(true); // true = force re-create
1521+
this._updateStyles();
15311522

15321523
return this;
15331524
}
@@ -1607,78 +1598,31 @@ export class GridStack {
16071598
return this;
16081599
}
16091600

1610-
/** @internal called to delete the current dynamic style sheet used for our layout */
1611-
protected _removeStylesheet(): GridStack {
1612-
1613-
if (this._styles) {
1614-
const styleLocation = this.opts.styleInHead ? undefined : this.el.parentNode as HTMLElement;
1615-
Utils.removeStylesheet(this._styleSheetClass, styleLocation);
1616-
delete this._styles;
1617-
}
1618-
return this;
1601+
private setVar(el: HTMLElement, varName: string, varValue: string) {
1602+
el.style.setProperty(varName, varValue);
16191603
}
16201604

1621-
/** @internal updated/create the CSS styles for row based layout and initial margin setting */
1622-
protected _updateStyles(forceUpdate = false, maxH?: number): GridStack {
1623-
// call to delete existing one if we change cellHeight / margin
1624-
if (forceUpdate) {
1625-
this._removeStylesheet();
1626-
}
1627-
1628-
if (maxH === undefined) maxH = this.getRow();
1605+
/**
1606+
* Updates the CSS variables (used in CSS and inline style) for row based layout and initial margin setting,
1607+
* Variables are scoped in DOM so they works for nested grids as well
1608+
* @internal
1609+
*/
1610+
protected _updateStyles(): GridStack {
16291611
this._updateContainerHeight();
16301612

16311613
// if user is telling us they will handle the CSS themselves by setting heights to 0. Do we need this opts really ??
16321614
if (this.opts.cellHeight === 0) {
16331615
return this;
16341616
}
16351617

1636-
const cellHeight = this.opts.cellHeight as number;
1637-
const cellHeightUnit = this.opts.cellHeightUnit;
1638-
const prefix = `.${this._styleSheetClass} > .${this.opts.itemClass}`;
1639-
1640-
// create one as needed
1641-
if (!this._styles) {
1642-
// insert style to parent (instead of 'head' by default) to support WebComponent
1643-
const styleLocation = this.opts.styleInHead ? undefined : this.el.parentNode as HTMLElement;
1644-
this._styles = Utils.createStylesheet(this._styleSheetClass, styleLocation, {
1645-
nonce: this.opts.nonce,
1646-
});
1647-
if (!this._styles) return this;
1648-
this._styles._max = 0;
1649-
1650-
// these are done once only
1651-
Utils.addCSSRule(this._styles, prefix, `height: ${cellHeight}${cellHeightUnit}`);
1652-
// content margins
1653-
const top: string = this.opts.marginTop + this.opts.marginUnit;
1654-
const bottom: string = this.opts.marginBottom + this.opts.marginUnit;
1655-
const right: string = this.opts.marginRight + this.opts.marginUnit;
1656-
const left: string = this.opts.marginLeft + this.opts.marginUnit;
1657-
const content = `${prefix} > .grid-stack-item-content`;
1658-
const placeholder = `.${this._styleSheetClass} > .grid-stack-placeholder > .placeholder-content`;
1659-
Utils.addCSSRule(this._styles, content, `top: ${top}; right: ${right}; bottom: ${bottom}; left: ${left};`);
1660-
Utils.addCSSRule(this._styles, placeholder, `top: ${top}; right: ${right}; bottom: ${bottom}; left: ${left};`);
1661-
// resize handles offset (to match margin)
1662-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-n`, `top: ${top};`);
1663-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-s`, `bottom: ${bottom}`);
1664-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-ne`, `right: ${right}; top: ${top}`);
1665-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-e`, `right: ${right}`);
1666-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-se`, `right: ${right}; bottom: ${bottom}`);
1667-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-nw`, `left: ${left}; top: ${top}`);
1668-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-w`, `left: ${left}`);
1669-
Utils.addCSSRule(this._styles, `${prefix} > .ui-resizable-sw`, `left: ${left}; bottom: ${bottom}`);
1670-
}
1671-
1672-
// now update the height specific fields
1673-
maxH = maxH || this._styles._max;
1674-
if (maxH > this._styles._max) {
1675-
const getHeight = (rows: number): string => (cellHeight * rows) + cellHeightUnit;
1676-
for (let i = this._styles._max + 1; i <= maxH; i++) { // start at 1
1677-
Utils.addCSSRule(this._styles, `${prefix}[gs-y="${i}"]`, `top: ${getHeight(i)}`);
1678-
Utils.addCSSRule(this._styles, `${prefix}[gs-h="${i + 1}"]`, `height: ${getHeight(i + 1)}`); // start at 2
1679-
}
1680-
this._styles._max = maxH;
1681-
}
1618+
// Set CSS var of cell height
1619+
this.setVar(this.el, "--gs-cell-height", `${this.opts.cellHeight}${this.opts.cellHeightUnit}`);
1620+
// content margins
1621+
this.setVar(this.el, "--gs-item-margin-top", `${this.opts.marginTop}${this.opts.marginUnit}`);
1622+
this.setVar(this.el, "--gs-item-margin-bottom", `${this.opts.marginBottom}${this.opts.marginUnit}`);
1623+
this.setVar(this.el, "--gs-item-margin-right", `${this.opts.marginRight}${this.opts.marginUnit}`);
1624+
this.setVar(this.el, "--gs-item-margin-left", `${this.opts.marginLeft}${this.opts.marginUnit}`);
1625+
16821626
return this;
16831627
}
16841628

@@ -1738,17 +1682,28 @@ export class GridStack {
17381682
return this;
17391683
}
17401684

1741-
/** @internal call to write position x,y,w,h attributes back to element */
1742-
protected _writePosAttr(el: HTMLElement, n: GridStackPosition): GridStack {
1685+
/**
1686+
* Call to write position x,y,w,h attributes back to element
1687+
* In addition, updates the inline top/height inline style as well
1688+
* @internal
1689+
*/
1690+
protected _writePosAttr(el: HTMLElement, n: GridStackNode): GridStack {
17431691
if (n.x !== undefined && n.x !== null) { el.setAttribute('gs-x', String(n.x)); }
17441692
if (n.y !== undefined && n.y !== null) { el.setAttribute('gs-y', String(n.y)); }
17451693
n.w > 1 ? el.setAttribute('gs-w', String(n.w)) : el.removeAttribute('gs-w');
17461694
n.h > 1 ? el.setAttribute('gs-h', String(n.h)) : el.removeAttribute('gs-h');
1695+
// Avoid overwriting the inline style of the element during drag/resize, but always update the placeholder
1696+
if ((!n._moving && !n._resizing) || this._placeholder === el) {
1697+
// Set inline style, refer CSS variables
1698+
el.style.top = `calc(${n.y} * var(--gs-cell-height))`;
1699+
// height is set to --gs-cell-height by default in the main CSS, so no need to set inline style when h = 1
1700+
el.style.height = n.h > 1 ? `calc(${n.h} * var(--gs-cell-height))` : undefined;
1701+
}
17471702
return this;
17481703
}
17491704

17501705
/** @internal call to write any default attributes back to element */
1751-
protected _writeAttr(el: HTMLElement, node: GridStackWidget): GridStack {
1706+
protected _writeAttr(el: HTMLElement, node: GridStackNode): GridStack {
17521707
if (!node) return this;
17531708
this._writePosAttr(el, node);
17541709

@@ -2400,7 +2355,7 @@ export class GridStack {
24002355
this.resizeToContentCheck(false, node);
24012356
if (subGrid) {
24022357
subGrid.parentGridNode = node;
2403-
if (!subGrid.opts.styleInHead) subGrid._updateStyles(true); // re-create sub-grid styles now that we've moved
2358+
subGrid._updateStyles(); // re-create sub-grid styles now that we've moved
24042359
}
24052360
this._updateContainerHeight();
24062361
}
@@ -2498,6 +2453,7 @@ export class GridStack {
24982453
this.placeholder.remove();
24992454
delete this.placeholder.gridstackNode;
25002455
delete node._moving;
2456+
delete node._resizing;
25012457
delete node._event;
25022458
delete node._lastTried;
25032459
const widthChanged = node.w !== node._orig.w;
@@ -2597,6 +2553,7 @@ export class GridStack {
25972553
node._lastUiPosition = ui.position;
25982554
node._prevYPix = ui.position.top;
25992555
node._moving = (event.type === 'dragstart'); // 'dropover' are not initially moving so they can go exactly where they enter (will push stuff out of the way)
2556+
node._resizing = (event.type === 'resizestart');
26002557
delete node._lastTried;
26012558

26022559
if (event.type === 'dropover' && node._temporaryRemoved) {

0 commit comments

Comments
 (0)