This commit is contained in:
root
2023-03-29 15:20:05 +00:00
parent 5ec489e0e0
commit a0bb8f2d1e
25468 changed files with 3063105 additions and 28 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Alex Reardon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,290 @@
# `css-box-model` 📦
Get accurate and well named [CSS Box Model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model) information about a [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element).
[![Build Status](https://travis-ci.org/alexreardon/css-box-model.svg?branch=master)](https://travis-ci.org/alexreardon/css-box-model)
[![npm](https://img.shields.io/npm/v/css-box-model.svg)](https://www.npmjs.com/package/css-box-model)
[![dependencies](https://david-dm.org/alexreardon/css-box-model.svg)](https://david-dm.org/alexreardon/css-box-model)
[![Downloads per month](https://img.shields.io/npm/dm/css-box-model.svg)](https://www.npmjs.com/package/css-box-model)
[![min](https://img.shields.io/bundlephobia/min/css-box-model.svg)](https://www.npmjs.com/package/css-box-model)
[![minzip](https://img.shields.io/bundlephobia/minzip/css-box-model.svg)](https://www.npmjs.com/package/css-box-model)
Any time you are using [`Element.getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) you might want to consider using `css-box-model` instead to get more detailed box model information.
## Usage
```js
// @flow
import { getBox } from 'css-box-model';
const el: HTMLElement = document.getElementById('foo');
const box: BoxModel = getBox(el);
// profit
```
## Installation
```bash
## yarn
yarn add css-box-model
# npm
npm install css-box-model --save
```
## The [CSS Box Model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model)
![the box model](https://user-images.githubusercontent.com/2182637/46847224-f8a23e80-ce2e-11e8-80d6-0ca62a1871a7.png)
| Box type | Composition |
| ----------- | ----------------------------------- |
| Margin box | margin + border + padding + content |
| Border box | border + padding + content |
| Padding box | padding + content |
| Content box | content |
This our returned `BoxModel`:
```js
export type BoxModel = {|
// content + padding + border + margin
marginBox: Rect,
// content + padding + border
borderBox: Rect,
// content + padding
paddingBox: Rect,
// content
contentBox: Rect,
// for your own consumption
border: Spacing,
padding: Spacing,
margin: Spacing,
|};
// Supporting types
// This is an extension of DOMRect and ClientRect
export type Rect = {|
// ClientRect
top: number,
right: number,
bottom: number,
left: number,
width: number,
height: number,
// DOMRect
x: number,
y: number,
// Rect
center: Position,
|};
export type Position = {|
x: number,
y: number,
|};
export type Spacing = {
top: number,
right: number,
bottom: number,
left: number,
};
```
## API
### `getBox`
> (el: HTMLElement) => BoxModel
Use `getBox` to return the box model for an element
### `withScroll`
> `(original: BoxModel, scroll?: Position = getWindowScroll()) => BoxModel`
This is useful if you want to know the box model for an element relative to a page
```js
const el: HTMLElement = document.getElementById('app');
const box: BoxModel = getBox(el);
const withScroll: BoxModel = withScroll(box);
```
You are welcome to pass in your own `scroll`. By default we we use the window scroll:
```js
const getWindowScroll = (): Position => ({
x: window.pageXOffset,
y: window.pageYOffset,
});
```
### `calculateBox`
> `(borderBox: AnyRectType, styles: CSSStyleDeclaration) => BoxModel`
This will do the box model calculations without needing to read from the DOM. This is useful if you have already got a `ClientRect` / `DOMRect` and a `CSSStyleDeclaration` as then we can skip computing these values.
```js
const el: HTMLElement = document.getElementById('app');
const borderBox: ClientRect = el.getBoundingClientRect();
const styles: CSSStyleDeclaration = window.getComputedStyles(el);
const box: BoxModel = calculateBox(borderBox, styles);
```
**`AnyRectType`** allows for simple interoperability with any rect type
```js
type AnyRectType = ClientRect | DOMRect | Rect | Spacing;
```
### `createBox`
> `({ borderBox, margin, border, padding }: CreateBoxArgs) => BoxModel`
Allows you to create a `BoxModel` by passing in a `Rect` like shape (`AnyRectType`) and optionally your own `margin`, `border` and or `padding`.
```js
type CreateBoxArgs = {|
borderBox: AnyRectType,
margin?: Spacing,
border?: Spacing,
padding?: Spacing,
|};
```
```js
const borderBox: Spacing = {
top: 10,
right: 100,
left: 20,
bottom: 80,
};
const padding: Spacing = {
top: 10,
right: 20,
left: 20,
bottom: 10,
};
const box: BoxModel = createBox({ borderBox, padding });
```
## Utility API
> Functions to help you interact with the objects we provide
### `getRect`
> `(spacing: AnyRectType) => Rect`
Given any `Rect` like shape, return a `Rect`. Accepts any object that has `top`, `right`, `bottom` and `right` (eg `ClientRect`, `DOMRect`);
```js
const spacing: Spacing = {
top: 0,
right: 100,
bottom: 50,
left: 50,
};
const rect: Rect = getRect(spacing);
console.log(rect);
/*
{
top: 0,
right: 100,
bottom: 50,
left: 50,
width: 100,
height: 50,
x: 0,
y: 0,
center: { x: 50, y: 50 },
}
*/
```
### `expand`
Used to expand a `Spacing`
```js
(target: Spacing, expandBy: Spacing) => Spacing;
```
```js
const original: Spacing = {
top: 10,
left: 11,
right: 21,
bottom: 22,
};
const expandBy: Spacing = {
top: 1,
left: 2,
right: 3,
bottom: 4,
};
const expanded: Spacing = expand(original, expandBy);
console.log(expanded);
/*
{
// pulled back
top: 8,
left: 8
// pushed forward
bottom: 22,
right: 22,
}
*/
```
### `shrink`
Used to shrink a `Spacing`
```js
(target: Spacing, shrinkBy: Spacing) => Spacing;
```
```js
const original: Spacing = {
top: 10,
left: 10,
right: 20,
bottom: 20,
};
const shrinkBy: Spacing = {
top: 2,
left: 2,
right: 2,
bottom: 2,
};
const smaller: Spacing = shrink(original, shrinkBy);
console.log(smaller);
/*
{
// pushed forward
top: 12,
left: 12
// pulled back
bottom: 18,
right: 18,
}
*/
```

View File

@@ -0,0 +1,165 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var invariant = _interopDefault(require('tiny-invariant'));
var getRect = function getRect(_ref) {
var top = _ref.top,
right = _ref.right,
bottom = _ref.bottom,
left = _ref.left;
var width = right - left;
var height = bottom - top;
var rect = {
top: top,
right: right,
bottom: bottom,
left: left,
width: width,
height: height,
x: left,
y: top,
center: {
x: (right + left) / 2,
y: (bottom + top) / 2
}
};
return rect;
};
var expand = function expand(target, expandBy) {
return {
top: target.top - expandBy.top,
left: target.left - expandBy.left,
bottom: target.bottom + expandBy.bottom,
right: target.right + expandBy.right
};
};
var shrink = function shrink(target, shrinkBy) {
return {
top: target.top + shrinkBy.top,
left: target.left + shrinkBy.left,
bottom: target.bottom - shrinkBy.bottom,
right: target.right - shrinkBy.right
};
};
var shift = function shift(target, shiftBy) {
return {
top: target.top + shiftBy.y,
left: target.left + shiftBy.x,
bottom: target.bottom + shiftBy.y,
right: target.right + shiftBy.x
};
};
var noSpacing = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
var createBox = function createBox(_ref2) {
var borderBox = _ref2.borderBox,
_ref2$margin = _ref2.margin,
margin = _ref2$margin === void 0 ? noSpacing : _ref2$margin,
_ref2$border = _ref2.border,
border = _ref2$border === void 0 ? noSpacing : _ref2$border,
_ref2$padding = _ref2.padding,
padding = _ref2$padding === void 0 ? noSpacing : _ref2$padding;
var marginBox = getRect(expand(borderBox, margin));
var paddingBox = getRect(shrink(borderBox, border));
var contentBox = getRect(shrink(paddingBox, padding));
return {
marginBox: marginBox,
borderBox: getRect(borderBox),
paddingBox: paddingBox,
contentBox: contentBox,
margin: margin,
border: border,
padding: padding
};
};
var parse = function parse(raw) {
var value = raw.slice(0, -2);
var suffix = raw.slice(-2);
if (suffix !== 'px') {
return 0;
}
var result = Number(value);
!!isNaN(result) ? process.env.NODE_ENV !== "production" ? invariant(false, "Could not parse value [raw: " + raw + ", without suffix: " + value + "]") : invariant(false) : void 0;
return result;
};
var getWindowScroll = function getWindowScroll() {
return {
x: window.pageXOffset,
y: window.pageYOffset
};
};
var offset = function offset(original, change) {
var borderBox = original.borderBox,
border = original.border,
margin = original.margin,
padding = original.padding;
var shifted = shift(borderBox, change);
return createBox({
borderBox: shifted,
border: border,
margin: margin,
padding: padding
});
};
var withScroll = function withScroll(original, scroll) {
if (scroll === void 0) {
scroll = getWindowScroll();
}
return offset(original, scroll);
};
var calculateBox = function calculateBox(borderBox, styles) {
var margin = {
top: parse(styles.marginTop),
right: parse(styles.marginRight),
bottom: parse(styles.marginBottom),
left: parse(styles.marginLeft)
};
var padding = {
top: parse(styles.paddingTop),
right: parse(styles.paddingRight),
bottom: parse(styles.paddingBottom),
left: parse(styles.paddingLeft)
};
var border = {
top: parse(styles.borderTopWidth),
right: parse(styles.borderRightWidth),
bottom: parse(styles.borderBottomWidth),
left: parse(styles.borderLeftWidth)
};
return createBox({
borderBox: borderBox,
margin: margin,
padding: padding,
border: border
});
};
var getBox = function getBox(el) {
var borderBox = el.getBoundingClientRect();
var styles = window.getComputedStyle(el);
return calculateBox(borderBox, styles);
};
exports.calculateBox = calculateBox;
exports.createBox = createBox;
exports.expand = expand;
exports.getBox = getBox;
exports.getRect = getRect;
exports.offset = offset;
exports.shrink = shrink;
exports.withScroll = withScroll;

View File

@@ -0,0 +1,3 @@
// @flow
export * from '../src';

View File

@@ -0,0 +1,152 @@
import invariant from 'tiny-invariant';
var getRect = function getRect(_ref) {
var top = _ref.top,
right = _ref.right,
bottom = _ref.bottom,
left = _ref.left;
var width = right - left;
var height = bottom - top;
var rect = {
top: top,
right: right,
bottom: bottom,
left: left,
width: width,
height: height,
x: left,
y: top,
center: {
x: (right + left) / 2,
y: (bottom + top) / 2
}
};
return rect;
};
var expand = function expand(target, expandBy) {
return {
top: target.top - expandBy.top,
left: target.left - expandBy.left,
bottom: target.bottom + expandBy.bottom,
right: target.right + expandBy.right
};
};
var shrink = function shrink(target, shrinkBy) {
return {
top: target.top + shrinkBy.top,
left: target.left + shrinkBy.left,
bottom: target.bottom - shrinkBy.bottom,
right: target.right - shrinkBy.right
};
};
var shift = function shift(target, shiftBy) {
return {
top: target.top + shiftBy.y,
left: target.left + shiftBy.x,
bottom: target.bottom + shiftBy.y,
right: target.right + shiftBy.x
};
};
var noSpacing = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
var createBox = function createBox(_ref2) {
var borderBox = _ref2.borderBox,
_ref2$margin = _ref2.margin,
margin = _ref2$margin === void 0 ? noSpacing : _ref2$margin,
_ref2$border = _ref2.border,
border = _ref2$border === void 0 ? noSpacing : _ref2$border,
_ref2$padding = _ref2.padding,
padding = _ref2$padding === void 0 ? noSpacing : _ref2$padding;
var marginBox = getRect(expand(borderBox, margin));
var paddingBox = getRect(shrink(borderBox, border));
var contentBox = getRect(shrink(paddingBox, padding));
return {
marginBox: marginBox,
borderBox: getRect(borderBox),
paddingBox: paddingBox,
contentBox: contentBox,
margin: margin,
border: border,
padding: padding
};
};
var parse = function parse(raw) {
var value = raw.slice(0, -2);
var suffix = raw.slice(-2);
if (suffix !== 'px') {
return 0;
}
var result = Number(value);
!!isNaN(result) ? process.env.NODE_ENV !== "production" ? invariant(false, "Could not parse value [raw: " + raw + ", without suffix: " + value + "]") : invariant(false) : void 0;
return result;
};
var getWindowScroll = function getWindowScroll() {
return {
x: window.pageXOffset,
y: window.pageYOffset
};
};
var offset = function offset(original, change) {
var borderBox = original.borderBox,
border = original.border,
margin = original.margin,
padding = original.padding;
var shifted = shift(borderBox, change);
return createBox({
borderBox: shifted,
border: border,
margin: margin,
padding: padding
});
};
var withScroll = function withScroll(original, scroll) {
if (scroll === void 0) {
scroll = getWindowScroll();
}
return offset(original, scroll);
};
var calculateBox = function calculateBox(borderBox, styles) {
var margin = {
top: parse(styles.marginTop),
right: parse(styles.marginRight),
bottom: parse(styles.marginBottom),
left: parse(styles.marginLeft)
};
var padding = {
top: parse(styles.paddingTop),
right: parse(styles.paddingRight),
bottom: parse(styles.paddingBottom),
left: parse(styles.paddingLeft)
};
var border = {
top: parse(styles.borderTopWidth),
right: parse(styles.borderRightWidth),
bottom: parse(styles.borderBottomWidth),
left: parse(styles.borderLeftWidth)
};
return createBox({
borderBox: borderBox,
margin: margin,
padding: padding,
border: border
});
};
var getBox = function getBox(el) {
var borderBox = el.getBoundingClientRect();
var styles = window.getComputedStyle(el);
return calculateBox(borderBox, styles);
};
export { calculateBox, createBox, expand, getBox, getRect, offset, shrink, withScroll };

View File

@@ -0,0 +1,179 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.cssBoxModel = {}));
}(this, function (exports) { 'use strict';
var prefix = 'Invariant failed';
function invariant(condition, message) {
if (condition) {
return;
}
{
throw new Error(prefix + ": " + (message || ''));
}
}
var getRect = function getRect(_ref) {
var top = _ref.top,
right = _ref.right,
bottom = _ref.bottom,
left = _ref.left;
var width = right - left;
var height = bottom - top;
var rect = {
top: top,
right: right,
bottom: bottom,
left: left,
width: width,
height: height,
x: left,
y: top,
center: {
x: (right + left) / 2,
y: (bottom + top) / 2
}
};
return rect;
};
var expand = function expand(target, expandBy) {
return {
top: target.top - expandBy.top,
left: target.left - expandBy.left,
bottom: target.bottom + expandBy.bottom,
right: target.right + expandBy.right
};
};
var shrink = function shrink(target, shrinkBy) {
return {
top: target.top + shrinkBy.top,
left: target.left + shrinkBy.left,
bottom: target.bottom - shrinkBy.bottom,
right: target.right - shrinkBy.right
};
};
var shift = function shift(target, shiftBy) {
return {
top: target.top + shiftBy.y,
left: target.left + shiftBy.x,
bottom: target.bottom + shiftBy.y,
right: target.right + shiftBy.x
};
};
var noSpacing = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
var createBox = function createBox(_ref2) {
var borderBox = _ref2.borderBox,
_ref2$margin = _ref2.margin,
margin = _ref2$margin === void 0 ? noSpacing : _ref2$margin,
_ref2$border = _ref2.border,
border = _ref2$border === void 0 ? noSpacing : _ref2$border,
_ref2$padding = _ref2.padding,
padding = _ref2$padding === void 0 ? noSpacing : _ref2$padding;
var marginBox = getRect(expand(borderBox, margin));
var paddingBox = getRect(shrink(borderBox, border));
var contentBox = getRect(shrink(paddingBox, padding));
return {
marginBox: marginBox,
borderBox: getRect(borderBox),
paddingBox: paddingBox,
contentBox: contentBox,
margin: margin,
border: border,
padding: padding
};
};
var parse = function parse(raw) {
var value = raw.slice(0, -2);
var suffix = raw.slice(-2);
if (suffix !== 'px') {
return 0;
}
var result = Number(value);
!!isNaN(result) ? invariant(false, "Could not parse value [raw: " + raw + ", without suffix: " + value + "]") : void 0;
return result;
};
var getWindowScroll = function getWindowScroll() {
return {
x: window.pageXOffset,
y: window.pageYOffset
};
};
var offset = function offset(original, change) {
var borderBox = original.borderBox,
border = original.border,
margin = original.margin,
padding = original.padding;
var shifted = shift(borderBox, change);
return createBox({
borderBox: shifted,
border: border,
margin: margin,
padding: padding
});
};
var withScroll = function withScroll(original, scroll) {
if (scroll === void 0) {
scroll = getWindowScroll();
}
return offset(original, scroll);
};
var calculateBox = function calculateBox(borderBox, styles) {
var margin = {
top: parse(styles.marginTop),
right: parse(styles.marginRight),
bottom: parse(styles.marginBottom),
left: parse(styles.marginLeft)
};
var padding = {
top: parse(styles.paddingTop),
right: parse(styles.paddingRight),
bottom: parse(styles.paddingBottom),
left: parse(styles.paddingLeft)
};
var border = {
top: parse(styles.borderTopWidth),
right: parse(styles.borderRightWidth),
bottom: parse(styles.borderBottomWidth),
left: parse(styles.borderLeftWidth)
};
return createBox({
borderBox: borderBox,
margin: margin,
padding: padding,
border: border
});
};
var getBox = function getBox(el) {
var borderBox = el.getBoundingClientRect();
var styles = window.getComputedStyle(el);
return calculateBox(borderBox, styles);
};
exports.calculateBox = calculateBox;
exports.createBox = createBox;
exports.expand = expand;
exports.getBox = getBox;
exports.getRect = getRect;
exports.offset = offset;
exports.shrink = shrink;
exports.withScroll = withScroll;
Object.defineProperty(exports, '__esModule', { value: true });
}));

View File

@@ -0,0 +1 @@
!function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports):"function"==typeof define&&define.amd?define(["exports"],o):o((t=t||self).cssBoxModel={})}(this,function(t){"use strict";var o="Invariant failed";var r=function(t){var o=t.top,r=t.right,e=t.bottom,i=t.left;return{top:o,right:r,bottom:e,left:i,width:r-i,height:e-o,x:i,y:o,center:{x:(r+i)/2,y:(e+o)/2}}},e=function(t,o){return{top:t.top-o.top,left:t.left-o.left,bottom:t.bottom+o.bottom,right:t.right+o.right}},i=function(t,o){return{top:t.top+o.top,left:t.left+o.left,bottom:t.bottom-o.bottom,right:t.right-o.right}},n={top:0,right:0,bottom:0,left:0},d=function(t){var o=t.borderBox,d=t.margin,f=void 0===d?n:d,g=t.border,a=void 0===g?n:g,p=t.padding,u=void 0===p?n:p,b=r(e(o,f)),m=r(i(o,a)),h=r(i(m,u));return{marginBox:b,borderBox:r(o),paddingBox:m,contentBox:h,margin:f,border:a,padding:u}},f=function(t){var r=t.slice(0,-2);if("px"!==t.slice(-2))return 0;var e=Number(r);return isNaN(e)&&function(t,r){if(!t)throw new Error(o)}(!1),e},g=function(t,o){var r,e,i=t.borderBox,n=t.border,f=t.margin,g=t.padding,a=(e=o,{top:(r=i).top+e.y,left:r.left+e.x,bottom:r.bottom+e.y,right:r.right+e.x});return d({borderBox:a,border:n,margin:f,padding:g})},a=function(t,o){var r={top:f(o.marginTop),right:f(o.marginRight),bottom:f(o.marginBottom),left:f(o.marginLeft)},e={top:f(o.paddingTop),right:f(o.paddingRight),bottom:f(o.paddingBottom),left:f(o.paddingLeft)},i={top:f(o.borderTopWidth),right:f(o.borderRightWidth),bottom:f(o.borderBottomWidth),left:f(o.borderLeftWidth)};return d({borderBox:t,margin:r,padding:e,border:i})};t.calculateBox=a,t.createBox=d,t.expand=e,t.getBox=function(t){var o=t.getBoundingClientRect(),r=window.getComputedStyle(t);return a(o,r)},t.getRect=r,t.offset=g,t.shrink=i,t.withScroll=function(t,o){return void 0===o&&(o={x:window.pageXOffset,y:window.pageYOffset}),g(t,o)},Object.defineProperty(t,"__esModule",{value:!0})});

View File

@@ -0,0 +1,61 @@
{
"name": "css-box-model",
"version": "1.2.1",
"description": "Get accurate and well named css box model information about an Element 📦",
"author": "Alex Reardon <alexreardon@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alexreardon/css-box-model.git"
},
"bugs": {
"url": "https://github.com/alexreardon/css-box-model/issues"
},
"keywords": [
"css",
"box model",
"css box model",
"getBoundingClientRect",
"DOMRect",
"ClientRect",
"Rect",
"Spacing",
"DOM"
],
"files": [
"/dist",
"/src"
],
"main": "dist/css-box-model.cjs.js",
"module": "dist/css-box-model.esm.js",
"types": "src/index.d.ts",
"sideEffects": false,
"scripts": {
"test": "yarn jest",
"lint": "yarn prettier --debug-check src/** test/**",
"validate": "yarn lint && flow check",
"build:clean": "rimraf dist",
"build:flow": "echo \"// @flow\n\nexport * from '../src';\" > dist/css-box-model.cjs.js.flow",
"build:dist": "yarn rollup --config rollup.config.js",
"build": "yarn build:clean && yarn build:dist && yarn build:flow",
"prepublishOnly": "yarn build"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-flow": "^7.0.0",
"babel-plugin-dev-expression": "^0.2.2",
"flow-bin": "0.106.1",
"jest": "^24.9.0",
"prettier": "1.18.2",
"rimraf": "^3.0.0",
"rollup": "^1.20.2",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^5.1.1"
},
"dependencies": {
"tiny-invariant": "^1.0.6"
}
}

View File

@@ -0,0 +1,59 @@
declare module 'css-box-model' {
export type Position = {
x: number;
y: number;
};
export type Rect = {
top: number;
right: number;
bottom: number;
left: number;
width: number;
height: number;
x: number;
y: number;
center: Position;
};
export type BoxModel = {
marginBox: Rect;
borderBox: Rect;
paddingBox: Rect;
contentBox: Rect;
border: Spacing;
padding: Spacing;
margin: Spacing;
};
export type AnyRectType = ClientRect | DOMRect | Rect | Spacing;
export type Spacing = {
top: number;
right: number;
bottom: number;
left: number;
};
export const getRect: ({ top, right, bottom, left }: Spacing) => Rect;
export const expand: (target: Spacing, expandBy: Spacing) => Spacing;
export const shrink: (target: Spacing, shrinkBy: Spacing) => Spacing;
type CreateBoxArgs = {
borderBox: AnyRectType;
margin?: Spacing;
border?: Spacing;
padding?: Spacing;
};
export const createBox: (boxArgs: CreateBoxArgs) => BoxModel;
export const offset: (original: BoxModel, change: Position) => BoxModel;
export const withScroll: (original: BoxModel, scroll?: Position) => BoxModel;
export const calculateBox: (
borderBox: AnyRectType,
styles: CSSStyleDeclaration,
) => BoxModel;
export const getBox: (el: Element) => BoxModel;
}

View File

@@ -0,0 +1,243 @@
// @flow
import invariant from 'tiny-invariant';
// # The CSS box model
// > https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model
//
// ------------------------------------
// | MARGIN | (marginBox)
// | ------------------------------ |
// | | BORDER | | (borderBox)
// | | ------------------------ | |
// | | | PADDING | | | (paddingBox) - not used by anything really
// | | | ------------------ | | |
// | | | | CONTENT | | | | (contentBox)
// | | | | | | | |
// | | | | | | | |
// | | | | | | | |
// | | | ------------------ | | |
// | | | | | |
// | | ------------------------ | |
// | | | |
// | ------------------------------ |
// | |
// | ----------------------------------|
export type Position = {|
x: number,
y: number,
|};
export type Rect = {|
top: number,
right: number,
bottom: number,
left: number,
width: number,
height: number,
x: number,
y: number,
center: Position,
|};
export type BoxModel = {|
// content + padding + border + margin
marginBox: Rect,
// content + padding + border
borderBox: Rect,
// content + padding
paddingBox: Rect,
// content
contentBox: Rect,
// for your own consumption
border: Spacing,
padding: Spacing,
margin: Spacing,
|};
export type AnyRectType = ClientRect | DOMRect | Rect | Spacing;
export type Spacing = {
top: number,
right: number,
bottom: number,
left: number,
};
export const getRect = ({ top, right, bottom, left }: Spacing): Rect => {
const width: number = right - left;
const height: number = bottom - top;
const rect: Rect = {
// ClientRect
top,
right,
bottom,
left,
width,
height,
// DOMRect
x: left,
y: top,
// Rect
center: {
x: (right + left) / 2,
y: (bottom + top) / 2,
},
};
return rect;
};
export const expand = (target: Spacing, expandBy: Spacing): Spacing => ({
// pulling back to increase size
top: target.top - expandBy.top,
left: target.left - expandBy.left,
// pushing forward to increase size
bottom: target.bottom + expandBy.bottom,
right: target.right + expandBy.right,
});
export const shrink = (target: Spacing, shrinkBy: Spacing): Spacing => ({
// pushing forward to decrease size
top: target.top + shrinkBy.top,
left: target.left + shrinkBy.left,
// pulling backwards to decrease size
bottom: target.bottom - shrinkBy.bottom,
right: target.right - shrinkBy.right,
});
const shift = (target: Spacing, shiftBy: Position): Spacing => ({
top: target.top + shiftBy.y,
left: target.left + shiftBy.x,
bottom: target.bottom + shiftBy.y,
right: target.right + shiftBy.x,
});
const noSpacing: Spacing = {
top: 0,
right: 0,
bottom: 0,
left: 0,
};
type CreateBoxArgs = {|
borderBox: AnyRectType,
margin?: Spacing,
border?: Spacing,
padding?: Spacing,
|};
export const createBox = ({
borderBox,
margin = noSpacing,
border = noSpacing,
padding = noSpacing,
}: CreateBoxArgs): BoxModel => {
// marginBox = borderBox + margin
const marginBox: Rect = getRect(expand(borderBox, margin));
// borderBox = borderBox - padding
const paddingBox: Rect = getRect(shrink(borderBox, border));
// contentBox = paddingBox - padding
const contentBox: Rect = getRect(shrink(paddingBox, padding));
return {
marginBox,
borderBox: getRect(borderBox),
paddingBox,
contentBox,
margin,
border,
padding,
};
};
// Computed spacing styles will always be in pixels
// https://codepen.io/alexreardon/pen/OZyqXe
const parse = (raw: string): number => {
const value: string = raw.slice(0, -2);
const suffix: string = raw.slice(-2);
// ## Used values vs computed values
// `getComputedStyle` will return the * used values * if the
// element has `display: none` and the *computed values* otherwise
// *used values* can include 'rem' etc.
// Rather than throwing we are returning `0`.
// Given that the element is _not visible_ it takes up no visible space and so `0` is correct
// ## `jsdom`
// The `raw` value can also not be populated in jsdom
if (suffix !== 'px') {
return 0;
}
const result: number = Number(value);
invariant(
!isNaN(result),
`Could not parse value [raw: ${raw}, without suffix: ${value}]`,
);
return result;
};
const getWindowScroll = (): Position => ({
x: window.pageXOffset,
y: window.pageYOffset,
});
export const offset = (original: BoxModel, change: Position): BoxModel => {
const { borderBox, border, margin, padding } = original;
const shifted: Spacing = shift(borderBox, change);
return createBox({
borderBox: shifted,
border,
margin,
padding,
});
};
export const withScroll = (
original: BoxModel,
scroll?: Position = getWindowScroll(),
): BoxModel => offset(original, scroll);
// Exposing this function directly for performance. If you have already computed these things
// then you can simply pass them in
export const calculateBox = (
borderBox: AnyRectType,
styles: CSSStyleDeclaration,
): BoxModel => {
const margin: Spacing = {
top: parse(styles.marginTop),
right: parse(styles.marginRight),
bottom: parse(styles.marginBottom),
left: parse(styles.marginLeft),
};
const padding: Spacing = {
top: parse(styles.paddingTop),
right: parse(styles.paddingRight),
bottom: parse(styles.paddingBottom),
left: parse(styles.paddingLeft),
};
const border: Spacing = {
top: parse(styles.borderTopWidth),
right: parse(styles.borderRightWidth),
bottom: parse(styles.borderBottomWidth),
left: parse(styles.borderLeftWidth),
};
return createBox({
borderBox,
margin,
padding,
border,
});
};
export const getBox = (el: Element): BoxModel => {
// getBoundingClientRect always returns the borderBox
const borderBox: ClientRect = el.getBoundingClientRect();
const styles: CSSStyleDeclaration = window.getComputedStyle(el);
return calculateBox(borderBox, styles);
};