diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..44d414b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+.idea/
+.vcs
\ No newline at end of file
diff --git a/README.md b/README.md
index eb4e662..34944cd 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,11 @@
# rotate-table-headings
-It rotates your table headings so that you can fit more stuff into the table
+It rotates your table headings so that you can fit more stuff into the table.
+
+
+WORK IN PROGRESS, based on this [code](https://codepen.io/mkoryak/pen/yLNVQVE) but better ;)
+
+
+## Sponsored by [Ctrl O](https://ctrlo.com)
+
+Ctrl O provides simple and innovative products to help an organization's business processes.
+Linkspace, its flagship product, helps share information between teams and individuals, in a simple and effective manner.
diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 0000000..cd0a9d0
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,465 @@
+
+
+
+ Example
+
+
+
+
+
+
+
+
+
+
+
+
+I got rid of bootstrap styles, they were making it hard to figure out what things I needed to have.
+
+
+
+
+
+ Food1 DescriptionFood2 DescriptionFood3 DescriptionFood4 DescriptionFood5 DescriptionFood6 Description7!!!!!!!!!! !!!
+ Calories(kcal)
+ Protein (g)
+ Fat (g)
+ Carbohydrate (g)
+ Food Description and Portion Size
+ Calories(kcal)
+ Protein (g)
+ Fat (g)
+ Carbohydrate (g)
+ Food Description and Portion Size
+ Calories(kcal)
+ Protein (g)
+ Fat (g)
+ Carbohydrate (g)
+
+
+
+
+ APPLE PIE 1 PIEAPPLE PIE 1 PIEAPPLE PIE 1 PIEAPPLE PIE 1 PIEAPPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121 21212121
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+ APPLE PIE 1 PIECE
+ 405
+ 3
+ 18
+ 60
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+ APPLE PIE 1 PIE
+ 2420
+ 21
+ 105
+ 360
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/styles.css b/demo/styles.css
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/demo/styles.css
@@ -0,0 +1 @@
+
diff --git a/dist/rotate-table-headings-global.js b/dist/rotate-table-headings-global.js
new file mode 100644
index 0000000..edd8248
--- /dev/null
+++ b/dist/rotate-table-headings-global.js
@@ -0,0 +1,177 @@
+(function () {
+ 'use strict';
+
+ /**
+ * Inserts styles that are required by this library into the DOM.
+ */
+ function insertStyles () {
+ var STYLE_ID = 'rotate-table-headings-style';
+
+ if (document.getElementById(STYLE_ID)) {
+ return;
+ }
+
+ var styleElem = document.createElement('style');
+ styleElem.type = 'text/css';
+ styleElem.id = STYLE_ID;
+ document.head.appendChild(styleElem);
+ var sheet = styleElem.sheet;
+
+ var stringify = function (selector, rules) {
+ return (selector + " {\n" + (Object.keys(rules)
+ .map(function (key) { return ((key.split(/(?=[A-Z])/).join('-').toLowerCase()) + ": " + (rules[key]) + ";"); }).join('\n')) + "\n}");
+ };
+
+ var add = function (selector, rules) {
+ sheet.insertRule(stringify(selector, rules),
+ sheet.cssRules.length);
+ };
+
+ add("table.rotate-table-headings", {
+ borderCollapse: 'collapse',
+ borderSpacing: '0',
+ });
+ add("table.rotate-table-headings .cell-rotate", {
+ verticalAlign: 'bottom',
+ padding: '0 !important',
+ textAlign: 'left',
+ });
+ add("table.rotate-table-headings .cell-rotate .cell-positioner", {
+ position: 'relative',
+ });
+ add("table.rotate-table-headings .cell-rotate .cell-label", {
+ position: 'absolute',
+ bottom: '0',
+ textAlign: 'left',
+ left: '100%',
+ transformOrigin: 'bottom left',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ });
+ }
+
+ /**
+ * Rotates the contents of table cells by `angle` while also
+ * truncating their contents if it does not fit inside the max
+ * cell height.
+ *
+ * @param {!NodeList} tableCells NodeList of table cells: TDs
+ * or THs to operate on.
+ * @param {number} maxCellHeight Maximum height of the cell in pixels.
+ * Used for truncating the cell contents to fit. Use a very large
+ * number if you do not want to truncate cell text.
+ * @param {number=} angle Angle in degrees.
+ * @param {number=} textTruncateOffset Subtract this from the max width
+ * used to truncate cell text. It is needed because our forumula does
+ * not take font-size into account.
+ *
+ * @return {number} The horizontal width of the last cell's label. Since
+ * this cell will extend outside of the table, you will need this value
+ * to add padding to the table if the label goes off-screen.
+ */
+ function rotateTableHeadings(
+ tableCells,
+ maxCellHeight,
+ angle,
+ textTruncateOffset
+ ){
+ if ( angle === void 0 ) angle = 45;
+ if ( textTruncateOffset === void 0 ) textTruncateOffset = 10;
+
+ var solveRightTriangle = function (ref) {
+ var hypotenuse = ref.hypotenuse;
+ var height = ref.height;
+
+ var rads = (angle * Math.PI) / 180;
+ if (hypotenuse) {
+ // Solve for height.
+ return hypotenuse * Math.sin(rads);
+ } else {
+ var width = height / Math.tan(rads);
+ // Solve for hypotenuse.
+ return Math.sqrt(Math.pow(height, 2) + Math.pow(width, 2));
+ }
+ };
+
+ var crel = function (clazz, name) {
+ if ( name === void 0 ) name = 'div';
+
+ var el = document.createElement(name);
+ el.classList.add(clazz);
+ return el;
+ };
+ var empty = function (el) {
+ while(el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ return el;
+ };
+ var findParentTable = function (el) {
+ while (el) {
+ if (el.nodeName === 'TABLE') {
+ return el;
+ } else {
+ el = el.parentElement;
+ }
+ }
+ };
+ if(tableCells.length === 0){
+ return 0;
+ }
+
+ // This method ensures that styles are inserted only once.
+ insertStyles();
+
+ var table = findParentTable(tableCells[0]);
+ table.classList.add('rotate-table-headings');
+
+ var maxHeadingWidth =
+ solveRightTriangle({ height: maxCellHeight }) - textTruncateOffset;
+
+ var maxWidth = -1;
+ var lastCellWidth = 0;
+ var cellLabels = [];
+
+ for(var i = 0, list = tableCells; i < list.length; i += 1){
+ var cell = list[i];
+
+ cell.style.whiteSpace = 'nowrap';
+ var hypotenuse = cell.offsetWidth;
+ var text = cell.innerText;
+ var height = solveRightTriangle({hypotenuse: hypotenuse});
+ var idealHeight = Math.min(height, maxCellHeight);
+ maxWidth = Math.max(hypotenuse, maxWidth);
+
+ cell.style.height = idealHeight + 'px';
+ cell.setAttribute('aria-label', text);
+ var cellLabel = crel('cell-label');
+ cellLabel.innerText = text;
+ cellLabel.setAttribute('label', text);
+ cellLabel.style.transform = "rotate(" + (360 - angle) + "deg)";
+ cellLabel.style.maxWidth = maxHeadingWidth + 'px';
+ var positioner = crel('cell-positioner');
+ positioner.appendChild(cellLabel);
+ empty(cell).appendChild(positioner);
+ cell.classList.add('cell-rotate');
+ cellLabels.push(cellLabel);
+
+ if(cell === tableCells[tableCells.length - 1]) {
+ lastCellWidth = height / Math.tan(angle * Math.PI / 180);
+ }
+ }
+ for(var i$1 = 0, list$1 = cellLabels; i$1 < list$1.length; i$1 += 1){
+ var cellLabel$1 = list$1[i$1];
+
+ cellLabel$1.style.width = maxWidth + 'px';
+
+ // Push the cell down to line the border up with the columns.
+ // Equal to border-bottom-width.
+ cellLabel$1.style.marginBottom = "-" + (getComputedStyle(cellLabel$1).borderBottomWidth);
+ }
+ return lastCellWidth - textTruncateOffset;
+ }
+
+ window.rotateTableHeadings = rotateTableHeadings;
+
+}());
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..e333d74
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,254 @@
+{
+ "name": "rotate-table-headings",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@rollup/plugin-buble": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-buble/-/plugin-buble-0.21.1.tgz",
+ "integrity": "sha512-Tmd4V95cVyGTwh7qc9ZNkg53E/isFY4q/sqZK7mSyGajYp9Wb0gbJyZWAzYlg9kZxEHmwCDlvcHDcn56SpOCCQ==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.0.4",
+ "@types/buble": "^0.19.2",
+ "buble": "^0.19.8"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz",
+ "integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^1.0.1"
+ }
+ },
+ "@types/buble": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@types/buble/-/buble-0.19.2.tgz",
+ "integrity": "sha512-uUD8zIfXMKThmFkahTXDGI3CthFH1kMg2dOm3KLi4GlC5cbARA64bEcUMbbWdWdE73eoc/iBB9PiTMqH0dNS2Q==",
+ "dev": true,
+ "requires": {
+ "magic-string": "^0.25.0"
+ }
+ },
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "acorn-dynamic-import": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
+ "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+ "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "buble": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/buble/-/buble-0.19.8.tgz",
+ "integrity": "sha512-IoGZzrUTY5fKXVkgGHw3QeXFMUNBFv+9l8a4QJKG1JhG3nCMHTdEX1DCOg8568E2Q9qvAQIiSokv6Jsgx8p2cA==",
+ "dev": true,
+ "requires": {
+ "acorn": "^6.1.1",
+ "acorn-dynamic-import": "^4.0.0",
+ "acorn-jsx": "^5.0.1",
+ "chalk": "^2.4.2",
+ "magic-string": "^0.25.3",
+ "minimist": "^1.2.0",
+ "os-homedir": "^2.0.0",
+ "regexpu-core": "^4.5.4"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
+ "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+ "dev": true,
+ "optional": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ },
+ "magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "os-homedir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-2.0.0.tgz",
+ "integrity": "sha512-saRNz0DSC5C/I++gFIaJTXoFJMRwiP5zHar5vV3xQ2TkgEw6hDCcU5F272JjUylpiVgBrZNQHnfjkLabTfb92Q==",
+ "dev": true
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "dev": true
+ },
+ "regenerate-unicode-properties": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+ "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0"
+ }
+ },
+ "regexpu-core": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
+ "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.2.0",
+ "regjsgen": "^0.5.1",
+ "regjsparser": "^0.6.4",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.2.0"
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
+ "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
+ "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ },
+ "rollup": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.2.tgz",
+ "integrity": "sha512-p66+fbfaUUOGE84sHXAOgfeaYQMslgAazoQMp//nlR519R61213EPFgrMZa48j31jNacJwexSAR1Q8V/BwGKBA==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.1.2"
+ }
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+ "dev": true
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+ "dev": true,
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^1.0.4",
+ "unicode-property-aliases-ecmascript": "^1.0.4"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+ "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
+ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
+ "dev": true
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..56e02a3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "rotate-table-headings",
+ "version": "0.1.0",
+ "description": "Rotates the table headings",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "./node_modules/rollup/dist/bin/rollup -c --watch"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mkoryak/rotate-table-headings.git"
+ },
+ "keywords": [
+ "table",
+ "heading",
+ "th",
+ "rotate",
+ "transform"
+ ],
+ "author": "Misha Koryak",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/mkoryak/rotate-table-headings/issues"
+ },
+ "homepage": "https://github.com/mkoryak/rotate-table-headings#readme",
+ "devDependencies": {
+ "@rollup/plugin-buble": "^0.21.1",
+ "rollup": "^2.3.2"
+ }
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..7d6e39b
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,13 @@
+import buble from '@rollup/plugin-buble';
+
+export default {
+ input: 'src/global.js',
+ output: [{
+ file: 'dist/rotate-table-headings-global.js',
+ format: 'iife',
+ }],
+ compact: true,
+ plugins: [buble({transforms: {
+ dangerousForOf: true,
+ }})]
+};
\ No newline at end of file
diff --git a/src/global.js b/src/global.js
new file mode 100644
index 0000000..5d801a1
--- /dev/null
+++ b/src/global.js
@@ -0,0 +1,5 @@
+import rotateTableHeadings from './rotate-table-headings';
+
+// Rollup entry point to generate an IIFE with a global on window.
+window.rotateTableHeadings = rotateTableHeadings;
+
diff --git a/src/insert-styles.js b/src/insert-styles.js
new file mode 100644
index 0000000..381fd5c
--- /dev/null
+++ b/src/insert-styles.js
@@ -0,0 +1,51 @@
+/**
+ * Inserts styles that are required by this library into the DOM.
+ */
+export default function () {
+ const STYLE_ID = 'rotate-table-headings-style';
+
+ if (document.getElementById(STYLE_ID)) {
+ return;
+ }
+
+ const styleElem = document.createElement('style');
+ styleElem.type = 'text/css';
+ styleElem.id = STYLE_ID;
+ document.head.appendChild(styleElem);
+ const sheet = styleElem.sheet;
+
+ const stringify = (selector, rules) => {
+ return `${ selector } {\n${
+ Object.keys(rules)
+ .map(key => `${key.split(/(?=[A-Z])/).join('-').toLowerCase()}: ${ rules[key] };`).join('\n')
+ }\n}`;
+ };
+
+ const add = (selector, rules) => {
+ sheet.insertRule(stringify(selector, rules),
+ sheet.cssRules.length);
+ };
+
+ add("table.rotate-table-headings", {
+ borderCollapse: 'collapse',
+ borderSpacing: '0',
+ });
+ add("table.rotate-table-headings .cell-rotate", {
+ verticalAlign: 'bottom',
+ padding: '0 !important',
+ textAlign: 'left',
+ });
+ add("table.rotate-table-headings .cell-rotate .cell-positioner", {
+ position: 'relative',
+ });
+ add("table.rotate-table-headings .cell-rotate .cell-label", {
+ position: 'absolute',
+ bottom: '0',
+ textAlign: 'left',
+ left: '100%',
+ transformOrigin: 'bottom left',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ });
+}
\ No newline at end of file
diff --git a/src/jquery.js b/src/jquery.js
new file mode 100644
index 0000000..5874b79
--- /dev/null
+++ b/src/jquery.js
@@ -0,0 +1,5 @@
+import rotateTableHeadings from './rotate-table-headings';
+
+// TODO: Rollup entry point to generate a jquery wrapper with a global on window.
+
+
diff --git a/src/rotate-table-headings.js b/src/rotate-table-headings.js
new file mode 100644
index 0000000..46e35d4
--- /dev/null
+++ b/src/rotate-table-headings.js
@@ -0,0 +1,110 @@
+import insertStyles from './insert-styles';
+
+/**
+ * Rotates the contents of table cells by `angle` while also
+ * truncating their contents if it does not fit inside the max
+ * cell height.
+ *
+ * @param {!NodeList} tableCells NodeList of table cells: TDs
+ * or THs to operate on.
+ * @param {number} maxCellHeight Maximum height of the cell in pixels.
+ * Used for truncating the cell contents to fit. Use a very large
+ * number if you do not want to truncate cell text.
+ * @param {number=} angle Angle in degrees.
+ * @param {number=} textTruncateOffset Subtract this from the max width
+ * used to truncate cell text. It is needed because our forumula does
+ * not take font-size into account.
+ *
+ * @return {number} The horizontal width of the last cell's label. Since
+ * this cell will extend outside of the table, you will need this value
+ * to add padding to the table if the label goes off-screen.
+ */
+export default function(
+ tableCells,
+ maxCellHeight,
+ angle = 45,
+ textTruncateOffset = 10
+){
+ const solveRightTriangle = ({ hypotenuse, height }) => {
+ const rads = (angle * Math.PI) / 180;
+ if (hypotenuse) {
+ // Solve for height.
+ return hypotenuse * Math.sin(rads);
+ } else {
+ const width = height / Math.tan(rads);
+ // Solve for hypotenuse.
+ return Math.sqrt(Math.pow(height, 2) + Math.pow(width, 2));
+ }
+ };
+
+ const crel = (clazz, name = 'div') => {
+ const el = document.createElement(name);
+ el.classList.add(clazz);
+ return el;
+ };
+ const empty = (el) => {
+ while(el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ return el;
+ };
+ const findParentTable = (el) => {
+ while (el) {
+ if (el.nodeName === 'TABLE') {
+ return el;
+ } else {
+ el = el.parentElement;
+ }
+ }
+ };
+ if(tableCells.length === 0){
+ return 0;
+ }
+
+ // This method ensures that styles are inserted only once.
+ insertStyles();
+
+ const table = findParentTable(tableCells[0]);
+ table.classList.add('rotate-table-headings');
+
+ const maxHeadingWidth =
+ solveRightTriangle({ height: maxCellHeight }) - textTruncateOffset;
+
+ let maxWidth = -1;
+ let lastCellWidth = 0;
+ const cellLabels = [];
+
+ for(const cell of tableCells){
+ cell.style.whiteSpace = 'nowrap';
+ const hypotenuse = cell.offsetWidth;
+ const text = cell.innerText;
+ const height = solveRightTriangle({hypotenuse});
+ const idealHeight = Math.min(height, maxCellHeight);
+ maxWidth = Math.max(hypotenuse, maxWidth);
+
+ cell.style.height = idealHeight + 'px';
+ cell.setAttribute('aria-label', text);
+ const cellLabel = crel('cell-label');
+ cellLabel.innerText = text;
+ cellLabel.setAttribute('label', text);
+ cellLabel.style.transform = `rotate(${360 - angle}deg)`;
+ cellLabel.style.maxWidth = maxHeadingWidth + 'px';
+ const positioner = crel('cell-positioner');
+ positioner.appendChild(cellLabel);
+ empty(cell).appendChild(positioner);
+ cell.classList.add('cell-rotate');
+ cellLabels.push(cellLabel);
+
+ if(cell === tableCells[tableCells.length - 1]) {
+ lastCellWidth = height / Math.tan(angle * Math.PI / 180);
+ }
+ }
+ for(const cellLabel of cellLabels){
+ cellLabel.style.width = maxWidth + 'px';
+
+ // Push the cell down to line the border up with the columns.
+ // Equal to border-bottom-width.
+ cellLabel.style.marginBottom = `-${getComputedStyle(cellLabel).borderBottomWidth}`;
+ }
+ return lastCellWidth - textTruncateOffset;
+};
\ No newline at end of file