diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7e45dcf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: marcj diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..4f29079 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +.idea +tests diff --git a/css-element-queries.d.ts b/css-element-queries.d.ts new file mode 100644 index 0000000..3fe0706 --- /dev/null +++ b/css-element-queries.d.ts @@ -0,0 +1,2 @@ +export { ResizeSensor, ResizeSensorCallback, Size } from "./src/ResizeSensor"; +export { ElementQueries } from './src/ElementQueries'; \ No newline at end of file diff --git a/package.json b/package.json index 28c459f..964230f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "css-element-queries", - "version": "1.0.2", - "description": "CSS-Element-Queries Polyfill. proof-of-concept for high-speed element dimension/media queries in valid css.", + "version": "1.2.3", + "description": "CSS-Element-Queries Polyfill. Proof-of-concept for high-speed element dimension/media queries in valid css.", "main": "index.js", + "typings": "css-element-queries.d.ts", "directories": { "test": "test" }, diff --git a/src/ElementQueries.d.ts b/src/ElementQueries.d.ts new file mode 100644 index 0000000..00c5d93 --- /dev/null +++ b/src/ElementQueries.d.ts @@ -0,0 +1,14 @@ +export declare class ElementQueries { + /** + * Attaches to DOMLoadContent + */ + static listen(): void; + + /** + * Parses all available CSS and attach ResizeSensor to those elements which have rules attached. + * Make sure this is called after 'load' event, because CSS files are not ready when domReady is fired. + */ + static init(): void; +} + +export default ElementQueries; diff --git a/src/ElementQueries.js b/src/ElementQueries.js index 65dc3c3..4fe4298 100755 --- a/src/ElementQueries.js +++ b/src/ElementQueries.js @@ -170,13 +170,12 @@ if (!element.elementQueriesSetupInformation) { element.elementQueriesSetupInformation = new SetupInformation(element, id); } + if (!element.elementQueriesSensor) { element.elementQueriesSensor = new ResizeSensor(element, function () { element.elementQueriesSetupInformation.call(); }); } - - element.elementQueriesSetupInformation.call(); } /** @@ -324,7 +323,7 @@ } } - element.resizeSensor = new ResizeSensor(element, check); + element.resizeSensorInstance = new ResizeSensor(element, check); check(); } @@ -409,9 +408,11 @@ document.body.addEventListener(animationStart, function (e) { var element = e.target; - var styles = window.getComputedStyle(element, null); + var styles = element && window.getComputedStyle(element, null); + var animationName = styles && styles.getPropertyValue('animation-name'); + var requiresSetup = animationName && (-1 !== animationName.indexOf('element-queries')); - if (-1 !== styles.getPropertyValue('animation-name').indexOf('element-queries')) { + if (requiresSetup) { element.elementQueriesSensor = new ResizeSensor(element, function () { if (element.elementQueriesSetupInformation) { element.elementQueriesSetupInformation.call(); @@ -439,7 +440,7 @@ for (var i = 0, j = document.styleSheets.length; i < j; i++) { try { if (document.styleSheets[i].href && 0 === document.styleSheets[i].href.indexOf('file://')) { - console.log("CssElementQueries: unable to parse local css files, " + document.styleSheets[i].href); + console.warn("CssElementQueries: unable to parse local css files, " + document.styleSheets[i].href); } readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText); @@ -482,11 +483,11 @@ delete element.elementQueriesSetupInformation; delete element.elementQueriesSensor; - } else if (element.resizeSensor) { + } else if (element.resizeSensorInstance) { //responsive image - element.resizeSensor.detach(); - delete element.resizeSensor; + element.resizeSensorInstance.detach(); + delete element.resizeSensorInstance; } }; diff --git a/src/ResizeSensor.d.ts b/src/ResizeSensor.d.ts index fe5bb03..3fba8c6 100644 --- a/src/ResizeSensor.d.ts +++ b/src/ResizeSensor.d.ts @@ -1,6 +1,38 @@ -declare class ResizeSensor { - constructor(element: (Element | Element[]), callback: Function); - detach(callback: Function): void; +export declare interface Size { + width: number; + height: number; } -export = ResizeSensor; +export declare type ResizeSensorCallback = (size: Size) => void; + +export declare class ResizeSensor { + /** + * Creates a new resize sensor on given elements. The provided callback is called max 1 times per requestAnimationFrame and + * is called initially. + */ + constructor(element: Element | Element[], callback: ResizeSensorCallback); + + /** + * Removes the resize sensor, and stops listening to resize events. + */ + detach(callback?: ResizeSensorCallback): void; + + /** + * Resets the resize sensors, so for the next element resize is correctly detected. This is rare cases necessary + * when the resize sensor isn't initialised correctly or is in a broken state due to DOM modifications. + */ + reset(): void; + + /** + * Removes the resize sensor, and stops listening to resize events. + */ + static detach(element: Element | Element[], callback?: ResizeSensorCallback): void; + + /** + * Resets the resize sensors, so for the next element resize is correctly detected. This is rare cases necessary + * when the resize sensor isn't initialised correctly or is in a broken state due to DOM modifications. + */ + static reset(element: Element | Element[]): void; +} + +export default ResizeSensor; diff --git a/src/ResizeSensor.js b/src/ResizeSensor.js index c8fcfd6..9a25503 100755 --- a/src/ResizeSensor.js +++ b/src/ResizeSensor.js @@ -19,14 +19,28 @@ if (typeof window === "undefined") { return null; } + // https://github.com/Semantic-Org/Semantic-UI/issues/3855 + // https://github.com/marcj/css-element-queries/issues/257 + var globalWindow = typeof window != 'undefined' && window.Math == Math + ? window + : typeof self != 'undefined' && self.Math == Math + ? self + : Function('return this')(); // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor. // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and // would generate too many unnecessary events. - var requestAnimationFrame = window.requestAnimationFrame || - window.mozRequestAnimationFrame || - window.webkitRequestAnimationFrame || + var requestAnimationFrame = globalWindow.requestAnimationFrame || + globalWindow.mozRequestAnimationFrame || + globalWindow.webkitRequestAnimationFrame || function (fn) { - return window.setTimeout(fn, 20); + return globalWindow.setTimeout(fn, 20); + }; + + var cancelAnimationFrame = globalWindow.cancelAnimationFrame || + globalWindow.mozCancelAnimationFrame || + globalWindow.webkitCancelAnimationFrame || + function (timer) { + globalWindow.clearTimeout(timer); }; /** @@ -74,6 +88,18 @@ } } + /** + * Apply CSS styles to element. + * + * @param {HTMLElement} element + * @param {Object} style + */ + function setStyle(element, style) { + Object.keys(style).forEach(function(key) { + element.style[key] = style[key]; + }); + } + /** * Class for dimension change detection. * @@ -83,8 +109,8 @@ * @constructor */ var ResizeSensor = function(element, callback) { - - var observer; + //Is used when checking in reset() only for invisible elements + var lastAnimationFrameForInvisibleCheck = 0; /** * @@ -134,60 +160,88 @@ element.resizeSensor = document.createElement('div'); element.resizeSensor.dir = 'ltr'; element.resizeSensor.className = 'resize-sensor'; - var style = 'position: absolute; left: -10px; top: -10px; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden; max-width: 100%'; - var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;'; - - element.resizeSensor.style.cssText = style; - element.resizeSensor.innerHTML = - '
' + - '\ No newline at end of file + diff --git a/tests/late-trigger.html b/tests/late-trigger.html new file mode 100644 index 0000000..71a2b17 --- /dev/null +++ b/tests/late-trigger.html @@ -0,0 +1,48 @@ + +
+ + + + +
\ No newline at end of file
diff --git a/tests/mutation/app.js b/tests/mutation/app.js
new file mode 100644
index 0000000..2100612
--- /dev/null
+++ b/tests/mutation/app.js
@@ -0,0 +1,75 @@
+var __values = (this && this.__values) || function (o) {
+ var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
+ if (m) return m.call(o);
+ return {
+ next: function () {
+ if (o && i >= o.length) o = void 0;
+ return { value: o && o[i++], done: !o };
+ }
+ };
+};
+var e_1, _a, e_2, _b;
+var state = {
+ dragged: null
+};
+var i = 0;
+try {
+ for (var _c = __values(document.getElementsByClassName('drag')), _d = _c.next(); !_d.done; _d = _c.next()) {
+ var item = _d.value;
+ i++;
+ item.setAttribute('draggable', 'true');
+ item.setAttribute('id', 'drag-' + i);
+ (function (element) {
+ var title = 'Drag me #' + i;
+ element.setAttribute('data-label', title);
+ new ResizeSensor(element, function (size) {
+ element.setAttribute('data-label', title + " (" + size.width + "x" + size.height + ")");
+ });
+ })(item);
+ item.addEventListener('dragstart', function (event) {
+ state.dragged = event.target;
+ event.dataTransfer.setData('text', 'thanks firefox');
+ event.dataTransfer.dropEffect = 'move';
+ });
+ }
+}
+catch (e_1_1) { e_1 = { error: e_1_1 }; }
+finally {
+ try {
+ if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
+ }
+ finally { if (e_1) throw e_1.error; }
+}
+var _loop_1 = function (item) {
+ (function (element) {
+ item.addEventListener('drop', function (event) {
+ event.preventDefault();
+ item.classList.remove('drag-hover');
+ state.dragged.parentNode.removeChild(state.dragged);
+ element.appendChild(state.dragged);
+ state.dragged = null;
+ });
+ })(item);
+ item.addEventListener('dragleave', function (event) {
+ item.classList.remove('drag-hover');
+ });
+ item.addEventListener('dragover', function (event) {
+ item.classList.add('drag-hover');
+ });
+ item.addEventListener('dragover', function (event) {
+ event.preventDefault();
+ });
+};
+try {
+ for (var _e = __values(document.getElementsByClassName('container')), _f = _e.next(); !_f.done; _f = _e.next()) {
+ var item = _f.value;
+ _loop_1(item);
+ }
+}
+catch (e_2_1) { e_2 = { error: e_2_1 }; }
+finally {
+ try {
+ if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
+ }
+ finally { if (e_2) throw e_2.error; }
+}
diff --git a/tests/mutation/app.ts b/tests/mutation/app.ts
new file mode 100644
index 0000000..15974f7
--- /dev/null
+++ b/tests/mutation/app.ts
@@ -0,0 +1,56 @@
+declare const ResizeSensor;
+
+const state: {
+ dragged: Element
+} = {
+ dragged: null
+};
+
+let i = 0;
+
+for (const item of document.getElementsByClassName('drag')) {
+ i++;
+ item.setAttribute('draggable', 'true');
+ item.setAttribute('id', 'drag-' + i);
+
+ (element => {
+ const title = 'Drag me #' + i;
+ element.setAttribute('data-label', title);
+
+ new ResizeSensor(element, (size) => {
+ element.setAttribute('data-label', `${title} (${size.width}x${size.height})`);
+ });
+ })(item);
+
+ item.addEventListener('dragstart', (event: DragEvent) => {
+ state.dragged =