├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── package.json ├── rollup.config.js ├── src ├── Divider.html ├── Pane.html ├── Subdivide.html ├── constants.js ├── elements.js └── utils.js └── test ├── public └── index.html ├── runner.js └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | yarn-error.log 5 | package-lock.json 6 | index.mjs 7 | index.js 8 | test/public/bundle.js 9 | !test/src/index.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | 5 | env: 6 | global: 7 | - BUILD_TIMEOUT=10000 8 | 9 | before_install: 10 | - sudo apt-get update 11 | - sudo apt-get install -y xsel -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # svelte-subdivide changelog 2 | 3 | ## 2.2.0 4 | 5 | * Fire `open`, `close` and `layout` events ([#20](https://github.com/sveltejs/svelte-subdivide/issues/20)) 6 | * Indicate when pane will be closed via custom cursor 7 | 8 | ## 2.1.0 9 | 10 | * Expose `layout` property for implementing save and restore ([#17](https://github.com/sveltejs/svelte-subdivide/pull/17)) 11 | * Use unique IDs, rather than sequential ones ([#19](https://github.com/sveltejs/svelte-subdivide/pull/19)) 12 | 13 | ## 2.0.0 14 | 15 | * Replace `spacing` parameter with `thickness` and `padding` 16 | * Add visual indicators for dragging and splitting 17 | * Prevent text selection during dragging 18 | * Use ctrl key in non-Mac environments 19 | * Fix broken relationships when dragging twice from left/top edge of a pane 20 | 21 | ## 1.0.0 22 | 23 | * First release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Rich Harris 2 | 3 | Permission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions: 4 | 5 | This license, or a link to its text, must be included with all copies of the software and any derivative works. 6 | 7 | Any modification to the software submitted to the authors may be incorporated into the software under the terms of this license. 8 | 9 | The software is provided "as is", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte-subdivide ([demo](https://svelte.technology/repl?version=2.6.3&gist=972edea66f74521601771be19e192c72)) 2 | 3 | A component for building Blender-style layouts in Svelte apps. 4 | 5 | ![subdivide-2](https://user-images.githubusercontent.com/1162160/40279920-696b12e6-5c19-11e8-8861-6bdb071441d5.gif) 6 | 7 | 8 | ## Installation 9 | 10 | ```bash 11 | yarn add @sveltejs/svelte-subdivide 12 | ``` 13 | 14 | 15 | ## Usage 16 | 17 | ```html 18 | 19 | 20 | 34 | ``` 35 | 36 | The component constructor you supply to `` will be instantiated for each cell of your layout. Typically, it would be a component that allows the user to select from a variety of different panes. 37 | 38 | ```html 39 | 40 |
41 | {#if selected} 42 | 43 | {:else} 44 | {#each options as option} 45 | 48 | {/each} 49 | {/if} 50 |
51 | ``` 52 | 53 | Note that this component uses CSS variables, and may therefore behave strangely in IE. 54 | 55 | 56 | ## Parameters 57 | 58 | You can specify the following parameters: 59 | 60 | * `thickness` — the thickness of the divider, as a CSS length. Defaults to zero 61 | * `padding` — the amount of space either side of the divider that will respond to mouse events. Larger values make it easier to resize panes, but makes it harder to split them. Defaults to 6px 62 | * `color` — the color of the divider, if `thickness` is larger than zero. Defaults to white 63 | 64 | ```html 65 | 71 | ``` 72 | 73 | 74 | ## Save/restore 75 | 76 | You can also specify a `layout` parameter, to implement save and restore: 77 | 78 | ```html 79 | 80 | 81 | 102 | ``` 103 | 104 | 105 | ## Events 106 | 107 | You can listen for `open`, `close` and `layout` events. Each event is an object with a `layout` property and, in the case of `open` and `close`, a `pane` property indicating which pane was opened or closed. 108 | 109 | ```html 110 | 116 | ``` 117 | 118 | 119 | ## Configuring webpack 120 | 121 | If you're using webpack with [svelte-loader](https://github.com/sveltejs/svelte-loader), make sure that you add `"svelte"` to [`resolve.mainFields`](https://webpack.js.org/configuration/resolve/#resolve-mainfields) in your webpack config. This ensures that webpack imports the uncompiled component (`src/index.html`) rather than the compiled version (`index.mjs`) — this is more efficient. 122 | 123 | If you're using Rollup with [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte), this will happen automatically. 124 | 125 | 126 | ## Credits 127 | 128 | Essential inspiration was provided by [philholden/subdivide](https://github.com/philholden/subdivide) — thanks Phil! 129 | 130 | 131 | ## License 132 | 133 | [LIL](LICENSE) 134 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | version: "{build}" 4 | 5 | clone_depth: 10 6 | 7 | init: 8 | - git config --global core.autocrlf false 9 | 10 | environment: 11 | matrix: 12 | # node.js 13 | - nodejs_version: 8 14 | 15 | install: 16 | - ps: Install-Product node $env:nodejs_version 17 | - npm install 18 | 19 | build: off 20 | 21 | test_script: 22 | - node --version && npm --version 23 | - npm test 24 | 25 | matrix: 26 | fast_finish: false 27 | 28 | # cache: 29 | # - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache 30 | # - node_modules -> package.json # local npm modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/svelte-subdivide", 3 | "version": "2.2.0", 4 | "description": "A component for building Blender-style layouts in Svelte apps", 5 | "svelte": "src/Subdivide.html", 6 | "module": "index.mjs", 7 | "main": "index.js", 8 | "scripts": { 9 | "build": "rollup -c", 10 | "dev": "rollup -cw", 11 | "prepublishOnly": "npm test", 12 | "test": "node test/runner.js", 13 | "test:browser": "npm run build && serve test/public", 14 | "pretest": "npm run build" 15 | }, 16 | "devDependencies": { 17 | "port-authority": "^1.0.3", 18 | "puppeteer": "^1.2.0", 19 | "rollup": "^0.59.4", 20 | "rollup-plugin-commonjs": "^9.1.0", 21 | "rollup-plugin-node-resolve": "^3.3.0", 22 | "rollup-plugin-svelte": "^4.1.0", 23 | "serve-handler": "^3.0.0", 24 | "svelte": "^2.7.1", 25 | "tap-diff": "^0.1.1", 26 | "tap-dot": "^1.0.5", 27 | "tape-modern": "^1.0.0" 28 | }, 29 | "repository": "https://github.com/sveltejs/svelte-subdivide", 30 | "author": "Rich Harris", 31 | "license": "LIL", 32 | "keywords": [ 33 | "svelte" 34 | ], 35 | "files": [ 36 | "src", 37 | "index.mjs", 38 | "index.js" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import pkg from './package.json'; 5 | 6 | export default [ 7 | { 8 | input: 'src/Subdivide.html', 9 | output: [ 10 | { file: pkg.module, 'format': 'es' }, 11 | { file: pkg.main, 'format': 'umd', name: 'Subdivide' } 12 | ], 13 | plugins: [ 14 | resolve(), 15 | svelte() 16 | ] 17 | }, 18 | 19 | // tests 20 | { 21 | input: 'test/src/index.js', 22 | output: { 23 | file: 'test/public/bundle.js', 24 | format: 'iife' 25 | }, 26 | plugins: [ 27 | resolve(), 28 | commonjs(), 29 | svelte() 30 | ] 31 | } 32 | ]; -------------------------------------------------------------------------------- /src/Divider.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 38 | 39 | -------------------------------------------------------------------------------- /src/Pane.html: -------------------------------------------------------------------------------- 1 |
14 |
15 | 16 |
17 |
18 | 19 | 34 | 35 | -------------------------------------------------------------------------------- /src/Subdivide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
9 | {#each _panes as pane (pane.id)} 10 | 11 | {/each} 12 | 13 | {#each _dividers as divider (divider.id)} 14 | 15 | {/each} 16 | 17 | {#if _dragging} 18 |
24 | {/if} 25 |
26 |
27 | 28 | 66 | 67 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const NORTH = 'NORTH'; 2 | export const SOUTH = 'SOUTH'; 3 | export const EAST = 'EAST'; 4 | export const WEST = 'WEST'; 5 | 6 | export const IS_MAC = navigator.platform === 'MacIntel'; 7 | export const KEYCODE = IS_MAC ? 91 : 17; // Cmd : Ctrl -------------------------------------------------------------------------------- /src/elements.js: -------------------------------------------------------------------------------- 1 | class Rect { 2 | constructor(pos, size, prev, next) { 3 | this.pos = pos; 4 | this.size = size; 5 | 6 | this.prev = prev; 7 | this.next = next; 8 | 9 | this.parent = null; 10 | } 11 | 12 | bounds(rect) { 13 | const width = rect.right - rect.left; 14 | const height = rect.bottom - rect.top; 15 | 16 | return { 17 | left: rect.left + width * this.getLeft(), 18 | top: rect.top + height * this.getTop(), 19 | width: width * this.getWidth(), 20 | height: height * this.getHeight() 21 | }; 22 | } 23 | 24 | getPos(row) { 25 | let pos = 0; 26 | 27 | let node = this; 28 | while (node.parent) { 29 | if (node.parent.row === row) pos = node.pos + (node.size * pos); 30 | node = node.parent; 31 | } 32 | 33 | return pos; 34 | } 35 | 36 | getLeft() { 37 | return this.getPos(true); 38 | } 39 | 40 | getTop() { 41 | return this.getPos(false); 42 | } 43 | 44 | getSize(row) { 45 | let size = 1; 46 | 47 | let node = this; 48 | while (node.parent) { 49 | if (node.parent.row === row) size *= node.size; 50 | node = node.parent; 51 | } 52 | 53 | return size; 54 | } 55 | 56 | getWidth() { 57 | return this.getSize(true); 58 | } 59 | 60 | getHeight() { 61 | return this.getSize(false); 62 | } 63 | 64 | setRange(a, b) { 65 | this.pos = a; 66 | this.size = (b - a); 67 | } 68 | } 69 | 70 | export class Pane extends Rect { 71 | constructor(id, { pos, size, prev, next }) { 72 | super(pos, size, prev, next); 73 | this.id = id; 74 | } 75 | 76 | destroy(panes, dividers) { 77 | const index = panes.indexOf(this); 78 | if (index === -1) throw new Error(`Unexpected error`); 79 | panes.splice(index, 1); 80 | } 81 | 82 | toJSON() { 83 | return { 84 | id: this.id, 85 | type: 'pane', 86 | pos: this.pos, 87 | size: this.size, 88 | prev: this.prev && this.prev.id, 89 | next: this.next && this.next.id 90 | }; 91 | } 92 | } 93 | 94 | export class Group extends Rect { 95 | constructor(row, { pos, size, prev, next }) { 96 | super(pos, size, prev, next); 97 | 98 | this.row = row; 99 | this.children = []; 100 | this.dividers = []; 101 | } 102 | 103 | addChild(child) { 104 | this.children.push(child); 105 | child.parent = this; 106 | } 107 | 108 | replaceChild(child, replacement) { 109 | const index = this.children.indexOf(child); 110 | if (index === -1) throw new Error(`Unexpected error`); 111 | this.children[index] = replacement; 112 | 113 | replacement.parent = this; 114 | 115 | child.parent = replacement; 116 | replacement.children.push(child); 117 | } 118 | 119 | destroy(panes, dividers) { 120 | let i = this.children.length; 121 | while (i--) this.children[i].destroy(panes, dividers); 122 | 123 | i = this.dividers.length; 124 | while (i--) this.dividers[i].destroy(dividers); 125 | } 126 | 127 | toJSON() { 128 | return { 129 | type: 'group', 130 | row: this.row, 131 | pos: this.pos, 132 | size: this.size, 133 | prev: this.prev && this.prev.id, 134 | next: this.next && this.next.id, 135 | children: this.children.map(child => child.toJSON()) 136 | }; 137 | } 138 | } 139 | 140 | export class Divider { 141 | constructor({ id, type, group, position, prev, next }) { 142 | this.id = id; 143 | this.type = type; 144 | this.parent = group; 145 | this.position = position; 146 | this.prev = prev; 147 | this.next = next; 148 | 149 | group.dividers.push(this); 150 | } 151 | 152 | destroy(dividers) { 153 | const index = dividers.indexOf(this); 154 | if (index === -1) throw new Error(`Unexpected error`); 155 | dividers.splice(index, 1); 156 | } 157 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function clamp(num, min, max) { 2 | if (num < min) return min; 3 | if (num > max) return max; 4 | return num; 5 | } 6 | 7 | export function removeFromArray(array, item) { 8 | const index = array.indexOf(item); 9 | if (index === -1) throw new Error('Unexpected error'); 10 | array.splice(index, 1); 11 | } 12 | 13 | export function getId() { 14 | return Math.random().toString(36).slice(2); 15 | } -------------------------------------------------------------------------------- /test/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | svelte-virtual-list tests 4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /test/runner.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const ports = require('port-authority'); 3 | const handler = require('serve-handler'); 4 | const puppeteer = require('puppeteer'); 5 | 6 | async function go() { 7 | const port = await ports.find(1234); 8 | console.log(`found available port: ${port}`); 9 | 10 | const server = http.createServer((req, res) => { 11 | handler(req, res, { 12 | public: 'test/public' 13 | }); 14 | }); 15 | 16 | server.listen(port); 17 | 18 | await ports.wait(port).catch(() => {}); // workaround windows gremlins 19 | 20 | const browser = await puppeteer.launch({args: ['--no-sandbox']}); 21 | const page = await browser.newPage(); 22 | 23 | page.on('console', msg => { 24 | console[msg.type()](msg.text()); 25 | }); 26 | 27 | await page.goto(`http://localhost:${port}`); 28 | 29 | await page.evaluate(() => done); 30 | await browser.close(); 31 | server.close(); 32 | } 33 | 34 | go(); -------------------------------------------------------------------------------- /test/src/index.js: -------------------------------------------------------------------------------- 1 | import svelte from 'svelte'; 2 | import Subdivide from '../..'; 3 | import { assert, test, done } from 'tape-modern'; 4 | 5 | // setup 6 | const target = document.querySelector('main'); 7 | 8 | function indent(node, spaces) { 9 | if (node.childNodes.length === 0) return; 10 | 11 | if (node.childNodes.length > 1 || node.childNodes[0].nodeType !== 3) { 12 | const first = node.childNodes[0]; 13 | const last = node.childNodes[node.childNodes.length - 1]; 14 | 15 | const head = `\n${spaces} `; 16 | const tail = `\n${spaces}`; 17 | 18 | if (first.nodeType === 3) { 19 | first.data = `${head}${first.data}`; 20 | } else { 21 | node.insertBefore(document.createTextNode(head), first); 22 | } 23 | 24 | if (last.nodeType === 3) { 25 | last.data = `${last.data}${tail}`; 26 | } else { 27 | node.appendChild(document.createTextNode(tail)); 28 | } 29 | 30 | let lastType = null; 31 | for (let i = 0; i < node.childNodes.length; i += 1) { 32 | const child = node.childNodes[i]; 33 | if (child.nodeType === 1) { 34 | indent(node.childNodes[i], `${spaces} `); 35 | 36 | if (lastType === 1) { 37 | node.insertBefore(document.createTextNode(head), child); 38 | i += 1; 39 | } 40 | } 41 | 42 | lastType = child.nodeType; 43 | } 44 | } 45 | } 46 | 47 | function normalize(html) { 48 | const div = document.createElement('div'); 49 | div.innerHTML = html 50 | .replace(//g, '') 51 | .replace(/svelte-ref-\w+=""/g, '') 52 | .replace(/\s*svelte-\w+\s*/g, '') 53 | .replace(/class=""/g, '') 54 | .replace(/>\s+/g, '>') 55 | .replace(/\s+ { 64 | assert.equal(normalize(a), normalize(b)); 65 | }; 66 | 67 | function mousedown(node, clientX, clientY, metaKey) { 68 | node.dispatchEvent(new MouseEvent('mousedown', { 69 | metaKey, 70 | clientX, 71 | clientY 72 | })); 73 | } 74 | 75 | function mousemove(node, clientX, clientY) { 76 | node.dispatchEvent(new MouseEvent('mousemove', { 77 | clientX, 78 | clientY 79 | })); 80 | } 81 | 82 | function mouseup(node, clientX, clientY) { 83 | node.dispatchEvent(new MouseEvent('mouseup', { 84 | clientX, 85 | clientY 86 | })); 87 | } 88 | 89 | function init(layout) { 90 | const Item = svelte.create(` 91 | {index} 92 | 93 | 104 | `); 105 | 106 | return new Subdivide({ 107 | target, 108 | data: { 109 | component: Item, 110 | thickness: '0px', 111 | layout 112 | } 113 | }); 114 | } 115 | 116 | // tests 117 | test('creates a single pane element that fills the target', t => { 118 | const layout = init(); 119 | 120 | t.htmlEqual(target.innerHTML, ` 121 |
122 |
123 |
124 |
125 | 0 126 |
127 |
128 |
129 |
130 | `); 131 | 132 | layout.destroy(); 133 | }); 134 | 135 | test('creates a new pane', t => { 136 | const layout = init(); 137 | 138 | const { container } = layout.refs; 139 | const pane = document.querySelector('.pane'); 140 | 141 | mousedown(pane, 5, 100, true); 142 | mouseup(document.querySelector('.overlay'), 200, 100); 143 | 144 | t.htmlEqual(target.innerHTML, ` 145 |
146 |
147 |
148 |
149 | 0 150 |
151 |
152 | 153 |
154 |
155 | 1 156 |
157 |
158 | 159 |
160 |
161 |
162 | `); 163 | 164 | layout.destroy(); 165 | }); 166 | 167 | test('preserves correct pane/divider relationships (a)', t => { 168 | const layout = init(); 169 | const { container } = layout.refs; 170 | 171 | const { left, top, right, bottom } = container.getBoundingClientRect(); 172 | const width = right - left; 173 | const height = bottom - top; 174 | const cx = left + width / 2; 175 | const cy = top + height / 2; 176 | 177 | let pane = document.querySelector('.pane'); 178 | 179 | // split from the left edge 180 | mousedown(pane, 5, 100, true); 181 | mouseup(document.querySelector('.overlay'), 200, 100); 182 | 183 | // split from the right edge 184 | mousedown(pane, 995, 100, true); 185 | mouseup(document.querySelector('.overlay'), 800, 100); 186 | 187 | // split from the top middle 188 | mousedown(pane, 500, 5, true); 189 | mouseup(document.querySelector('.overlay'), 500, 500); 190 | 191 | // split the lower middle chunk from the left 192 | mousedown(pane, 205, 750, true); 193 | mouseup(document.querySelector('.overlay'), 500, 750); 194 | 195 | t.htmlEqual(target.innerHTML, ` 196 |
197 |
198 |
199 |
200 | 0 201 |
202 |
203 | 204 |
205 |
206 | 1 207 |
208 |
209 | 210 |
211 |
212 | 2 213 |
214 |
215 | 216 |
217 |
218 | 3 219 |
220 |
221 | 222 |
223 |
224 | 4 225 |
226 |
227 | 228 |
229 |
230 |
231 |
232 |
233 |
234 | `); 235 | 236 | // now, check that dragging the leftmost vertical slider updates the 237 | // layout how we expect 238 | let divider = target.querySelectorAll('.divider')[0]; 239 | mousedown(divider, 200, 500); 240 | mouseup(document.querySelector('.overlay'), 100, 500); 241 | 242 | t.htmlEqual(target.innerHTML, ` 243 |
244 |
245 |
246 |
247 | 0 248 |
249 |
250 | 251 |
252 |
253 | 1 254 |
255 |
256 | 257 |
258 |
259 | 2 260 |
261 |
262 | 263 |
264 |
265 | 3 266 |
267 |
268 | 269 |
270 |
271 | 4 272 |
273 |
274 | 275 |
276 |
277 |
278 |
279 |
280 |
281 | `); 282 | 283 | // split the top middle pane 284 | pane = target.querySelectorAll('.pane')[3]; 285 | mousedown(pane, 105, 250, true); 286 | mouseup(document.querySelector('.overlay'), 500, 250); 287 | 288 | // drag the rightmost vertical divider 289 | divider = target.querySelectorAll('.divider')[1]; 290 | mousedown(divider, 800, 500); 291 | mouseup(document.querySelector('.overlay'), 900, 500); 292 | 293 | // TODO tweak the numbers so we get nice round (testable) numbers 294 | // t.htmlEqual(target.innerHTML, ` 295 | //
296 | //
297 | //
298 | // 0 299 | //
300 | //
301 | 302 | //
303 | //
304 | // 1 305 | //
306 | //
307 | 308 | //
309 | //
310 | // 2 311 | //
312 | //
313 | 314 | //
315 | //
316 | // 3 317 | //
318 | //
319 | 320 | //
321 | //
322 | // 4 323 | //
324 | //
325 | 326 | //
327 | //
328 | // 5 329 | //
330 | //
331 | 332 | //
333 | 334 | //
335 | 336 | //
337 | 338 | //
339 | 340 | //
341 | //
342 | // `); 343 | 344 | layout.destroy(); 345 | }); 346 | 347 | test('preserves correct pane/divider relationships (b)', t => { 348 | const layout = init(); 349 | const { container } = layout.refs; 350 | 351 | const { left, top, right, bottom } = container.getBoundingClientRect(); 352 | const width = right - left; 353 | const height = bottom - top; 354 | const cx = left + width / 2; 355 | const cy = top + height / 2; 356 | 357 | let pane = document.querySelector('.pane'); 358 | 359 | // split from the left edge 360 | mousedown(pane, 5, 100, true); 361 | mouseup(document.querySelector('.overlay'), 700, 100); 362 | 363 | // split from the new split 364 | pane = target.querySelectorAll('.pane')[1]; 365 | mousedown(pane, 695, 100, true); 366 | mouseup(document.querySelector('.overlay'), 300, 100); 367 | 368 | t.htmlEqual(target.innerHTML, ` 369 |
370 |
371 |
372 |
373 | 0 374 |
375 |
376 | 377 |
378 |
379 | 1 380 |
381 |
382 | 383 |
384 |
385 | 2 386 |
387 |
388 | 389 |
390 |
391 |
392 |
393 | `); 394 | 395 | // now, check that dragging the leftmost vertical slider updates the 396 | // layout how we expect 397 | let divider = target.querySelectorAll('.divider')[0]; 398 | mousedown(divider, 700, 500); 399 | mouseup(document.querySelector('.overlay'), 500, 500); 400 | 401 | t.htmlEqual(target.innerHTML, ` 402 |
403 |
404 |
405 |
406 | 0 407 |
408 |
409 | 410 |
411 |
412 | 1 413 |
414 |
415 | 416 |
417 |
418 | 2 419 |
420 |
421 | 422 |
423 |
424 |
425 |
426 | `); 427 | 428 | layout.destroy(); 429 | }); 430 | 431 | test('preserves correct pane/divider relationships (c)', t => { 432 | const layout = init(); 433 | 434 | let pane = document.querySelector('.pane'); 435 | 436 | // split from the left edge 437 | mousedown(pane, 5, 100, true); 438 | mouseup(document.querySelector('.overlay'), 250, 100); 439 | 440 | // split from the new split 441 | pane = target.querySelectorAll('.pane')[0]; 442 | mousedown(pane, 255, 100, true); 443 | mouseup(document.querySelector('.overlay'), 500, 100); 444 | 445 | t.htmlEqual(target.innerHTML, ` 446 |
447 |
448 |
449 |
450 | 0 451 |
452 |
453 | 454 |
455 |
456 | 1 457 |
458 |
459 | 460 |
461 |
462 | 2 463 |
464 |
465 | 466 |
467 |
468 |
469 |
470 | `); 471 | 472 | // now, check that dragging the leftmost vertical slider updates the 473 | // layout how we expect 474 | let divider = target.querySelectorAll('.divider')[0]; 475 | mousedown(divider, 250, 500); 476 | mouseup(document.querySelector('.overlay'), 350, 500); 477 | 478 | t.htmlEqual(target.innerHTML, ` 479 |
480 |
481 |
482 |
483 | 0 484 |
485 |
486 | 487 |
488 |
489 | 1 490 |
491 |
492 | 493 |
494 |
495 | 2 496 |
497 |
498 | 499 |
500 |
501 |
502 |
503 | `); 504 | 505 | layout.destroy(); 506 | }); 507 | 508 | test('destroys panes', t => { 509 | const layout = init(); 510 | 511 | const { container } = layout.refs; 512 | const pane = document.querySelector('.pane'); 513 | 514 | mousedown(pane, 5, 100, true); 515 | mouseup(document.querySelector('.overlay'), 200, 100); 516 | 517 | t.htmlEqual(target.innerHTML, ` 518 |
519 |
520 |
521 |
522 | 0 523 |
524 |
525 | 526 |
527 |
528 | 1 529 |
530 |
531 | 532 |
533 |
534 |
535 | `); 536 | 537 | let divider = target.querySelector('.divider'); 538 | 539 | mousedown(divider, 200, 500); 540 | mouseup(document.querySelector('.overlay'), 1001, 500); 541 | 542 | t.htmlEqual(target.innerHTML, ` 543 |
544 |
545 |
546 |
547 | 1 548 |
549 |
550 |
551 |
552 | `); 553 | 554 | layout.destroy(); 555 | }); 556 | 557 | test('accepts a layout', t => { 558 | const layout = init({ 559 | root: { 560 | id: 0, 561 | type: 'group', 562 | row: false, 563 | pos: 0, 564 | size: 1, 565 | prev: null, 566 | next: null, 567 | children: [ 568 | { 569 | type: 'pane', 570 | id: 1, 571 | pos: 0, 572 | size: 0.5, 573 | prev: null, 574 | next: null 575 | }, 576 | { 577 | type: 'pane', 578 | id: 2, 579 | pos: 0.5, 580 | size: 0.5, 581 | prev: null, 582 | next: null 583 | } 584 | ] 585 | } 586 | }); 587 | 588 | t.htmlEqual(target.innerHTML, ` 589 |
590 |
591 |
592 |
593 | 1 594 |
595 |
596 | 597 |
598 |
599 | 0 600 |
601 |
602 | 603 |
604 |
605 |
606 | `); 607 | 608 | layout.destroy(); 609 | }); 610 | 611 | test('fires open/close/layout events', t => { 612 | const layout = init(); 613 | 614 | const events = { 615 | open: [], 616 | close: [], 617 | layout: [] 618 | }; 619 | 620 | Object.keys(events).forEach(name => { 621 | layout.on(name, event => { 622 | events[name].push(event); 623 | }); 624 | }); 625 | 626 | const pane = document.querySelector('.pane'); 627 | 628 | mousedown(pane, 5, 100, true); 629 | mouseup(document.querySelector('.overlay'), 200, 100); 630 | 631 | t.equal(events.open.length, 1); 632 | const open = events.open[0]; 633 | t.ok('pane' in open); 634 | t.ok('layout' in open); 635 | t.ok('id' in open.pane); 636 | 637 | t.equal(events.layout.length, 2); 638 | const layout0 = events.layout[0]; 639 | t.equal(JSON.stringify(open.layout), JSON.stringify(layout0.layout)); 640 | 641 | t.htmlEqual(target.innerHTML, ` 642 |
643 |
644 |
645 |
646 | 0 647 |
648 |
649 | 650 |
651 |
652 | 1 653 |
654 |
655 | 656 |
657 |
658 |
659 | `); 660 | 661 | const divider = document.querySelector('.divider'); 662 | 663 | mousedown(divider, 200, 100); 664 | mouseup(document.querySelector('.overlay'), 0, 100); 665 | 666 | t.equal(events.close.length, 1); 667 | const close = events.close[0]; 668 | t.ok('pane' in close); 669 | t.ok('layout' in close); 670 | t.ok('id' in close.pane); 671 | 672 | t.equal(events.layout.length, 3); 673 | const layout2 = events.layout[2]; 674 | t.equal(JSON.stringify(close.layout), JSON.stringify(layout2.layout)); 675 | 676 | layout.destroy(); 677 | }); 678 | 679 | // TODO save to localStorage 680 | 681 | // this allows us to close puppeteer once tests have completed 682 | window.done = done; --------------------------------------------------------------------------------