This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
Generated Vendored
+6
View File
@@ -0,0 +1,6 @@
name: "Tether"
description: "Marrying DOM elements for life"
domain: "tether.io"
source: "coffee/*.coffee"
examples: "**/*.md"
assets: "{dist/js/*.js,dist/css/*.css,docs/css/*.css,docs/js/*,js,docs/welcome/*,examples/*}"
+5
View File
@@ -0,0 +1,5 @@
.sass-cache
node_modules/
deps/
.DS_Store
npm-debug.log
+13
View File
@@ -0,0 +1,13 @@
## v1.3.0
- Tether instances now fire an 'update' event when attachments change due to constraints (#119)
## v1.0.1
- Update arrow mixin to change arrow pointer event
## v1.0.0
- Coffeescript -> ES6
- Proper UMD Wrapper
- Update build steps
- Add changelog
- Provide minified CSS
+59
View File
@@ -0,0 +1,59 @@
# Contributing Guide
You will need:
- Node.js/io.js & npm
- Bower
- Gulp
## Getting started
1. Fork the project
2. Clone your forked project by running `git clone git@github.com:{
YOUR_USERNAME }/tether.git`
3. Run `npm install` to install both node modules and bower components
4. Test that you can build the source by moving/renaming the existing `dist`
directory and running `npm run build`
5. Assuming everything went well, you should now have a `dist` directory that
matches the one you moved in step 4
## Writing code!
We use `gulp` to facilitate things like transpilation, minification, etc. so
can you focus on writing relevant code. If there is a fix or feature you would like
to contribute, we ask that you take the following steps:
1. Most of the _editable_ code lives in the `src` directory while built code
will end up in the `dist` directory upon running `npm run build`.
2. Depending on how big your changes are, bump the version numbers appropriately
in `bower.json` and `package.json`. We try to follow semver, so a good rule
of thumb for how to bump the version is:
- A fix to existing code, perform a patch bump e.g. x.x.0 -> x.x.1
- New feature, perform a minor bump e.g. x.0.x -> x.1.x
- Breaking changes such a rewrite, perform a major bump e.g.
1.x.x -> 2.x.x
Versioning is hard, so just use good judgement and we'll be more than happy
to help out.
__NOTE__: There is a `gulp` task that will automate some of the versioning.
You can run `gulp version:{type}` where type is `patch|minor|major` to
update both `bower.json` and `package.json` as well as add the appropriate
git tag.
3. Provide a thoughtful commit message and push your changes to your fork using
`git push origin master` (assuming your forked project is using `origin` for
the remote name and you are on the `master` branch).
4. Open a Pull Request on GitHub with a description of your changes.
## Testing
Work in progress. We are hoping to add some tests, so if you would like to help
us get started, feel free to contact us through the Issues or open a Pull
Request.
+8
View File
@@ -0,0 +1,8 @@
Copyright (c) 2014-2016 HubSpot, Inc.
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.
+106
View File
@@ -0,0 +1,106 @@
## Tether
[![GitHub
version](https://badge.fury.io/gh/HubSpot%2Ftether.svg)](http://badge.fury.io/gh/HubSpot%2Ftether)
[Tether](http://github.hubspot.com/tether/) is a small, focused JavaScript library for defining and managing the position of user interface (UI) elements in relation to one another on a web page. It is a tool for web developers building features that require certain UI elements to be precisely positioned based on the location of another UI element.
There are often situations in UI development where elements need to be attached to other elements, but placing them right next to each other in the [DOM tree](https://en.wikipedia.org/wiki/Document_Object_Model) can be problematic based on the context. For example, what happens if the element were attaching other elements to is fixed to the center of the screen? Or what if the element is inside a scrollable container? How can we prevent the attached element from being clipped as it disappears from view while a user is scrolling? Tether can solve all of these problems and more.
Some common UI elements that have been built with Tether are [tooltips](http://github.hubspot.com/tooltip/docs/welcome), [select menus](http://github.hubspot.com/select/docs/welcome), [dropdown menus](http://github.hubspot.com/drop/docs/welcome), and [guided tours](http://github.hubspot.com/shepherd/docs/welcome). Tether is flexible and can be used to [solve](http://github.hubspot.com/tether/examples/out-of-bounds/) [all](http://github.hubspot.com/tether/examples/content-visible) [kinds](http://github.hubspot.com/tether/examples/element-scroll) [of](http://github.hubspot.com/tether/examples/enable-disable) interesting [problems](http://github.hubspot.com/tether/examples/viewport); it ensures UI elements stay where they need to be, based on the various user interactions (click, scroll, etc) and layout contexts (fixed positioning, inside scrollable containers, etc).
Please have a look at the [documentation](http://github.hubspot.com/tether/) for a more detailed explanation of why you might need Tether for your next project.
## What to Use Tether for and When to Use It
Tether is a small, focused JavaScript library. For those who might be new to JavaScript, a library is simply a JavaScript file (or files) that contain useful JavaScript code to help achieve tasks easier and faster. Since Tether is a JavaScript user interface (**UI**) library, it contains code to help you to manage the way your website or web app appears.
Tethers goal to is to help you position your elements side-by-side when needed.
Lets say youve started working on your dream project—a fancy web app thats sure to become the next big thing! An important feature of your new app is to allow users to comment on shared photos. However, due to limited vertical space and the overall layout of your new app, youd like to display the comments **next** to the image, similar to how Instagram does it.
Your HTML code might look something like this:
```html
<div class="container">
<img src="awesome-picture.jpg" alt="Awesome Picture" class="picture">
<div class="comments">
...
</div>
</div>
```
Now, you could achieve this with some CSS using its `position` property, but going this route can be problematic since many of `position`s values take elements **out** of the natural DOM flow. For example, if you have an element at the bottom of your HTML document, using `position: absolute` or `position: fixed` might could move it all the way to the top of your website in the browser.
Not only that, but you also have to make manual adjustments to ensure **other** elements arent negatively affected by the positioned elements. Not to mention, you probably want your comment box to be **responsive**, and look good across different device sizes. Coding a solution for this manually is a challenge all on its own.
**Enter Tether!**
After installing Tether and including it in your project, you can begin using it!
1. In your JavaScript file, create a new instance (or constructor function) of the `Tether` object:
```javascript
new Tether({});
```
2. Within the curly braces (`{}`) you can configure the librarys options. Tethers extensive list of options can be found in the [Tether documentation](http://github.hubspot.com/tether/).
```javascript
new Tether({
element: '.comments',
target: '.picture',
attachment: 'top right'
targetAttachment: 'top left'
});
```
Now you have a perfectly placed comment section to go with your awesome picture! Itll even stay attached to the element when a user resizes their browser window.
There are tons of other useful features of Tether as well, instead of “comment boxes” you could also build:
* Tooltips for useful hints and tricks,
* Dropdown menus,
* Autocomplete popups for forms,
* and [more](http://github.hubspot.com/tether/examples/list_of_examples/)!
## Install
__npm__
```sh
$ npm install tether
```
__bower__
```sh
$ bower install tether
```
__download__
Or just download from the [releases](https://github.com/HubSpot/tether/releases).
## Usage
You only need to include [tether.min.js](https://github.com/HubSpot/tether/blob/master/dist/js/tether.min.js) in your page:
```
<script src="path/to/dist/js/tether.min.js"></script>
```
Or just use a CDN:
```
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.1/js/tether.min.js"></script>
```
The css files in the [dist/css](https://github.com/HubSpot/tether/tree/master/dist/css) folder are not required to get tether running.
For more details jump straight in to the detailed [Usage](http://github.hubspot.com/tether/#usage) page.
[![Tether Docs](http://i.imgur.com/YCx8cLr.png)](http://github.hubspot.com/tether/#usage)
[Demo & API Documentation](http://github.hubspot.com/tether/)
## Contributing
We encourage contributions of all kinds. If you would like to contribute in some way, please review our [guidelines for contributing](CONTRIBUTING.md).
## License
Copyright &copy; 2014-2016 HubSpot - [MIT License](LICENSE)
+26
View File
@@ -0,0 +1,26 @@
{
"name": "tether",
"version": "1.4.0",
"homepage": "http://github.hubspot.com/tether",
"authors": [
"Zack Bloom <zackbloom@gmail.com>",
"Adam Schwartz <adam.flynn.schwartz@gmail.com>"
],
"maintainers": [
"Nicholas Hwang <nick.joosung.hwang@gmail.com>",
"Trevor Burnham <trevorburnham@gmail.com>"
],
"description": "A client-side library to make absolutely positioned elements attach to elements in the page efficiently.",
"keywords": [
"javascript"
],
"license": "MIT",
"main": "dist/js/tether.js",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "tether",
"repo": "HubSpot/tether",
"version": "1.3.9",
"description": "A client-side library to make absolutely positioned elements attach to elements in the page efficiently.",
"authors": [
"Zack Bloom <zackbloom@gmail.com>",
"Adam Schwartz <adam.flynn.schwartz@gmail.com>"
],
"maintainers": [
"Nicholas Hwang <nick.joosung.hwang@gmail.com>"
],
"license": "MIT",
"demo": "http://github.hubspot.com/tether/docs/welcome/",
"main": "dist/js/tether.js",
"styles": [
"dist/css/tether.css"
],
"scripts": [
"dist/js/tether.js"
]
}
+150
View File
@@ -0,0 +1,150 @@
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
box-sizing: border-box; }
.tether-element {
position: absolute;
display: none; }
.tether-element.tether-open {
display: block; }
.tether-element.tether-theme-arrows-dark {
max-width: 100%;
max-height: 100%; }
.tether-element.tether-theme-arrows-dark .tether-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #000;
color: #fff;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.tether-element.tether-theme-arrows-dark .tether-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-center .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-center .tether-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #000;
border-bottom: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-center .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-center .tether-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #000;
border-top: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-right.tether-element-attached-middle .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-right.tether-element-attached-middle .tether-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #000;
border-right: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-left.tether-element-attached-middle .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-left.tether-element-attached-middle .tether-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #000;
border-left: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-left.tether-target-attached-center .tether-content {
left: -32px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-right.tether-target-attached-center .tether-content {
left: 32px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #000;
border-top: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #000;
border-top: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content:before {
top: 100%;
left: 16px;
border-top-color: #000;
border-bottom: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content:before {
top: 100%;
right: 16px;
border-top-color: #000;
border-bottom: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #000;
border-top: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #000;
border-top: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content:before {
top: 100%;
left: 16px;
border-top-color: #000;
border-bottom: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content:before {
top: 100%;
right: 16px;
border-top-color: #000;
border-bottom: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content:before {
top: 16px;
left: 100%;
border-left-color: #000;
border-right: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content:before {
top: 16px;
right: 100%;
border-right-color: #000;
border-left: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content:before {
bottom: 16px;
left: 100%;
border-left-color: #000;
border-right: 0; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows-dark.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content:before {
bottom: 16px;
right: 100%;
border-right-color: #000;
border-left: 0; }
File diff suppressed because one or more lines are too long
+154
View File
@@ -0,0 +1,154 @@
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
box-sizing: border-box; }
.tether-element {
position: absolute;
display: none; }
.tether-element.tether-open {
display: block; }
.tether-element.tether-theme-arrows {
max-width: 100%;
max-height: 100%; }
.tether-element.tether-theme-arrows .tether-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #fff;
color: inherit;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2)); }
.tether-element.tether-theme-arrows .tether-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-center .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-center .tether-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #fff;
border-bottom: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-center .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-center .tether-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #fff;
border-top: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-right.tether-element-attached-middle .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-right.tether-element-attached-middle .tether-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #fff;
border-right: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-left.tether-element-attached-middle .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-left.tether-element-attached-middle .tether-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #fff;
border-left: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-left.tether-target-attached-center .tether-content {
left: -32px; }
.tether-element.tether-theme-arrows.tether-element-attached-right.tether-target-attached-center .tether-content {
left: 32px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff;
border-top: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff;
border-top: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content:before {
top: 100%;
left: 16px;
border-top-color: #fff;
border-bottom: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content:before {
top: 100%;
right: 16px;
border-top-color: #fff;
border-bottom: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff;
border-top: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content {
margin-top: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff;
border-top: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content:before {
top: 100%;
left: 16px;
border-top-color: #fff;
border-bottom: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content {
margin-bottom: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content:before {
top: 100%;
right: 16px;
border-top-color: #fff;
border-bottom: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content:before {
top: 16px;
left: 100%;
border-left-color: #fff;
border-right: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content:before {
top: 16px;
right: 100%;
border-right-color: #fff;
border-left: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content {
margin-right: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content:before {
bottom: 16px;
left: 100%;
border-left-color: #fff;
border-right: 0; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content {
margin-left: 16px; }
.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content:before {
bottom: 16px;
right: 100%;
border-right-color: #fff;
border-left: 0; }
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
box-sizing: border-box; }
.tether-element {
position: absolute;
display: none; }
.tether-element.tether-open {
display: block; }
.tether-element.tether-theme-basic {
max-width: 100%;
max-height: 100%; }
.tether-element.tether-theme-basic .tether-content {
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
font-family: inherit;
background: #fff;
color: inherit;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
+1
View File
@@ -0,0 +1 @@
.tether-element,.tether-element *,.tether-element :after,.tether-element :before,.tether-element:after,.tether-element:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-basic{max-width:100%;max-height:100%}.tether-element.tether-theme-basic .tether-content{border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,.2);font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em}
+8
View File
@@ -0,0 +1,8 @@
.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
box-sizing: border-box; }
.tether-element {
position: absolute;
display: none; }
.tether-element.tether-open {
display: block; }
+1
View File
@@ -0,0 +1 @@
.tether-element,.tether-element *,.tether-element :after,.tether-element :before,.tether-element:after,.tether-element:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}
+1811
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+43
View File
@@ -0,0 +1,43 @@
## Why You Should Use Tether
Virtually every app includes some sort of overlay attached to an element on the page.
Things like [tooltips](http://github.hubspot.com/tooltip/docs/welcome),
[dropdowns](http://github.hubspot.com/select/docs/welcome), [hover-activated info boxes](http://github.hubspot.com/drop/docs/welcome), etc.
Those elements need to be attached to something on the page. Actually placing them next to
the element in the DOM causes problems though, if any parent element is anything
but `overflow: visible`, the element gets cut off. So you need absolute positioning
in the body.
Some of the time absolute positioning is right, but what about if the thing we're
attached to is fixed to the center of the screen? We'll have to move it every
time the user scrolls. What about if the element is in a scrollable container,
if the overlay is inside of it (so no clipping), it would be cool if the code
were smart enough to move it inside when that area is scrolled. That way we
need to reposition it even less.
It would also be nice if the code could somehow figure out whether positioning it
from the top, bottom, left, or right would result in the fewest repositionings
as the user scrolls or resizes.
Most of the time you're building these elements it would be nice for the element to
flip to the other side of the element if it hits the edge of the screen, or a scrollable
container it might be in. It would be nice if we could confine the element
to within some area, or even hide it when it leaves.
It would be nice for the element to be repositioned with CSS transforms
rather than top and left when possible, to allow the positioning to be done entirely
in the GPU.
Now that the positioning is so fancy, you're going to use it for more and more
elements. It would be cool if the library could optimize all of their repositioning
into a single repaint.
All of that is baked into Tether.
### tl;dr
- Optimized GPU-accelerated repositioning for 60fps scrolling
- Reliable positioning on any possible corner, edge or point in between.
- Support for repositioning or pinning the element when it would be offscreen
- Designed to be embeddable in other libraries
+46
View File
@@ -0,0 +1,46 @@
Repositioning
-----
Tethers will be automatically repositioned when the page is resized, and when any element containing the Tether is scrolled.
If the element moves for some other reason (e.g. with JavaScript), Tether won't know to reposition the element.
#### Manually Repositioning
The simplest way to reposition every Tether on the page is to call `Tether.position()`. It will efficiently reposition every
Tether in a single repaint, making it more efficient than manually repositioning many Tethers individually.
```javascript
Tether.position()
```
#### Repositioning a Single Tether
If you have many Tethers on screen, it may be more efficient to just reposition the tether that needs it. You can do this
by calling the `.position` method on the Tether instance:
```javascript
tether = new Tether({ ... })
// Later:
tether.position()
```
#### Tethering Hidden Elements
If you are creating a tether involving elements which are `display: none`, or not actually in the DOM,
your Tether may not be able to position itself properly. One way around this is to
ensure that a position call happens after all layouts have finished:
```javascript
myElement.style.display = 'block'
tether = new Tether({ ... })
setTimeout(function(){
tether.position();
})
```
In general however, you shouldn't have any trouble if both the element and the target are visible and in the DOM when you
create the Tether. If that is not the case, create the Tether disabled (option `enabled`: `false`), and enable it when
the elements are ready.
+47
View File
@@ -0,0 +1,47 @@
Why we don't support IE 8
-------------------------
We've been living in 2007 for a while now, pretending that new browser features don't
exist because they aren't in IE8. You might not even know about some of these features,
or think they are only enabled by jQuery or underscore, simply because it hasn't
been an option to rely upon them.
Here is the list of features you don't have if you choose to support IE 8:
- HTML5 audio and video
- SVG
- Canvas
- TrueType fonts
- Media Queries
- CSS Transforms
- Multiple Backgrounds
- CSS3 Units (vh, vw, rem)
- Custom DOM events
- Hardware accelerated graphics
- The DOMContentLoaded event
- addEventListener
- Object.create, .seal, .freeze, .defineProperty
- Array.isArray, .indexOf, .every, .some, .forEach, .map, .filter, .reduce
- A modern JavaScript engine
- A real developer tools
- A consistent box model
- jQuery 2
- Google Apps
- Tether
It's true that IE 8 still holds a big chunk of the browsing population, but the reasons
why they can't update are dwindling. There are two big reasons for continuing IE 8 support.
#### Enterprises
Microsoft is dropping support for XP in April, organizations who want security updates will have to upgrade.
#### China uses XP
Chrome, Firefox and Opera all support XP. Nothing prevents users from upgrading, except the inertia of
organizations who still support IE 8.
#### The Future
We are skating towards where the puck will be, and we hope that as you decide to drop IE 8 support,
you choose to add Tether to the list of awesome things you can do.
+27
View File
@@ -0,0 +1,27 @@
### Examples
It's our goal to create a wide variety of example of how Tether
can be used. Here's what we have so far, please send a PR with
any examples you might create.
#### Beginner
- [simple](../../examples/simple): A simple example to get you started
- [out-of-bounds](../../examples/out-of-bounds): How to hide the element when it would
otherwise be offscreen
- [pin](../../examples/pin): How to pin the element so it never goes offscreen
- [enable-disable](../../examples/enable-disable): How to enable and disable the Tether
in JavaScript
#### Advanced
- [content-visible](../../examples/content-visible): Demonstrates using the `'visible'`
`targetModifier` to align an element with the visible portion of another.
- [dolls](../../examples/dolls): A performance test to show several dozen elements,
each tethered to the previous. Try dragging the top left tether.
- [element-scroll](../../examples/element-scroll): Demonstrates using the `'scroll-handle'`
`targetModifier` to align an element with the scrollbar of an element.
- [scroll](../../examples/scroll): Demonstrates using the `'scroll-handle'` `targetModifier`
to align an element with the body's scroll handle.
- [viewport](../../examples/viewport): Demonstrates aligning an element with the
viewport by using the `'visible'` `targetModifier` when tethered to the body.
+37
View File
@@ -0,0 +1,37 @@
## Projects Using Tether
Here at HubSpot we have built a bunch of libraries on top of Tether,
both because we wanted Tether-performance, and because we saw opportunities
to improve on what was available in the client-side ecosystem.
### [Select](http://github.hubspot.com/select/docs/welcome)
Select is a replacement for native browser select elements that is fully stylable.
### [Shepherd](http://github.hubspot.com/shepherd/docs/welcome)
Shepherd is a library for making tours of your app to help onboard users and show off
new features.
### [Tooltip](http://github.hubspot.com/tooltip/docs/welcome)
A simple, easy-to-use implementation of tooltips that works well.
### [Drop](http://github.hubspot.com/drop/docs/welcome)
Where Tether does general-purpose positioning, Drop assumes that you are interested
in making something which pops up next to something the user clicks or hovers on.
If you're building something that fits that pattern, Drop can make things a little easier.
### [React Datepicker](https://github.com/Hacker0x01/react-datepicker)
A simple and reusable datepicker component for React
### [ember-tether](https://github.com/yapplabs/ember-tether)
An Ember.js-friendly interface for tether.
### Your Project Here
If you have a cool open-source library built on Tether, PR this doc.
+9
View File
@@ -0,0 +1,9 @@
## Embedding Tether
Tether is designed to be embeddable in other libraries.
There is one thing you should think about doing to create an embedded Tether:
- Set the `classPrefix` of the tethers you create. That prefix will replace `'tether'` in
all of the classes. You can also disable classes you don't intend on using with the `classes`
option.
+54
View File
@@ -0,0 +1,54 @@
Extending Tether
-----
Tether has a module system which can be used to modify Tether's positioning, or just do something each time the Tether is moved.
Tether has an array called `Tether.modules`, push onto it to add a module:
```coffeescript
Tether.modules.push
position: ({top, left}) ->
top += 10
{top, left}
```
#### Position
Your position function can either return a new object with `top` and `left`, `null`/`undefined` to leave the coordinates unchanged, or
`false` to cancel the positioning.
The position function is passed an object with the following elements:
```javascript
{
left, // The element's new position, from the top left corner of the page
top,
targetAttachment, // The targetAttachment, with 'auto' resolved to an actual attachment
targetPos, // The coordinates of the target
attachment, // The attachment, as passed in the option
elementPos, // The coordinates of the element
offset, // The offset, after it's converted into pixels and the manual offset is added
targetOffset, // The attachment is converted into an offset and is included in these values
manualOffset, // The manual offset, in pixels
manualTargetOffset
}
```
It is called with the Tether instance as its context (`this`).
#### Initialize
Modules can also have an `initialize` function which will be called when a new tether is created. The initialize function
is also called with the Tether instance as its context.
```coffeescript
Tether.modules.push
initialize: ->
console.log "New Tether Created!", @
```
#### Examples
[Constraints](https://github.com/HubSpot/tether/blob/master/src/js/constraint.js) and [shift](https://github.com/HubSpot/tether/blob/master/src/js/shift.js) are both implemented as modules.
[Mark Attachment](https://github.com/HubSpot/tether/blob/master/src/js/markAttachment.js) is used by the docs.
+113
View File
@@ -0,0 +1,113 @@
{uniqueId} = Tether.Utils
SETUP_JS = """
yellowBox = $('.yellow-box', $output);
greenBox = $('.green-box', $output);
scrollBox = $('.scroll-box', $output);
"""
OUTPUT_HTML = (key) -> """
<div class="scroll-box">
<div class="scroll-content">
<div class="yellow-box" data-example="#{ key }"></div>
<div class="green-box" data-example="#{ key }"></div>
</div>
</div>
"""
tethers = {}
getOutput = ($block) ->
key = $block.data('example')
if key and typeof key is 'string'
return $("output[data-example='#{ key }']")
else
return $block.parents('pre').nextAll('output').first()
run = (key) ->
if typeof key is 'string'
$block = $("code[data-example='#{ key }']")
else
$block = key
key = $block.attr('data-example')
$output = getOutput $block
code = $block.text()
code = SETUP_JS + code
window.$output = $output
tethers[key] = eval code
setupBlock = ($block) ->
key = $block.data('example')
$output = getOutput $block
if not key
key = uniqueId()
$block.attr('data-example', key)
$output.attr('data-example', key)
$output.find('.tether-element').attr('data-example', key)
$output.html OUTPUT_HTML(key)
$scrollBox = $output.find('.scroll-box')
$scrollContent = $scrollBox.find('.scroll-content')
$scrollBox.scrollTop(parseInt($scrollContent.css('height')) / 2 - $scrollBox.height() / 2)
$scrollBox.scrollLeft(parseInt($scrollContent.css('width')) / 2 - $scrollBox.width() / 2)
setTimeout ->
$scrollBox.on 'scroll', ->
$output.addClass 'scrolled'
$scrollBox.css 'height', "#{ $block.parent().outerHeight() }px"
if not $output.attr('deactivated')?
run $block
$(document.body).on 'click', (e) ->
if $(e.target).is('output[deactivated]')
activate $(e.target)
false
else if $(e.target).is('output[activated]')
deactivate $(e.target)
false
activate = ($output) ->
$block = $output.prev().find('code')
run $block
$output.find('.tether-element').show()
key = $output.data('example')
$(tethers[key].element).show()
tethers[key].enable()
$output.removeAttr('deactivated')
$output.attr('activated', true)
deactivate = ($output) ->
$block = $output.prev().find('code')
key = $output.data('example')
tethers[key].disable()
$el = $(tethers[key].element)
$el.detach()
$output.find('.scroll-content').append $el
$el.hide()
$output.removeAttr('activated')
$output.attr('deactivated', true)
init = ->
$blocks = $('code[data-example]')
setupBlock($ block) for block in $blocks
window.EXECUTR_OPTIONS =
codeSelector: 'code[executable]'
$ init
+218
View File
@@ -0,0 +1,218 @@
@charset "UTF-8";
*, *:after, *:before {
box-sizing: border-box; }
body {
position: relative; }
.yellow-box {
width: 100px;
height: 100px;
background-color: #fe8;
pointer-events: none; }
.green-box {
margin-top: 65px;
margin-left: 100px;
width: 200px;
height: 50px;
background-color: #4e9; }
.no-green .green-box {
display: none; }
.scroll-box {
height: 150px;
border: 10px solid #eee;
background: #fbfbfb;
overflow: auto;
position: relative; }
.scroll-content {
height: 2000px;
width: 2000px;
padding: 910px 809px; }
pre.pre-with-output {
margin: 0;
width: 50%;
float: left; }
pre.pre-with-output code mark {
background: #b8daff;
color: #000; }
p, h2, h3 {
clear: both; }
output {
display: block;
position: relative;
width: 50%;
float: right;
margin-bottom: 15px; }
output.scroll-page .scroll-box {
overflow: hidden; }
output.scroll-page:after {
content: "↕ scroll the page ↕"; }
output:after {
content: "↕ scroll this area ↕";
position: absolute;
bottom: 25px;
width: 100%;
text-align: center;
font-size: 16px;
font-variant: small-caps;
color: #777;
opacity: 1;
-webkit-transition: opacity 0.2s;
transition: opacity 0.2s; }
output.scrolled:after {
opacity: 0; }
output[deactivated], output[activated] {
cursor: pointer; }
output[deactivated] .scroll-box, output[activated] .scroll-box {
pointer-events: none; }
output[deactivated]:after, output[activated]:after {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
content: "Click To Show";
background-color: #AAA;
border-left: 10px solid #EEE;
color: white;
font-size: 24px;
font-variant: normal;
padding-top: 80px; }
output[activated]:after {
content: "Click To Hide"; }
output[activated].visible-enabled:after {
height: 35px;
padding-top: 5px; }
.attachment-mark, .tether-marker-dot {
position: relative; }
.attachment-mark:after, .tether-marker-dot:after {
content: "A";
width: 10px;
height: 10px;
background-color: red;
display: inline-block;
line-height: 10px;
font-size: 9px;
color: white;
text-align: center;
position: absolute; }
span.attachment-mark:after, span.tether-marker-dot:after {
position: relative;
top: -1px;
margin-right: 1px; }
.tether-marker-dot {
position: absolute; }
.tether-marker-dot:after {
top: -5px;
left: -5px; }
.tether-target-marker {
position: absolute; }
div.tether-target-attached-left .tether-target-marker {
left: 0; }
div.tether-target-attached-top .tether-target-marker {
top: 0; }
div.tether-target-attached-bottom .tether-target-marker {
bottom: 0; }
div.tether-target-attached-right .tether-target-marker {
right: 0; }
div.tether-target-attached-center .tether-target-marker {
left: 50%; }
.tether-element-marker {
position: absolute; }
div.tether-element-attached-left .tether-element-marker {
left: 0; }
div.tether-element-attached-top .tether-element-marker {
top: 0; }
div.tether-element-attached-bottom .tether-element-marker {
bottom: 0; }
div.tether-element-attached-right .tether-element-marker {
right: 0; }
div.tether-element-attached-center .tether-element-marker {
left: 50%; }
.tether-element-attached-middle .tether-element-marker {
top: 50px; }
.tether-target-attached-middle .tether-target-marker {
top: 25px; }
.tether-element {
position: relative; }
.tether-element.tether-pinned-left {
box-shadow: inset 2px 0 0 0 red; }
.tether-element.tether-pinned-right {
box-shadow: inset -2px 0 0 0 red; }
.tether-element.tether-pinned-top {
box-shadow: inset 0 2px 0 0 red; }
.tether-element.tether-pinned-bottom {
box-shadow: inset 0 -2px 0 0 red; }
.tether-target {
position: relative; }
.tether-element.tether-out-of-bounds[data-example="hide"] {
display: none; }
[data-example^="optimizer"].lang-javascript {
/* This should just be a `code` selector, but sass doesn't allow that with & */
min-height: 220px; }
[data-example^="optimizer"].tether-element:before {
margin-top: 26px;
display: block;
text-align: center;
content: "I'm in the body";
line-height: 1.2;
font-size: 15px;
padding: 4px;
color: #666; }
[data-example^="optimizer"] .scroll-box .tether-element:before {
content: "I'm in my scroll parent!"; }
.tether-element[data-example="scroll-visible"] {
height: 30px; }
.tether-element[data-example="scroll-visible"] .tether-marker-dot {
display: none; }
.hs-doc-content h2.projects-header {
text-align: center;
font-weight: 300; }
.projects-paragraph {
text-align: center; }
.projects-paragraph a {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
text-align: center;
margin-right: 30px;
color: inherit; }
.projects-paragraph a span {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
margin-bottom: 20px;
font-size: 20px;
color: inherit;
font-weight: 300; }
.projects-paragraph a img {
display: block;
max-width: 100%;
width: 100px; }
+591
View File
@@ -0,0 +1,591 @@
<script src="dist/js/tether.js"></script>
<script src="docs/js/markAttachment.js"></script>
<script src="docs/js/intro.js"></script>
<link rel="stylesheet" href="docs/css/intro.css"></link>
Tether
======
Tether is a JavaScript library for efficiently making an absolutely positioned
element stay next to another element on the page. For example, you might
want a tooltip or dialog to open, and remain, next to the relevant item
on the page.
Tether includes the ability to constrain the element within the viewport, its
scroll parent, any other element on the page, or a fixed bounding box. When it
exceeds those constraints it can be pinned to the edge, flip to the other
side of its target, or hide itself.
Tether optimizes its location placement to result in the minimum amount of
'jankyness' as the page is scrolled and resized. The page can maintain 60fps
scrolling even with dozens or hundreds of tethers on screen (pop open the
devtools timeline as you scroll this page).
Tether is 5kb minified and gzipped, and supports IE9+, and all modern
browsers.
<h2 class="projects-header">Projects Built With Tether</h2>
<p class="projects-paragraph">
<a href="http://github.hubspot.com/select/docs/welcome"><span>Select</span><img src="http://github.hubspot.com/os-icons/select-icon.png" /></a>
<a href="http://github.hubspot.com/drop/docs/welcome"><span>Drop</span><img src="http://github.hubspot.com/os-icons/drop-icon.png" /></a>
<a href="http://github.hubspot.com/tooltip/docs/welcome"><span>Tooltip</span><img src="http://github.hubspot.com/os-icons/tooltip-icon.png" /></a>
<a href="http://github.hubspot.com/shepherd/docs/welcome"><span>Shepherd</span><img src="http://github.hubspot.com/os-icons/shepherd-icon.png" /></a>
</p>
Usage
-----
The element to be moved is called the 'element'.
The element in the page it's to be attached to is called the 'target'.
To use Tether, you define a point on the target and a point on the element.
Tether moves the element to keep those two points on top of each other.
That point is called the attachment (we've marked it in the examples with
a red <span class="attachment-mark"></span>). For example, if you'd like
the element to sit on the left of the target:
<pre class="pre-with-output"><code class="lang-javascript" data-example='usage'>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top right',
targetAttachment: 'top left'
});
</code></pre><output data-example='usage'></output>
Attachment
----------
You can move the attachment points of both the element and the target.
For example, lets move the element's attachment:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: <mark>'bottom left'</mark>,
targetAttachment: 'top left'
});
</code></pre><output></output>
We can also change the target's attachment point:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'bottom left',
targetAttachment: <mark>'bottom right'</mark>
});
</code></pre><output></output>
There are two more attachment points we haven't seen yet, center and middle:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: <mark>'middle center'</mark>,
targetAttachment: <mark>'middle center'</mark>
});
</code></pre><output></output>
All told, Tether provides six built in attachment positions:
- left
- center
- right
- top
- middle
- bottom
The syntax of the attachment properties is: `"vertical-attachment horizontal-attachment"`.
You must always supply an `attachment`. If you don't supply a `target-attachment`, it is
assumed to be the mirror image of `attachment`.
### Offset
The six attachment points we provide are not always enough to place the element
exactly where you want it. To correct this, we provide two more properties,
`offset` and `targetOffset`.
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top right',
targetAttachment: 'top left',
<mark>offset: '0 10px'</mark>
});
</code></pre><output></output>
As you can see, we've moved the attachment point of the element 10px to the right.
We can also move the attachment point of the target:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top right',
targetAttachment: 'top left',
offset: '0 10px',
<mark>targetOffset: '20px 0'</mark>
});
</code></pre><output></output>
The offset properties also accept percentages. Percentages in `offset` refer to
the height and width of the element, `targetOffset` the height and width of
the target.
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top right',
targetAttachment: 'top left',
targetOffset: <mark>'0 75%'</mark>
});
</code></pre><output></output>
The syntax of the offset properties is `"vertical-offset horizontal-offset"`
Tether offers a couple of special attachments, using the `targetModifier`
option:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: scrollBox,
attachment: 'middle right',
targetAttachment: 'middle left',
targetModifier: 'scroll-handle'
});
</code></pre><output></output>
Set the target to `document.body` to have the element follow the page's scroll bar.
The `targetModifier` `visible` can be used to attach an element to the visible part
of an element:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: document.body,
attachment: 'middle center',
targetAttachment: 'middle center',
<mark>targetModifier: 'visible'</mark>
});
</code></pre><output deactivated></output>
<pre class="pre-with-output"><code class="lang-javascript" data-example="scroll-visible">new Tether({
element: yellowBox,
<mark>target: scrollBox</mark>,
attachment: 'middle center',
targetAttachment: 'middle center',
targetModifier: 'visible'
});
</code></pre><output class="no-green scroll-page" data-example="scroll-visible"></output>
Constraints
-----------
If you have tried any of the previous examples, you'll notice that it's pretty
easy to scroll the regions in such a way that the element is hanging out on
its own, with no target in sight.
Constraints allow you to control what happens when the tethered element would
have to fall outside of a defined region to maintain the attachment.
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'middle left',
targetAttachment: 'middle left',
<mark>constraints</mark>: [
{
to: 'scrollParent',
pin: true
}
]
});
</code></pre><output></output>
We've created a constraint which will keep the element within its scroll
parent by 'pinning' it to the edges if it tries to escape. For the sake
of the example, we're also highlighting the pinned edge in red.
Specify an array of sides if you'd only like to pin those edges:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'middle left',
targetAttachment: 'middle left',
constraints: [
{
to: 'scrollParent',
pin: <mark>['top']</mark>
}
]
});
</code></pre><output></output>
You might want to allow the element to change its attachment, if doing so
would keep more of it within its assigned region:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'scrollParent',
<mark>attachment: 'together'</mark>
}
]
});
</code></pre><output></output>
If you scroll the example a bit, you'll see it flip the attachment when necessary.
You can combine `pin` and `attachment` as well:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'scrollParent',
attachment: 'together',
<mark>pin: true</mark>
}
]
});
</code></pre><output></output>
Attachment will accept any of these values:
- `element`: Only change the element's attachment
- `target`: Only change the target's attachment
- `both`: Change either's attachment (or both), as needed
- `together`: Change both the element's and target's attachment at the same time (to
'flip' the element to the other side of the attachment)
- `none`: Don't allow changes to attachment (the default)
Together is the option you will use most commonly:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top right',
targetAttachment: 'bottom left',
constraints: [
{
to: 'scrollParent',
attachment: <mark>'together'</mark>
}
]
});
</code></pre><output></output>
You can also provide different settings for the vertical and horizontal attachments:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'scrollParent',
attachment: <mark>'together none'</mark>
}
]
});
</code></pre><output></output>
Whenever the element is out of the constrained area, we add the `tether-out-of-bounds`
class to it. If you add some CSS to make items with that class `display: none`, the
tether will hide.
<pre class="pre-with-output"><code class="lang-javascript" data-example="hide">new Tether({
element: yellowBox,
target: greenBox,
attachment: 'middle center',
targetAttachment: 'middle center',
constraints: [
{
to: 'scrollParent'
}
]
});
</code></pre><output data-example="hide"></output>
You can also constrain the element to the viewport, you'll have to scroll the
page to see this one.
<pre class="pre-with-output"><code class="lang-javascript" data-example="window">new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: <mark>'window'</mark>,
attachment: 'together'
}
]
});
</code></pre><output data-example="window" class="scroll-page"></output>
You can, of course, use pin with the window as well to
make it always visible no matter where the user scrolls:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'window',
attachment: 'together',
<mark>pin: true</mark>
}
]
});
</code></pre><output deactivated class="scroll-page visible-enabled"></output>
`to` can be any of:
- `'scrollParent'`
- `'window'`
- any DOM element
- an array of bound points relative to the body `[X1, Y1, X2, Y2]`
You can also provide multiple constraints, keeping in mind that they are
processed in the order supplied (the last one always has the final word).
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: <mark>'scrollParent'</mark>,
pin: true
},
{
to: <mark>'window'</mark>,
attachment: 'together'
}
]
});
</code></pre><output></output>
Optimization
------------
### Element Moving
The goal of Tether's optimizer is to not have to change the positioning
CSS as the page is scrolled or resized. To accomplish this it looks at the
last few positions, finds commonalities, and uses them to decide whether to
position the element absolutely or with fixed positioning.
If the element is fully contained within its scroll parent, its DOM node
can also be moved inside the scroll parent, to avoid repaints as the
container is scrolled.
<pre class="pre-with-output"><code class="lang-javascript" data-example="optimizer">new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left'
});
</code></pre><output data-example="optimizer"></output>
We are moving where the DOM node is, so if you have CSS which styles elements
within the offset parent, you may see some rendering changes. Also note
that this optimization works best if the scroll parent is the offset parent.
In other words, **the scroll parent should be made position relative, fixed or
absolute to enable this optimization.**
If you do see stylistic changes occur when the element is moved,
you might want to disable this optimization. You can do that by
setting `optimizations.moveElement` to false.
<pre class="pre-with-output"><code class="lang-javascript" data-example="optimizer2">new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
targetAttachment: 'bottom left',
optimizations: {
<mark>moveElement: false</mark>
}
});
</code></pre><output data-example="optimizer2"></output>
### GPU
By default tether positions elements using CSS transforms. These transforms allow the
tethered element to be moved as its own layer to not force a repaint of the underlying
page.
This method of positioning can cause some issues however, including color shifts and artifacts.
If you experience these issues, you can disable this optimization by setting `optimizations.gpu`
to false:
<pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
element: yellowBox,
target: greenBox,
attachment: 'top left',
optimizations: {
<mark>gpu: false</mark>
}
});
</code></pre><output></output>
Methods
-------
The `Tether` constructor we've been using in these examples returns us a
`Tether` object.
The `Tether` object has these methods:
- `setOptions({ options })` - Update any of the options (such as attachment)
- `disable()` - Disable the tethering
- `enable()` - Enable the tethering
- `destroy()` - Disable and remove all references
- `position()` - Manually trigger a repositioning
Options
-------
The full list of options which can be passed to the `Tether` constructor and
`setOptions`:
- `element`: The DOM element, jQuery element, or a selector string of an element which will be moved
- `target`: The DOM element, jQuery element, or a selector string of an element which the `element` will be attached to
- `attachment`: A string of the form `'vert-attachment horiz-attachment'`
- `vert-attachment` can be any of `'top'`, `'middle'`, `'bottom'`
- `horiz-attachment` can be any of `'left'`, `'center'`, `'right'`
- `targetAttachment`: A string similar to `attachment`.
The one difference is that, if it's not provided, targetAttachment will assume the mirror
image of `attachment`.
- `offset`: A string of the form `'vert-offset horiz-offset'`
- `vert-offset` and `horiz-offset` can be of the form `"20px"` or `"55%"`
- `targetOffset`: A string similar to `offset`, but refering to the offset of the target
- `targetModifier`: Can be set to `'visible'` or `'scroll-handle'`
- `enabled`: Should the tether be enabled initially? Defaults to `true`.
- `classes`: A hash of classes which should be changed or disabled
- `classPrefix`: The prefix placed at the beginning of the default classes, defaults to `'tether'`
- `optimizations`: A hash of optimizations, used to disable them
- `constraints`: An array of constraint definition objects. Each definition is of the form:
- `to`: A DOM element, bounding box, the string `'window'`, or the string `'scrollParent'`
- `pin`: `true` or an array of strings representing the sides of the constraint
- `attachment`: A string of the form `"vert-modifier horiz-modifier"`, or a single value
representing both
- Each modifier should be one of `"none"`, `"together"`, `"element"`, `"target"`, or `"both"`.
- `outOfBoundsClass`: An alternative to `"tether-out-of-bounds"`, useful if the class
needs to be differentiated from that of another constraint.
- `pinnedClass`: An alternative to `"tether-pinned"`, similar to `outOfBoundsClass`.
Classes
-------
Tether adds a variety of classes to the element and target to allow you to style
them based on their tethering.
You can change the prefix of the classes with the `classPrefix` option. It is `'tether'` by
default, but you could, for example, change it to be `'bill'` if you were building the bill
library and all the classes would be `'bill-*'`.
```javascript
new Tether({
classPrefix: 'bill'
});
```
The sass/css is similarily configurable, see
[tooltip](https://github.com/HubSpot/tooltip/blob/master/sass/tooltip-theme-arrows.sass#L14) for
an example of how to make your own prefixed css file.
All classes can be changed or disabled with the `classes` option. For example, to change the
`tether-element` class to be `my-box`:
```javascript
new Tether({
classes: {
element: 'my-box'
}
});
```
You can also disable classes you're not going to use:
```javascript
new Tether({
classes: {
out-of-bounds: false
}
});
```
- `tether-element` is added to the element
- `tether-target` is added to the target
- `tether-enabled` is added to both elements when tether is not disabled
- `tether-element-attached-[left,right,top,bottom,middle,center]` is added to both
elements based on the elements attachment, if the element becomes detached (for
example, if it's pinned), that class is removed. The class reflects how the
element is actually attached, so if a constraint changes the attachment, that
change will be reflected in the class.
- `tether-target-attached-[left,right,top,bottom,middle,center]` is added to both
elements based on the target's attachment. All of the characteristics are the
same as for element-attached.
### Constraint-related Classes
- `tether-out-of-bounds`, `tether-out-of-bounds-[side]` are added to both the element and the target
when the element is placed outside of its constraint.
- `tether-pinned`, `tether-pinned-[side]` are added to both the element and target when a constraint
has pinned the element to the [side] of the container.
Browser Support
---------------
Tether supports IE9+, and all modern browsers.
Google doesn't support IE8, Microsoft is dropping support in a few months, and not supporting it saves
us a whole lot of trouble. If you are interested in adding support, get in touch, we're happy to accept
a PR.
Contributing
------------
Please contribute! Tether is developed in Coffeescript, but if that's problematic for you, feel free
to submit pull requests which just change the JavaScript files, we can adapt them as needed.
To build Tether, you need:
- Node.js
#### Instructions
- Install the build tool
```bash
npm install -g gulp
```
- Install the project
```bash
# In the project directory
npm install
```
- Build / Watch
```bash
gulp
```
+117
View File
@@ -0,0 +1,117 @@
(function() {
var OUTPUT_HTML, SETUP_JS, activate, deactivate, getOutput, init, run, setupBlock, tethers, uniqueId;
uniqueId = Tether.Utils.uniqueId;
SETUP_JS = "yellowBox = $('.yellow-box', $output);\ngreenBox = $('.green-box', $output);\nscrollBox = $('.scroll-box', $output);";
OUTPUT_HTML = function(key) {
return "<div class=\"scroll-box\">\n <div class=\"scroll-content\">\n <div class=\"yellow-box\" data-example=\"" + key + "\"></div>\n <div class=\"green-box\" data-example=\"" + key + "\"></div>\n </div>\n</div>";
};
tethers = {};
getOutput = function($block) {
var key;
key = $block.data('example');
if (key && typeof key === 'string') {
return $("output[data-example='" + key + "']");
} else {
return $block.parents('pre').nextAll('output').first();
}
};
run = function(key) {
var $block, $output, code;
if (typeof key === 'string') {
$block = $("code[data-example='" + key + "']");
} else {
$block = key;
}
key = $block.attr('data-example');
$output = getOutput($block);
code = $block.text();
code = SETUP_JS + code;
window.$output = $output;
return tethers[key] = eval(code);
};
setupBlock = function($block) {
var $output, $scrollBox, $scrollContent, key;
key = $block.data('example');
$output = getOutput($block);
if (!key) {
key = uniqueId();
$block.attr('data-example', key);
$output.attr('data-example', key);
$output.find('.tether-element').attr('data-example', key);
}
$output.html(OUTPUT_HTML(key));
$scrollBox = $output.find('.scroll-box');
$scrollContent = $scrollBox.find('.scroll-content');
$scrollBox.scrollTop(parseInt($scrollContent.css('height')) / 2 - $scrollBox.height() / 2);
$scrollBox.scrollLeft(parseInt($scrollContent.css('width')) / 2 - $scrollBox.width() / 2);
setTimeout(function() {
return $scrollBox.on('scroll', function() {
return $output.addClass('scrolled');
});
});
$scrollBox.css('height', "" + ($block.parent().outerHeight()) + "px");
if ($output.attr('deactivated') == null) {
return run($block);
}
};
$(document.body).on('click', function(e) {
if ($(e.target).is('output[deactivated]')) {
activate($(e.target));
return false;
} else if ($(e.target).is('output[activated]')) {
deactivate($(e.target));
return false;
}
});
activate = function($output) {
var $block, key;
$block = $output.prev().find('code');
run($block);
$output.find('.tether-element').show();
key = $output.data('example');
$(tethers[key].element).show();
tethers[key].enable();
$output.removeAttr('deactivated');
return $output.attr('activated', true);
};
deactivate = function($output) {
var $block, $el, key;
$block = $output.prev().find('code');
key = $output.data('example');
tethers[key].disable();
$el = $(tethers[key].element);
$el.detach();
$output.find('.scroll-content').append($el);
$el.hide();
$output.removeAttr('activated');
return $output.attr('deactivated', true);
};
init = function() {
var $blocks, block, _i, _len, _results;
$blocks = $('code[data-example]');
_results = [];
for (_i = 0, _len = $blocks.length; _i < _len; _i++) {
block = $blocks[_i];
_results.push(setupBlock($(block)));
}
return _results;
};
window.EXECUTR_OPTIONS = {
codeSelector: 'code[executable]'
};
$(init);
}).call(this);
+51
View File
@@ -0,0 +1,51 @@
/* globals Tether */
'use strict';
Tether.modules.push({
initialize: function initialize() {
var _this = this;
this.markers = {};
['target', 'element'].forEach(function (type) {
var el = document.createElement('div');
el.className = _this.getClass('' + type + '-marker');
var dot = document.createElement('div');
dot.className = _this.getClass('marker-dot');
el.appendChild(dot);
_this[type].appendChild(el);
_this.markers[type] = { dot: dot, el: el };
});
},
position: function position(_ref) {
var manualOffset = _ref.manualOffset;
var manualTargetOffset = _ref.manualTargetOffset;
var offsets = {
element: manualOffset,
target: manualTargetOffset
};
for (var type in offsets) {
var offset = offsets[type];
for (var side in offset) {
var val = offset[side];
var notString = typeof val !== 'string';
if (notString || val.indexOf('%') === -1 && val.indexOf('px') === -1) {
val += 'px';
}
if (this.markers[type].dot.style[side] !== val) {
this.markers[type].dot.style[side] = val;
}
}
}
return true;
}
});
+233
View File
@@ -0,0 +1,233 @@
$scrollableArea: 2000px
$exampleWidth: 400px
$exampleHeight: 180px
@mixin inline-block
display: inline-block
vertical-align: middle
*vertical-align: auto
*zoom: 1
*display: inline
*, *:after, *:before
box-sizing: border-box
body
position: relative
.yellow-box
width: 100px
height: 100px
background-color: #fe8
pointer-events: none
.green-box
margin-top: ($exampleHeight - 50px) / 2
margin-left: ($exampleWidth - 200px) / 2
width: 200px
height: 50px
background-color: #4e9
.no-green &
display: none
.scroll-box
height: 150px
border: 10px solid #eee
background: #fbfbfb
overflow: auto
position: relative
.scroll-content
height: $scrollableArea
width: $scrollableArea
padding: ($scrollableArea - $exampleHeight)/2 ($scrollableArea - $exampleWidth)/2 + 9
pre.pre-with-output
margin: 0
width: 50%
float: left
code mark
background: #b8daff
color: #000
p, h2, h3
clear: both
output
display: block
position: relative
width: 50%
float: right
margin-bottom: 15px
&.scroll-page
.scroll-box
overflow: hidden
&:after
content: "↕ scroll the page ↕"
&:after
content: "↕ scroll this area ↕"
position: absolute
bottom: 25px
width: 100%
text-align: center
font-size: 16px
font-variant: small-caps
color: #777
opacity: 1
transition: opacity 0.2s
&.scrolled:after
opacity: 0
&[deactivated], &[activated]
.scroll-box
pointer-events: none
cursor: pointer
&:after
position: absolute
top: 0
left: 0
right: 0
bottom: 0
opacity: 1
content: "Click To Show"
background-color: #AAA
border-left: 10px solid #EEE
color: white
font-size: 24px
font-variant: normal
padding-top: 80px
&[activated]
&:after
content: "Click To Hide"
&.visible-enabled
&:after
height: 35px
padding-top: 5px
.attachment-mark
position: relative
&:after
content: "A"
width: 10px
height: 10px
background-color: red
display: inline-block
line-height: 10px
font-size: 9px
color: white
text-align: center
position: absolute
span.attachment-mark
&:after
position: relative
top: -1px
margin-right: 1px
.tether-marker-dot
@extend .attachment-mark
position: absolute
&:after
top: -5px
left: -5px
@each $type in target, element
.tether-#{ $type }-marker
position: absolute
@each $side in left, top, bottom, right
div.tether-#{ $type }-attached-#{ $side } &
#{ $side }: 0
div.tether-#{ $type }-attached-center &
left: 50%
.tether-element-attached-middle .tether-element-marker
top: 50px
.tether-target-attached-middle .tether-target-marker
top: 25px
.tether-element
position: relative
&.tether-pinned-left
box-shadow: inset 2px 0 0 0 red
&.tether-pinned-right
box-shadow: inset -2px 0 0 0 red
&.tether-pinned-top
box-shadow: inset 0 2px 0 0 red
&.tether-pinned-bottom
box-shadow: inset 0 -2px 0 0 red
.tether-target
position: relative
.tether-element.tether-out-of-bounds[data-example="hide"]
display: none
[data-example^="optimizer"]
&.lang-javascript
/* This should just be a `code` selector, but sass doesn't allow that with & */
min-height: 220px
&.tether-element
&:before
margin-top: 26px
display: block
text-align: center
content: "I'm in the body"
line-height: 1.2
font-size: 15px
padding: 4px
color: #666
.scroll-box .tether-element:before
content: "I'm in my scroll parent!"
.tether-element[data-example="scroll-visible"]
height: 30px
.tether-marker-dot
display: none
.hs-doc-content h2.projects-header
text-align: center
font-weight: 300
.projects-paragraph
text-align: center
a
+inline-block
text-align: center
margin-right: 30px
color: inherit
span
+inline-block
margin-bottom: 20px
font-size: 20px
color: inherit
font-weight: 300
img
display: block
max-width: 100%
width: 100px
+76
View File
@@ -0,0 +1,76 @@
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Drop Browser Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="icon" href="http://static.hubspot.com/favicon.ico">
<script type="text/javascript" src="//use.typekit.net/ghy0wve.js"></script>
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>
<!-- Drop themes -->
<link rel="stylesheet" href="//github.hubspot.com/tether/dist/css/tether-theme-arrows-dark.css" />
<!-- Browser demo styles -->
<link rel="stylesheet" href="//github.hubspot.com/tether/docs/welcome/css/browser-demo.css" />
</head>
<body>
<div class="browser-demo">
<div class="top"><div class="title"></div></div>
<div class="bottom">
<div class="left">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="right">
<div class="title"></div>
<p>
<div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div>
</p>
<p>
<div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div><div class="word"></div>
</p>
</div>
</div>
</div>
<!-- Tether javascript -->
<script src="//github.hubspot.com/tether/dist/js/tether.min.js"></script>
<!-- Welcome docs javascript -->
<script src="//github.hubspot.com/tether/docs/welcome/js/log.js"></script>
<script src="//github.hubspot.com/tether/docs/welcome/js/jquery.js"></script>
<script src="//github.hubspot.com/tether/docs/welcome/js/drop.js"></script>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
_Drop = Drop.createContext classPrefix: 'tether'
isMobile = $(window).width() < 567
init = ->
setupHero()
setupBrowserDemo()
setupHero = ->
$target = $('.tether-target-demo')
positions = [
'top left'
'left top'
'left middle'
'left bottom'
'bottom left'
'bottom center'
'bottom right'
'right bottom'
'right middle'
'right top'
'top right'
'top center'
]
if isMobile
positions = [
'top left'
'bottom left'
'bottom right'
'top right'
]
window.drops = {}
for position in positions
drops[position] = new _Drop
target: $target[0]
classes: 'tether-theme-arrows-dark'
position: position
constrainToWindow: false
openOn: ''
content: '<div style="height: 50px; width: 50px"></div>'
openIndex = 0
frames = 0
frameLengthMS = 10
openAllDrops = ->
for position, drop of drops
drop.open()
openNextDrop = ->
for position, drop of drops
drop.close()
drops[positions[openIndex]].open()
drops[positions[(openIndex + 6) % positions.length]].open()
openIndex = (openIndex + 1) % positions.length
if frames > 5
finalDropState()
return
frames += 1
setTimeout openNextDrop, frameLengthMS * frames
finalDropState = ->
$(drops['top left'].dropContent).html('Marrying DOM elements for life.')
$(drops['bottom right'].dropContent).html('<a class="button" href="http://github.com/HubSpot/tether">★ On Github</a>')
drops['top left'].open()
drops['bottom right'].open()
if true or isMobile
drops['top left'].open()
drops['top left'].tether.position()
drops['bottom right'].open()
drops['bottom right'].tether.position()
finalDropState()
else
openNextDrop()
setupBrowserDemo = ->
$browserDemo = $('.browser-demo.showcase')
$startPoint = $('.browser-demo-start-point')
$stopPoint = $('.browser-demo-stop-point')
$iframe = $('.browser-window iframe')
$browserContents = $('.browser-content .browser-demo-inner')
$sections = $('.browser-demo-section')
$('body').append """
<style>
table.showcase.browser-demo.fixed-bottom {
top: #{ $sections.length }00%
}
</style>
"""
$(window).scroll ->
scrollTop = $(window).scrollTop()
if $startPoint.position().top < scrollTop and scrollTop + window.innerHeight < $stopPoint.position().top
$browserDemo.removeClass('fixed-bottom')
$browserDemo.addClass('fixed')
$sections.each ->
$section = $ @
if $section.position().top < scrollTop < $section.position().top + $section.outerHeight()
setSection $section.data('section')
return true
else
$browserDemo.removeAttr('data-section')
$browserDemo.removeClass('fixed')
if scrollTop + window.innerHeight > $stopPoint.position().top
$browserDemo.addClass('fixed-bottom')
else
$browserDemo.removeClass('fixed-bottom')
$iframe.load ->
iframeWindow = $iframe[0].contentWindow
$items = $iframe.contents().find('.item')
$items.each (i) ->
$item = $(@)
_iframeWindowDrop = iframeWindow.Drop.createContext classPrefix: 'tether'
drop = new _iframeWindowDrop
target: $item[0]
classes: 'tether-theme-arrows-dark'
position: 'right top'
constrainToWindow: true
openOn: 'click'
content: '''
<ul>
<li>Action&nbsp;1</li>
<li>Action&nbsp;2</li>
<li>Action&nbsp;3</li>
</ul>
'''
$item.data('drop', drop)
scrollInterval = undefined
scrollTop = 0
scrollTopDirection = 1
setSection = (section) ->
$browserDemo.attr('data-section', section)
$('.section-copy').removeClass('active')
$(""".section-copy[data-section="#{ section }"]""").addClass('active')
openExampleItem = ->
if isMobile
$iframe.contents().find('.item:first').data().drop.open()
else
$iframe.contents().find('.item:eq(2)').data().drop.open()
closeAllItems = ->
$iframe.contents().find('.item').each -> $(@).data().drop.close() or true
scrollLeftSection = ->
scrollInterval = setInterval ->
$iframe.contents().find('.left').scrollTop scrollTop
scrollTop += scrollTopDirection
if scrollTop > 50
scrollTopDirection = -1
if scrollTop < 0
scrollTopDirection = 1
, 30
stopScrollingLeftSection = ->
clearInterval scrollInterval
switch section
when 'what'
closeAllItems()
openExampleItem()
stopScrollingLeftSection()
when 'how'
closeAllItems()
openExampleItem()
stopScrollingLeftSection()
scrollLeftSection()
when 'why'
closeAllItems()
openExampleItem()
stopScrollingLeftSection()
scrollLeftSection()
when 'outro'
closeAllItems()
openExampleItem()
stopScrollingLeftSection()
init()
+86
View File
@@ -0,0 +1,86 @@
html, body {
height: 100%;
overflow: hidden;
font-family: "proxima-nova", sans-serif; }
.tether.tether-theme-arrows-dark .tether-content {
-webkit-filter: none;
filter: none;
background: #000; }
.tether.tether-theme-arrows-dark .tether-content ul {
color: #fff;
list-style: none;
padding: 0;
margin: 0; }
.tether.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content:before {
border-right-color: #000; }
.browser-demo {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0; }
.browser-demo *, .browser-demo *:after, .browser-demo *:before {
box-sizing: border-box; }
.browser-demo .top {
position: absolute;
height: 60px;
padding: 20px;
line-height: 40px;
width: 100%;
border-bottom: 1px solid rgba(0, 0, 0, 0.1); }
.browser-demo .bottom {
position: absolute;
top: 60px;
bottom: 0;
width: 100%; }
.browser-demo .bottom .left {
border-right: 1px solid rgba(0, 0, 0, 0.1);
position: absolute;
width: 30%;
height: 100%;
overflow: auto; }
.browser-demo .bottom .left .item {
height: 64px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
cursor: pointer; }
.browser-demo .bottom .left .item:hover, .browser-demo .bottom .left .item.tether-open {
background: rgba(0, 0, 0, 0.1);
border-bottom: 1px solid transparent; }
.browser-demo .bottom .left .item:last-child {
border-bottom: 0; }
.browser-demo .bottom .right {
position: absolute;
width: 70%;
right: 0;
height: 100%;
padding: 20px; }
.browser-demo .title {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
background: rgba(0, 0, 0, 0.1);
width: 150px;
height: 15px;
margin-bottom: 20px; }
.browser-demo .word {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
background: rgba(0, 0, 0, 0.1);
width: 50px;
height: 8px;
margin-right: 5px;
margin-bottom: 5px; }
.browser-demo .word:nth-last-child(4n+1) {
width: 73px; }
.browser-demo .word:nth-last-child(10n+1) {
width: 14px; }
.browser-demo .word:nth-last-child(9n+1) {
width: 80px; }
+2
View File
@@ -0,0 +1,2 @@
/* Prism.js */
code[class*="language-"], pre[class*="language-"] {color: black; font-family: Consolas, Monaco, 'Andale Mono', monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } /* Code blocks */ pre[class*="language-"] {padding: 1em; margin: .5em 0; overflow: auto; font-size: 14px; } :not(pre) > code[class*="language-"], pre[class*="language-"] {background: rgba(0, 0, 0, .05); } /* Inline code */ :not(pre) > code[class*="language-"] {padding: .1em; border-radius: .3em; } .token.comment, .token.prolog, .token.doctype, .token.cdata {color: slategray; } .token.punctuation {color: #999; } .namespace {opacity: .7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol {color: #905; } .token.selector, .token.attr-name, .token.string, .token.builtin {color: #690; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string, .token.variable {color: #a67f59; } .token.atrule, .token.attr-value, .token.keyword {color: #07a; } .token.regex, .token.important {color: #e90; } .token.important {font-weight: bold; } .token.entity {cursor: help; }
+247
View File
@@ -0,0 +1,247 @@
html, body {
height: 100%; }
body {
margin: 0;
font-family: "proxima-nova", "Helvetica Neue", sans-serif; }
.button {
display: inline-block;
border: 2px solid #333;
color: #333;
padding: 1em 1.25em;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 3px;
text-decoration: none;
cursor: pointer;
width: 140px;
font-size: .8em;
line-height: 1.3em;
text-align: center; }
.tether-element.tether-theme-arrows-dark .tether-content {
padding: 1em;
font-size: 1.1em; }
.tether-element.tether-theme-arrows-dark .tether-content .button {
border-color: #fff;
color: #fff;
width: 170px;
pointer-events: all; }
.mobile-copy {
display: none; }
@media (max-width: 568px) {
.mobile-copy {
display: block; } }
.button.dark {
background: #333;
color: #fff; }
.hero-wrap {
height: 100%;
overflow: hidden; }
table.showcase {
height: 100%;
width: 100%;
position: relative; }
table.showcase:after {
content: "";
display: block;
position: absolute;
left: 0;
right: 0;
bottom: 20px;
margin: auto;
height: 0;
width: 0;
border-width: 18px;
border-style: solid;
border-color: transparent;
border-top-color: rgba(0, 0, 0, 0.2); }
table.showcase.no-next-arrow:after {
display: none; }
table.showcase .showcase-inner {
margin: 40px auto 60px;
padding: 10px; }
table.showcase .showcase-inner h1 {
font-size: 50px;
text-align: center;
font-weight: 300; }
@media (max-width: 567px) {
table.showcase .showcase-inner h1 {
font-size: 40px; } }
table.showcase .showcase-inner h2 {
font-size: 24px;
text-align: center;
font-weight: 300;
margin: 1em 0 1em; }
@media (max-width: 567px) {
table.showcase .showcase-inner h2 {
font-size: 14px; } }
table.showcase .showcase-inner p {
text-align: center; }
table.showcase.hero {
text-align: center; }
table.showcase.hero .tether-target-demo {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border: 2px dotted #000;
margin: 5rem auto;
padding: 5rem; }
@media (max-width: 567px) {
table.showcase.hero .tether-target-demo {
padding: 1rem; } }
table.showcase.share {
background: #f3f3f3; }
table.showcase.projects-showcase .showcase-inner .projects-list {
width: 80%;
max-width: 1200px;
margin: 0 auto; }
table.showcase.projects-showcase .showcase-inner .projects-list .project {
color: inherit;
text-decoration: none;
position: relative;
width: 50%;
float: left;
text-align: center;
margin-bottom: 2rem; }
table.showcase.projects-showcase .showcase-inner .projects-list .project:nth-child(odd) {
clear: left; }
table.showcase.projects-showcase .showcase-inner .projects-list .os-icon {
width: 8rem;
height: 8rem;
margin-bottom: 1rem;
background-size: 100%; }
table.showcase.projects-showcase .showcase-inner .projects-list h1 {
font-size: 2.5rem; }
table.showcase.projects-showcase .showcase-inner .projects-list p {
font-size: 1.3rem; }
table.showcase.browser-demo {
background-image: -webkit-linear-gradient(top left, #723362 0%, #9d223c 100%);
background-image: linear-gradient(top left, #723362 0%, #9d223c 100%);
background-color: #9d223c;
position: absolute;
top: 100%; }
table.showcase.browser-demo.fixed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1; }
table.showcase.browser-demo.fixed .browser-demo-inner {
-webkit-transition: width 2s ease-in-out, height 2s ease-in-out;
transition: width 2s ease-in-out, height 2s ease-in-out; }
table.showcase.browser-demo.fixed[data-section="what"] {
box-shadow: 0 0 0 0; }
table.showcase.browser-demo.fixed[data-section="why"] .browser-demo-inner {
width: 70%; }
table.showcase.browser-demo.fixed[data-section="outro"] .showcase-inner {
pointer-events: all; }
table.showcase.browser-demo .showcase-inner {
pointer-events: none;
position: absolute;
left: 10%;
right: 40%;
top: 220px;
bottom: 120px;
margin: 0;
padding: 0; }
@media (max-width: 567px) {
table.showcase.browser-demo .showcase-inner {
bottom: 90px;
top: 180px; } }
table.showcase.browser-demo .browser-demo-inner {
height: 100%;
width: 100%; }
table.showcase.browser-demo .section-copy {
-webkit-transition: opacity 0.5s ease-in-out, top 0.5s ease-in-out;
transition: opacity 0.5s ease-in-out, top 0.5s ease-in-out;
opacity: 0;
position: absolute;
top: 0;
position: absolute;
height: 200px;
color: #fff;
text-align: center;
width: 100%; }
table.showcase.browser-demo .section-copy.active {
opacity: 1;
top: -150px; }
@media (max-width: 567px) {
table.showcase.browser-demo .section-copy.active {
top: -130px; } }
table.showcase.browser-demo .section-copy h2 {
font-size: 40px;
font-weight: bold;
line-height: 1;
margin: 25px 0 15px; }
@media (max-width: 567px) {
table.showcase.browser-demo .section-copy h2 {
font-size: 30px; } }
table.showcase.browser-demo .browser-window {
border-radius: 4px;
background: #fff;
position: relative;
height: 100%;
width: 100%;
max-width: 1200px;
margin: 0 auto; }
table.showcase.browser-demo .browser-window .browser-titlebar {
position: absolute;
top: 0;
left: 0;
right: 0;
border-bottom: 1px solid #eee;
height: 55px; }
table.showcase.browser-demo .browser-window .browser-titlebar .browser-dots {
padding: 16px; }
table.showcase.browser-demo .browser-window .browser-titlebar .browser-dots b {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 50%;
width: 10px;
height: 10px;
margin-right: 7px;
background: rgba(0, 0, 0, 0.1); }
table.showcase.browser-demo .browser-window .browser-frame {
position: absolute;
top: 55px;
left: 0;
right: 0;
bottom: 0; }
table.showcase.browser-demo .browser-window .browser-frame iframe {
border-radius: 0 0 4px 4px;
border: 0;
width: 100%;
height: 100%; }
table.showcase.browser-demo-section .section-scroll-copy {
position: relative;
z-index: 10;
color: #fff;
width: 100%;
font-size: 22px; }
table.showcase.browser-demo-section .section-scroll-copy .section-scroll-copy-inner {
position: absolute;
z-index: 10;
color: #fff;
right: 10%;
width: 23%; }
table.showcase.browser-demo-section .section-scroll-copy .section-scroll-copy-inner a {
color: inherit; }
table.showcase.browser-demo-section .section-scroll-copy .section-scroll-copy-inner .example-paragraph {
border-radius: 4px;
background: #000;
padding: 1rem; }
.browser-content {
display: none; }
+226
View File
@@ -0,0 +1,226 @@
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Tether Marrying elements for life</title>
<meta name="description" content="Tether is a JavaScript and CSS library. It is free and open source and was developed by HubSpot developers Adam Schwartz (@adamfschwartz) and Zack Bloom (@zackbloom).">
<link rel="icon" href="http://static.hubspot.com/favicon.ico">
<script type="text/javascript" src="//use.typekit.net/ghy0wve.js"></script>
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>
<!-- Tether themes -->
<link rel="stylesheet" href="//github.hubspot.com/tether/dist/css/tether-theme-arrows-dark.css" />
<!-- Welcome docs styles -->
<link rel="stylesheet" href="//github.hubspot.com/tether/docs/welcome/css/prism.css" />
<link rel="stylesheet" href="//github.hubspot.com/tether/docs/welcome/css/welcome.css" />
<!-- OS icons -->
<link rel="stylesheet" href="http://github.hubspot.com/os-icons/os-icons.css" />
</head>
<body>
<div class="hero-wrap">
<table class="showcase hero"><tr><td>
<div class="showcase-inner">
<div class="tether-target-demo">
<h1>Tether</h1>
<div class="mobile-copy">
<h2>Marrying elements for life</h2>
<p>
<a class="button" href="http://github.com/HubSpot/tether">★ On Github</a>
</p>
</div>
</div>
</div>
</td></tr></table>
</div>
<div class="browser-demo-start-point"></div>
<table class="showcase browser-demo"><tr><td>
<div class="showcase-inner">
<div class="section-copy" data-section="what">
<h2>What is Tether?</h2>
</div>
<div class="section-copy" data-section="how">
<h2>How Tether works.</h2>
</div>
<div class="section-copy" data-section="why">
<h2>Tether is powerful.</h2>
</div>
<div class="section-copy" data-section="outro">
<h2>Play with Tether</h2>
</div>
<div class="browser-demo-inner">
<div class="browser-window">
<div class="browser-titlebar">
<div class="browser-dots"><b></b><b></b><b></b></div>
</div>
<div class="browser-frame">
<iframe src="browser-demo.html"></iframe>
</div>
</div>
</div>
</div>
</td></tr></table>
<table class="showcase browser-demo-section no-next-arrow" data-section="what"><tr><td>
<div class="section-scroll-copy">
<div class="section-scroll-copy-inner">
<p>Tether is a low-level UI library that can be used to position any element on a page <i>next to any other element</i>.</p>
<p>It can be used for dropdown menus, tooltips, popovers, tours, help information, scroll guides, autocompletes, etc. The possibilities are endless.</p>
<p class="example-paragraph">In this example we're showing an action menu <em>tethered</em> to a list item.</p>
</div>
</div>
</td></tr></table>
<table class="showcase browser-demo-section no-next-arrow" data-section="how"><tr><td>
<div class="section-scroll-copy">
<div class="section-scroll-copy-inner">
<p>Tether works by creating an absolutely positioned element and meticulously tracking the movements of a <i>target</i> which you specify.</p>
<p>The <i>target</i> and <i>element</i> can be tethered together in a variety of different ways.</p>
<p class="example-paragraph">Notice how the <i>tethered element</i> stays tethered to its <i>target</i> list item even as the left pane is scrolled up and down.</p>
</div>
</div>
</td></tr></table>
<table class="showcase browser-demo-section no-next-arrow" data-section="why"><tr><td>
<div class="section-scroll-copy">
<div class="section-scroll-copy-inner">
<p>Tether can keep your element positioned properly even in some tough situations.</p>
<p>Tether handles all of the common pain points:</p>
<ul>
<li>Automatically detect collisions with the edge of the page or edge of the scrollParent</li>
<li>Automatically reposition on browser resize, scroll, and other events,</li>
<li>Constrain the position to any bounding box,</li>
</ul>
<p>...and a lot more.</p>
</div>
</div>
</td></tr></table>
<table class="showcase browser-demo-section no-next-arrow" data-section="outro"><tr><td>
<div class="section-scroll-copy">
<div class="section-scroll-copy-inner">
<p class="example-paragraph">Interact with this demo.</p>
<p>&nbsp;</p>
<p>To learn more, check out our <a href="/">documentation</a>.</p>
</div>
</div>
</td></tr></table>
<table class="showcase browser-demo-section no-next-arrow" data-section="__empty"><tr><td></td></tr></table>
<div class="browser-demo-stop-point"></div>
<table class="showcase projects-showcase no-next-arrow"><tr><td>
<div class="showcase-inner">
<h1>Tether Family</h1>
<h2>These projects are all powered by Tether's positioning engine.</h2>
<div class="projects-list">
<a href="//github.hubspot.com/drop/docs/welcome" class="project">
<h1>Drop</h1>
<span class="os-icon drop-icon"></span>
<p>Create dropdowns, popovers, and more.</p>
</a>
<a href="//github.hubspot.com/tooltip/docs/welcome" class="project">
<h1>Tooltip</h1>
<span class="os-icon tooltip-icon"></span>
<p>Stylable tooltips built on Tether.</p>
</a>
<a href="//github.hubspot.com/select/docs/welcome" class="project">
<h1>Select</h1>
<span class="os-icon select-icon"></span>
<p>Stylable select elements built on Tether.</p>
</a>
<a href="//github.hubspot.com/shepherd/docs/welcome" class="project">
<h1>Shepherd</h1>
<span class="os-icon shepherd-icon"></span>
<p>Guide your users through a tour of your app.</p>
</a>
</div>
</div>
</td></tr></table>
<table class="showcase last-showcase no-next-arrow share"><tr><td>
<div class="showcase-inner">
<h1>Share</h1>
<h2>Help us spread the word.</h2>
<!-- Share -->
<style>
.share-buttons {
margin: 4em auto;
text-align: center;
}
.share-button {
display: inline-block;
}
.retweet-button {
width: 100px;
margin-left: 20px;
}
.github-stars {
width: 100px;
}
</style>
<div class="share-buttons">
<div class="share-button retweet-button">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://github.hubspot.com/tether/docs/welcome" data-text="Tether.js - A positioning engine for JavaScript" data-count="horizontal" data-via="HubSpotDev">Tweet</a>
<script>
(function(){
var recommends, button;
if (Math.random() >= 0.5) {
recommends = ['hubspotdev', 'zackbloom', 'adamfschwartz'];
} else {
recommends = ['hubspotdev', 'adamfschwartz', 'zackbloom'];
}
button = document.querySelector('.twitter-share-button');
if (button) {
button.setAttribute('data-related', recommends.join(','));
}
})();
</script>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
<div class="share-button github-stars-button">
<iframe src="http://ghbtns.com/github-btn.html?user=HubSpot&amp;repo=tether&amp;type=watch&amp;count=true&amp;size=small" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
</div>
</p>
</div>
</td></tr></table>
<!-- Tether javascript -->
<script src="//github.hubspot.com/tether/dist/js/tether.min.js"></script>
<!-- Welcome docs javascript -->
<script src="//github.hubspot.com/tether/docs/welcome/js/log.js"></script>
<script src="//github.hubspot.com/tether/docs/welcome/js/jquery.js"></script>
<script src="//github.hubspot.com/tether/docs/welcome/js/drop.js"></script>
<script src="//github.hubspot.com/tether/docs/welcome/js/welcome.js"></script>
<!-- HubSpot analytics -->
<script type="text/javascript">
(function(d,s,i,r) {
if (d.getElementById(i)){return;}
var n=d.createElement(s),e=d.getElementsByTagName(s)[0];
n.id=i;n.src='//js.hubspot.com/analytics/'+(Math.ceil(new Date()/r)*r)+'/51294.js';
e.parentNode.insertBefore(n, e);
})(document,"script","hs-analytics",300000);
</script>
<!-- Google analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-45159009-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>
+239
View File
@@ -0,0 +1,239 @@
(function() {
var Evented, MIRROR_ATTACH, addClass, allDrops, clickEvent, createContext, extend, hasClass, removeClass, sortAttach, touchDevice, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
_ref = Tether.Utils, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, hasClass = _ref.hasClass, Evented = _ref.Evented;
touchDevice = 'ontouchstart' in document.documentElement;
clickEvent = touchDevice ? 'touchstart' : 'click';
sortAttach = function(str) {
var first, second, _ref1, _ref2;
_ref1 = str.split(' '), first = _ref1[0], second = _ref1[1];
if (first === 'left' || first === 'right') {
_ref2 = [second, first], first = _ref2[0], second = _ref2[1];
}
return [first, second].join(' ');
};
MIRROR_ATTACH = {
left: 'right',
right: 'left',
top: 'bottom',
bottom: 'top',
middle: 'middle',
center: 'center'
};
allDrops = {};
createContext = function(options) {
var DropInstance, defaultOptions, drop, _name;
if (options == null) {
options = {};
}
drop = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(DropInstance, arguments, function(){});
};
extend(drop, {
createContext: createContext,
drops: [],
defaults: {}
});
defaultOptions = {
classPrefix: 'drop',
defaults: {
attach: 'bottom left',
openOn: 'click',
constrainToScrollParent: true,
constrainToWindow: true,
classes: '',
tetherOptions: {}
}
};
extend(drop, defaultOptions, options);
extend(drop.defaults, defaultOptions.defaults, options.defaults);
if (allDrops[_name = drop.classPrefix] == null) {
allDrops[_name] = [];
}
drop.updateBodyClasses = function() {
var anyOpen, _drop, _i, _len, _ref1;
anyOpen = false;
_ref1 = allDrops[drop.classPrefix];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
_drop = _ref1[_i];
if (!(_drop.isOpened())) {
continue;
}
anyOpen = true;
break;
}
if (anyOpen) {
return addClass(document.body, "" + drop.classPrefix + "-open");
} else {
return removeClass(document.body, "" + drop.classPrefix + "-open");
}
};
DropInstance = (function(_super) {
__extends(DropInstance, _super);
function DropInstance(options) {
this.options = options;
this.options = extend({}, drop.defaults, this.options);
this.target = this.options.target;
if (this.target == null) {
throw new Error('Drop Error: You must provide a target.');
}
drop.drops.push(this);
allDrops[drop.classPrefix].push(this);
this.setupElements();
this.setupEvents();
this.setupTether();
}
DropInstance.prototype.setupElements = function() {
this.drop = document.createElement('div');
addClass(this.drop, drop.classPrefix);
if (this.options.classes) {
addClass(this.drop, this.options.classes);
}
this.dropContent = document.createElement('div');
addClass(this.dropContent, "" + drop.classPrefix + "-content");
if (typeof this.options.content === 'object') {
this.dropContent.appendChild(this.options.content);
} else {
this.dropContent.innerHTML = this.options.content;
}
return this.drop.appendChild(this.dropContent);
};
DropInstance.prototype.setupTether = function() {
var constraints, dropAttach;
dropAttach = this.options.position.split(' ');
dropAttach[0] = MIRROR_ATTACH[dropAttach[0]];
dropAttach = dropAttach.join(' ');
constraints = [];
if (this.options.constrainToScrollParent) {
constraints.push({
to: 'scrollParent',
pin: 'top, bottom',
attachment: 'together none'
});
}
if (this.options.constrainToWindow !== false) {
constraints.push({
to: 'window',
pin: true,
attachment: 'together'
});
}
constraints.push({
to: 'scrollParent'
});
options = {
element: this.drop,
target: this.target,
attachment: sortAttach(dropAttach),
targetAttachment: sortAttach(this.options.position),
classPrefix: drop.classPrefix,
offset: '0 0',
targetOffset: '0 0',
enabled: false,
constraints: constraints
};
if (this.options.tether !== false) {
return this.tether = new Tether(extend({}, options, this.options.tether));
}
};
DropInstance.prototype.setupEvents = function() {
var events,
_this = this;
if (!this.options.openOn) {
return;
}
events = this.options.openOn.split(' ');
if (__indexOf.call(events, 'click') >= 0) {
this.target.addEventListener(clickEvent, function() {
return _this.toggle();
});
document.addEventListener(clickEvent, function(event) {
if (!_this.isOpened()) {
return;
}
if (event.target === _this.drop || _this.drop.contains(event.target)) {
return;
}
if (event.target === _this.target || _this.target.contains(event.target)) {
return;
}
return _this.close();
});
}
if (__indexOf.call(events, 'hover') >= 0) {
this.target.addEventListener('mouseover', function() {
return _this.open();
});
return this.target.addEventListener('mouseout', function() {
return _this.close();
});
}
};
DropInstance.prototype.isOpened = function() {
return hasClass(this.drop, "" + drop.classPrefix + "-open");
};
DropInstance.prototype.toggle = function() {
if (this.isOpened()) {
return this.close();
} else {
return this.open();
}
};
DropInstance.prototype.open = function() {
var _ref1;
if (!this.drop.parentNode) {
document.body.appendChild(this.drop);
}
addClass(this.target, "" + drop.classPrefix + "-open");
addClass(this.drop, "" + drop.classPrefix + "-open");
if ((_ref1 = this.tether) != null) {
_ref1.enable();
}
this.trigger('open');
return drop.updateBodyClasses();
};
DropInstance.prototype.close = function() {
var _ref1;
removeClass(this.target, "" + drop.classPrefix + "-open");
removeClass(this.drop, "" + drop.classPrefix + "-open");
this.trigger('close');
if ((_ref1 = this.tether) != null) {
_ref1.disable();
}
return drop.updateBodyClasses();
};
return DropInstance;
})(Evented);
return drop;
};
window.Drop = createContext();
document.addEventListener('DOMContentLoaded', function() {
return Drop.updateBodyClasses();
});
}).call(this);
+9597
View File
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
(function() {
var ffSupport, formats, getOrderedMatches, hasMatches, isFF, isIE, isOpera, isSafari, log, makeArray, operaSupport, safariSupport, stringToArgs, _log;
if (!(window.console && window.console.log)) {
return;
}
log = function() {
var args;
args = [];
makeArray(arguments).forEach(function(arg) {
if (typeof arg === 'string') {
return args = args.concat(stringToArgs(arg));
} else {
return args.push(arg);
}
});
return _log.apply(window, args);
};
_log = function() {
return console.log.apply(console, makeArray(arguments));
};
makeArray = function(arrayLikeThing) {
return Array.prototype.slice.call(arrayLikeThing);
};
formats = [
{
regex: /\*([^\*]+)\*/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['font-style: italic', ''];
}
}, {
regex: /\_([^\_]+)\_/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['font-weight: bold', ''];
}
}, {
regex: /\`([^\`]+)\`/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['background: rgb(255, 255, 219); padding: 1px 5px; border: 1px solid rgba(0, 0, 0, 0.1)', ''];
}
}, {
regex: /\[c\=(?:\"|\')?((?:(?!(?:\"|\')\]).)*)(?:\"|\')?\]((?:(?!\[c\]).)*)\[c\]/,
replacer: function(m, p1, p2) {
return "%c" + p2 + "%c";
},
styles: function(match) {
return [match[1], ''];
}
}
];
hasMatches = function(str) {
var _hasMatches;
_hasMatches = false;
formats.forEach(function(format) {
if (format.regex.test(str)) {
return _hasMatches = true;
}
});
return _hasMatches;
};
getOrderedMatches = function(str) {
var matches;
matches = [];
formats.forEach(function(format) {
var match;
match = str.match(format.regex);
if (match) {
return matches.push({
format: format,
match: match
});
}
});
return matches.sort(function(a, b) {
return a.match.index - b.match.index;
});
};
stringToArgs = function(str) {
var firstMatch, matches, styles;
styles = [];
while (hasMatches(str)) {
matches = getOrderedMatches(str);
firstMatch = matches[0];
str = str.replace(firstMatch.format.regex, firstMatch.format.replacer);
styles = styles.concat(firstMatch.format.styles(firstMatch.match));
}
return [str].concat(styles);
};
isSafari = function() {
return /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
};
isOpera = function() {
return /OPR/.test(navigator.userAgent) && /Opera/.test(navigator.vendor);
};
isFF = function() {
return /Firefox/.test(navigator.userAgent);
};
isIE = function() {
return /MSIE/.test(navigator.userAgent);
};
safariSupport = function() {
var m;
m = navigator.userAgent.match(/AppleWebKit\/(\d+)\.(\d+)(\.|\+|\s)/);
if (!m) {
return false;
}
return 537.38 <= parseInt(m[1], 10) + (parseInt(m[2], 10) / 100);
};
operaSupport = function() {
var m;
m = navigator.userAgent.match(/OPR\/(\d+)\./);
if (!m) {
return false;
}
return 15 <= parseInt(m[1], 10);
};
ffSupport = function() {
return window.console.firebug || window.console.exception;
};
if (isIE() || (isFF() && !ffSupport()) || (isOpera() && !operaSupport()) || (isSafari() && !safariSupport())) {
window.log = _log;
} else {
window.log = log;
}
window.log.l = _log;
}).call(this);
File diff suppressed because one or more lines are too long
+193
View File
@@ -0,0 +1,193 @@
(function() {
var init, isMobile, setupBrowserDemo, setupHero, _Drop;
_Drop = Drop.createContext({
classPrefix: 'tether'
});
isMobile = $(window).width() < 567;
init = function() {
setupHero();
return setupBrowserDemo();
};
setupHero = function() {
var $target, finalDropState, frameLengthMS, frames, openAllDrops, openIndex, openNextDrop, position, positions, _i, _len;
$target = $('.tether-target-demo');
positions = ['top left', 'left top', 'left middle', 'left bottom', 'bottom left', 'bottom center', 'bottom right', 'right bottom', 'right middle', 'right top', 'top right', 'top center'];
if (isMobile) {
positions = ['top left', 'bottom left', 'bottom right', 'top right'];
}
window.drops = {};
for (_i = 0, _len = positions.length; _i < _len; _i++) {
position = positions[_i];
drops[position] = new _Drop({
target: $target[0],
classes: 'tether-theme-arrows-dark',
position: position,
constrainToWindow: false,
openOn: '',
content: '<div style="height: 50px; width: 50px"></div>'
});
}
openIndex = 0;
frames = 0;
frameLengthMS = 10;
openAllDrops = function() {
var drop, _results;
_results = [];
for (position in drops) {
drop = drops[position];
_results.push(drop.open());
}
return _results;
};
openNextDrop = function() {
var drop;
for (position in drops) {
drop = drops[position];
drop.close();
}
drops[positions[openIndex]].open();
drops[positions[(openIndex + 6) % positions.length]].open();
openIndex = (openIndex + 1) % positions.length;
if (frames > 5) {
finalDropState();
return;
}
frames += 1;
return setTimeout(openNextDrop, frameLengthMS * frames);
};
finalDropState = function() {
$(drops['top left'].dropContent).html('Marrying DOM elements for life.');
$(drops['bottom right'].dropContent).html('<a class="button" href="http://github.com/HubSpot/tether">★ On Github</a>');
drops['top left'].open();
return drops['bottom right'].open();
};
if (true || isMobile) {
drops['top left'].open();
drops['top left'].tether.position();
drops['bottom right'].open();
drops['bottom right'].tether.position();
return finalDropState();
} else {
return openNextDrop();
}
};
setupBrowserDemo = function() {
var $browserContents, $browserDemo, $iframe, $sections, $startPoint, $stopPoint, scrollInterval, scrollTop, scrollTopDirection, setSection;
$browserDemo = $('.browser-demo.showcase');
$startPoint = $('.browser-demo-start-point');
$stopPoint = $('.browser-demo-stop-point');
$iframe = $('.browser-window iframe');
$browserContents = $('.browser-content .browser-demo-inner');
$sections = $('.browser-demo-section');
$('body').append("<style>\n table.showcase.browser-demo.fixed-bottom {\n top: " + $sections.length + "00%\n }\n</style>");
$(window).scroll(function() {
var scrollTop;
scrollTop = $(window).scrollTop();
if ($startPoint.position().top < scrollTop && scrollTop + window.innerHeight < $stopPoint.position().top) {
$browserDemo.removeClass('fixed-bottom');
$browserDemo.addClass('fixed');
return $sections.each(function() {
var $section;
$section = $(this);
if (($section.position().top < scrollTop && scrollTop < $section.position().top + $section.outerHeight())) {
setSection($section.data('section'));
}
return true;
});
} else {
$browserDemo.removeAttr('data-section');
$browserDemo.removeClass('fixed');
if (scrollTop + window.innerHeight > $stopPoint.position().top) {
return $browserDemo.addClass('fixed-bottom');
} else {
return $browserDemo.removeClass('fixed-bottom');
}
}
});
$iframe.load(function() {
var $items, iframeWindow;
iframeWindow = $iframe[0].contentWindow;
$items = $iframe.contents().find('.item');
return $items.each(function(i) {
var $item, drop, _iframeWindowDrop;
$item = $(this);
_iframeWindowDrop = iframeWindow.Drop.createContext({
classPrefix: 'tether'
});
drop = new _iframeWindowDrop({
target: $item[0],
classes: 'tether-theme-arrows-dark',
position: 'right top',
constrainToWindow: true,
openOn: 'click',
content: '<ul>\n <li>Action&nbsp;1</li>\n <li>Action&nbsp;2</li>\n <li>Action&nbsp;3</li>\n</ul>'
});
return $item.data('drop', drop);
});
});
scrollInterval = void 0;
scrollTop = 0;
scrollTopDirection = 1;
return setSection = function(section) {
var closeAllItems, openExampleItem, scrollLeftSection, stopScrollingLeftSection;
$browserDemo.attr('data-section', section);
$('.section-copy').removeClass('active');
$(".section-copy[data-section=\"" + section + "\"]").addClass('active');
openExampleItem = function() {
if (isMobile) {
return $iframe.contents().find('.item:first').data().drop.open();
} else {
return $iframe.contents().find('.item:eq(2)').data().drop.open();
}
};
closeAllItems = function() {
return $iframe.contents().find('.item').each(function() {
return $(this).data().drop.close() || true;
});
};
scrollLeftSection = function() {
return scrollInterval = setInterval(function() {
$iframe.contents().find('.left').scrollTop(scrollTop);
scrollTop += scrollTopDirection;
if (scrollTop > 50) {
scrollTopDirection = -1;
}
if (scrollTop < 0) {
return scrollTopDirection = 1;
}
}, 30);
};
stopScrollingLeftSection = function() {
return clearInterval(scrollInterval);
};
switch (section) {
case 'what':
closeAllItems();
openExampleItem();
return stopScrollingLeftSection();
case 'how':
closeAllItems();
openExampleItem();
stopScrollingLeftSection();
return scrollLeftSection();
case 'why':
closeAllItems();
openExampleItem();
stopScrollingLeftSection();
return scrollLeftSection();
case 'outro':
closeAllItems();
openExampleItem();
return stopScrollingLeftSection();
}
};
};
init();
}).call(this);
+6
View File
@@ -0,0 +1,6 @@
@mixin inline-block
display: inline-block
vertical-align: middle
*vertical-align: auto
*zoom: 1
*display: inline
+93
View File
@@ -0,0 +1,93 @@
@import inline-block
html, body
height: 100%
overflow: hidden
font-family: "proxima-nova", sans-serif
.tether.tether-theme-arrows-dark .tether-content
filter: none
background: #000
ul
color: #fff
list-style: none
padding: 0
margin: 0
.tether.tether-theme-arrows-dark.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content:before
border-right-color: #000
.browser-demo
position: absolute
top: 0
left: 0
bottom: 0
right: 0
*, *:after, *:before
box-sizing: border-box
.top
position: absolute
height: 60px
padding: 20px
line-height: 40px
width: 100%
border-bottom: 1px solid rgba(0, 0, 0, .1)
.bottom
position: absolute
top: 60px
bottom: 0
width: 100%
.left
border-right: 1px solid rgba(0, 0, 0, .1)
position: absolute
width: 30%
height: 100%
overflow: auto
.item
height: 64px
border-bottom: 1px solid rgba(0, 0, 0, .1)
cursor: pointer
&:hover, &.tether-open
background: rgba(0, 0, 0, .1)
border-bottom: 1px solid rgba(0, 0, 0, 0)
&:last-child
border-bottom: 0
.right
position: absolute
width: 70%
right: 0
height: 100%
padding: 20px
.title
+inline-block
background: rgba(0, 0, 0, .1)
width: 150px
height: 15px
margin-bottom: 20px
.word
+inline-block
background: rgba(0, 0, 0, .1)
width: 50px
height: 8px
margin-right: 5px
margin-bottom: 5px
&:nth-last-child(4n+1)
width: 73px
&:nth-last-child(10n+1)
width: 14px
&:nth-last-child(9n+1)
width: 80px
+285
View File
@@ -0,0 +1,285 @@
@import inline-block
html, body
height: 100%
body
margin: 0
font-family: "proxima-nova", "Helvetica Neue", sans-serif
.button
display: inline-block
border: 2px solid #333
color: #333
padding: 1em 1.25em
font-weight: 500
text-transform: uppercase
letter-spacing: 3px
text-decoration: none
cursor: pointer
width: 140px
font-size: .8em
line-height: 1.3em
text-align: center
.tether-element.tether-theme-arrows-dark .tether-content
padding: 1em
font-size: 1.1em
.button
border-color: #fff
color: #fff
width: 170px
pointer-events: all
.mobile-copy
display: none
@media (max-width: 568px)
display: block
.button.dark
background: #333
color: #fff
.hero-wrap
height: 100%
overflow: hidden
table.showcase
height: 100%
width: 100%
position: relative
&:after
content: ""
display: block
position: absolute
left: 0
right: 0
bottom: 20px
margin: auto
height: 0
width: 0
border-width: 18px
border-style: solid
border-color: transparent
border-top-color: rgba(0, 0, 0, 0.2)
&.no-next-arrow:after
display: none
.showcase-inner
margin: 40px auto 60px
padding: 10px
h1
font-size: 50px
text-align: center
font-weight: 300
@media (max-width: 567px)
font-size: 40px
h2
font-size: 24px
text-align: center
font-weight: 300
margin: 1em 0 1em
@media (max-width: 567px)
font-size: 14px
p
text-align: center
&.hero
text-align: center
.tether-target-demo
+inline-block
border: 2px dotted #000
margin: 5rem auto
padding: 5rem
@media (max-width: 567px)
padding: 1rem
&.share
background: #f3f3f3
&.projects-showcase .showcase-inner
.projects-list
width: 80%
max-width: 1200px
margin: 0 auto
.project
color: inherit
text-decoration: none
position: relative
width: 50%
float: left
text-align: center
margin-bottom: 2rem
&:nth-child(odd)
clear: left
.os-icon
width: 8rem
height: 8rem
margin-bottom: 1rem
background-size: 100%
h1
font-size: 2.5rem
p
font-size: 1.3rem
&.browser-demo
background-image: linear-gradient(top left, #723362 0%, #9d223c 100%)
background-color: #9d223c
position: absolute
top: 100%
&.fixed
position: fixed
top: 0
bottom: 0
left: 0
right: 0
z-index: 1
.browser-demo-inner
transition: width 2s ease-in-out, height 2s ease-in-out
// Sections
&[data-section="what"]
box-shadow: 0 0 0 0
&[data-section="why"]
.browser-demo-inner
width: 70%
&[data-section="outro"]
.showcase-inner
pointer-events: all
.showcase-inner
pointer-events: none
position: absolute
left: 10%
right: 40%
top: 220px
bottom: 120px
margin: 0
padding: 0
@media (max-width: 567px)
bottom: 90px
top: 180px
.browser-demo-inner
height: 100%
width: 100%
.section-copy
transition: opacity .5s ease-in-out, top .5s ease-in-out
opacity: 0
position: absolute
top: 0
position: absolute
height: 200px
color: #fff
text-align: center
width: 100%
&.active
opacity: 1
top: -150px
@media (max-width: 567px)
top: -130px
h2
font-size: 40px
font-weight: bold
line-height: 1
margin: 25px 0 15px
@media (max-width: 567px)
font-size: 30px
.browser-window
border-radius: 4px
background: #fff
position: relative
height: 100%
width: 100%
max-width: 1200px
margin: 0 auto
.browser-titlebar
position: absolute
top: 0
left: 0
right: 0
border-bottom: 1px solid #eee
height: 55px
.browser-dots
padding: 16px
b
+inline-block
border-radius: 50%
width: 10px
height: 10px
margin-right: 7px
background: rgba(0, 0, 0, .1)
.browser-frame
position: absolute
top: 55px
left: 0
right: 0
bottom: 0
iframe
border-radius: 0 0 4px 4px
border: 0
width: 100%
height: 100%
&.browser-demo-section
.section-scroll-copy
position: relative
z-index: 10
color: #fff
width: 100%
font-size: 22px
.section-scroll-copy-inner
position: absolute
z-index: 10
color: #fff
right: 10%
width: 23%
a
color: inherit
.example-paragraph
border-radius: 4px
background: #000
padding: 1rem
.browser-content
display: none
Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

+430
View File
@@ -0,0 +1,430 @@
/* @group Base */
.chosen-container {
position: relative;
display: inline-block;
vertical-align: middle;
font-size: 13px;
zoom: 1;
*display: inline;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.chosen-container .chosen-drop {
position: absolute;
top: 100%;
left: -9999px;
z-index: 1010;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
border: 1px solid #aaa;
border-top: 0;
background: #fff;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
}
.chosen-container.chosen-with-drop .chosen-drop {
left: 0;
}
.chosen-container a {
cursor: pointer;
}
/* @end */
/* @group Single Chosen */
.chosen-container-single .chosen-single {
position: relative;
display: block;
overflow: hidden;
padding: 0 0 0 8px;
height: 23px;
border: 1px solid #aaa;
border-radius: 5px;
background-color: #fff;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-clip: padding-box;
box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
color: #444;
text-decoration: none;
white-space: nowrap;
line-height: 24px;
}
.chosen-container-single .chosen-default {
color: #999;
}
.chosen-container-single .chosen-single span {
display: block;
overflow: hidden;
margin-right: 26px;
text-overflow: ellipsis;
white-space: nowrap;
}
.chosen-container-single .chosen-single-with-deselect span {
margin-right: 38px;
}
.chosen-container-single .chosen-single abbr {
position: absolute;
top: 6px;
right: 26px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-single .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single .chosen-single div {
position: absolute;
top: 0;
right: 0;
display: block;
width: 18px;
height: 100%;
}
.chosen-container-single .chosen-single div b {
display: block;
width: 100%;
height: 100%;
background: url('chosen-sprite.png') no-repeat 0px 2px;
}
.chosen-container-single .chosen-search {
position: relative;
z-index: 1010;
margin: 0;
padding: 3px 4px;
white-space: nowrap;
}
.chosen-container-single .chosen-search input[type="text"] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 1px 0;
padding: 4px 20px 4px 5px;
width: 100%;
height: auto;
outline: 0;
border: 1px solid #aaa;
background: white url('chosen-sprite.png') no-repeat 100% -20px;
background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat 100% -20px, linear-gradient(#eeeeee 1%, #ffffff 15%);
font-size: 1em;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-single .chosen-drop {
margin-top: -1px;
border-radius: 0 0 4px 4px;
background-clip: padding-box;
}
.chosen-container-single.chosen-container-single-nosearch .chosen-search {
position: absolute;
left: -9999px;
}
/* @end */
/* @group Results */
.chosen-container .chosen-results {
position: relative;
overflow-x: hidden;
overflow-y: auto;
margin: 0 4px 4px 0;
padding: 0 0 0 4px;
max-height: 240px;
-webkit-overflow-scrolling: touch;
}
.chosen-container .chosen-results li {
display: none;
margin: 0;
padding: 5px 6px;
list-style: none;
line-height: 15px;
}
.chosen-container .chosen-results li.active-result {
display: list-item;
cursor: pointer;
}
.chosen-container .chosen-results li.disabled-result {
display: list-item;
color: #ccc;
cursor: default;
}
.chosen-container .chosen-results li.highlighted {
background-color: #3875d7;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
color: #fff;
}
.chosen-container .chosen-results li.no-results {
display: list-item;
background: #f4f4f4;
}
.chosen-container .chosen-results li.group-result {
display: list-item;
font-weight: bold;
cursor: default;
}
.chosen-container .chosen-results li.group-option {
padding-left: 15px;
}
.chosen-container .chosen-results li em {
font-style: normal;
text-decoration: underline;
}
/* @end */
/* @group Multi Chosen */
.chosen-container-multi .chosen-choices {
position: relative;
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
width: 100%;
height: auto !important;
height: 1%;
border: 1px solid #aaa;
background-color: #fff;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
cursor: text;
}
.chosen-container-multi .chosen-choices li {
float: left;
list-style: none;
}
.chosen-container-multi .chosen-choices li.search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
margin: 1px 0;
padding: 5px;
height: 15px;
outline: 0;
border: 0 !important;
background: transparent !important;
box-shadow: none;
color: #666;
font-size: 100%;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-multi .chosen-choices li.search-field .default {
color: #999;
}
.chosen-container-multi .chosen-choices li.search-choice {
position: relative;
margin: 3px 0 3px 5px;
padding: 3px 20px 3px 5px;
border: 1px solid #aaa;
border-radius: 3px;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-clip: padding-box;
box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
color: #333;
line-height: 13px;
cursor: default;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
position: absolute;
top: 4px;
right: 3px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-choices li.search-choice-disabled {
padding-right: 5px;
border: 1px solid #ccc;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
color: #666;
}
.chosen-container-multi .chosen-choices li.search-choice-focus {
background: #d4d4d4;
}
.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-results {
margin: 0;
padding: 0;
}
.chosen-container-multi .chosen-drop .result-selected {
display: list-item;
color: #ccc;
cursor: default;
}
/* @end */
/* @group Active */
.chosen-container-active .chosen-single {
border: 1px solid #5897fb;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.chosen-container-active.chosen-with-drop .chosen-single {
border: 1px solid #aaa;
-moz-border-radius-bottomright: 0;
border-bottom-right-radius: 0;
-moz-border-radius-bottomleft: 0;
border-bottom-left-radius: 0;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
box-shadow: 0 1px 0 #fff inset;
}
.chosen-container-active.chosen-with-drop .chosen-single div {
border-left: none;
background: transparent;
}
.chosen-container-active.chosen-with-drop .chosen-single div b {
background-position: -18px 2px;
}
.chosen-container-active .chosen-choices {
border: 1px solid #5897fb;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.chosen-container-active .chosen-choices li.search-field input[type="text"] {
color: #111 !important;
}
/* @end */
/* @group Disabled Support */
.chosen-disabled {
opacity: 0.5 !important;
cursor: default;
}
.chosen-disabled .chosen-single {
cursor: default;
}
.chosen-disabled .chosen-choices .search-choice .search-choice-close {
cursor: default;
}
/* @end */
/* @group Right to Left */
.chosen-rtl {
text-align: right;
}
.chosen-rtl .chosen-single {
overflow: visible;
padding: 0 8px 0 0;
}
.chosen-rtl .chosen-single span {
margin-right: 0;
margin-left: 26px;
direction: rtl;
}
.chosen-rtl .chosen-single-with-deselect span {
margin-left: 38px;
}
.chosen-rtl .chosen-single div {
right: auto;
left: 3px;
}
.chosen-rtl .chosen-single abbr {
right: auto;
left: 26px;
}
.chosen-rtl .chosen-choices li {
float: right;
}
.chosen-rtl .chosen-choices li.search-field input[type="text"] {
direction: rtl;
}
.chosen-rtl .chosen-choices li.search-choice {
margin: 3px 5px 3px 0;
padding: 3px 5px 3px 19px;
}
.chosen-rtl .chosen-choices li.search-choice .search-choice-close {
right: auto;
left: 4px;
}
.chosen-rtl.chosen-container-single-nosearch .chosen-search,
.chosen-rtl .chosen-drop {
left: 9999px;
}
.chosen-rtl.chosen-container-single .chosen-results {
margin: 0 0 4px 4px;
padding: 0 4px 0 0;
}
.chosen-rtl .chosen-results li.group-option {
padding-right: 15px;
padding-left: 0;
}
.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
border-right: none;
}
.chosen-rtl .chosen-search input[type="text"] {
padding: 4px 5px 4px 20px;
background: white url('chosen-sprite.png') no-repeat -30px -20px;
background: url('chosen-sprite.png') no-repeat -30px -20px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background: url('chosen-sprite.png') no-repeat -30px -20px, -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat -30px -20px, -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat -30px -20px, -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
background: url('chosen-sprite.png') no-repeat -30px -20px, linear-gradient(#eeeeee 1%, #ffffff 15%);
direction: rtl;
}
.chosen-rtl.chosen-container-single .chosen-single div b {
background-position: 6px 2px;
}
.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
background-position: -12px 2px;
}
/* @end */
/* @group Retina compatibility */
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) {
.chosen-rtl .chosen-search input[type="text"],
.chosen-container-single .chosen-single abbr,
.chosen-container-single .chosen-single div b,
.chosen-container-single .chosen-search input[type="text"],
.chosen-container-multi .chosen-choices .search-choice .search-choice-close,
.chosen-container .chosen-results-scroll-down span,
.chosen-container .chosen-results-scroll-up span {
background-image: url('chosen-sprite@2x.png') !important;
background-size: 52px 37px !important;
background-repeat: no-repeat !important;
}
}
/* @end */
+1166
View File
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../../css/drop.css" />
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="chosen.css" />
</head>
<body>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
<br/>
Favorite pizza style: <br/>
<div class="drop-chosen-target chosen-container chosen-container-single chosen-with-drop" style="width: 350px">
<a class="chosen-single chosen-default" tabindex="-1"><span>Choose a style...</span><div><b></b></div></a>
</div>
<br/>
<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
text<br/>
<script src="../resources/js/log.js"></script>
<script src="../resources/js/jquery.js"></script>
<script src="chosen.js"></script>
<script src="../../drop.js"></script>
<script>
$(function(){
var $target, $drop;
$target = $('.drop-chosen-target').drop({
className: 'drop-chosen',
attach: 'bottom-left',
content: ''
});
$drop = $target.data().drop.$drop;
$drop.append('<select><option>Plain</option><option>Pepperoni</option><option>Supreme</option></select>');
$drop.on('openDrop', function(event){
$target.addClass('chosen-container-active');
}).on('closeDrop', function(event){
$target.removeClass('chosen-container-active');
});
$drop.on('openDrop', function(event) {
var $select = $drop.find('select');
if (!$drop.find('.chosen-container').length) {
$select
.css('width', $target.outerWidth())
.chosen({
allow_single_deselect: true,
disable_search_threshold: 0
})
;
}
setTimeout(function(){
$drop.find('.chosen-search input').click().focus().keyup();
$target.drop('positionDrop');
}, 0);
});
});
</script>
<style>
.drop-chosen-target > a {
outline: none;
}
.drop.drop-chosen {
background: transparent;
}
.drop.drop-chosen .chosen-container > .chosen-single {
display: none;
}
.drop.drop-chosen .chosen-container .chosen-drop {
position: static;
}
</style>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
body {
min-height: 3000px;
}
.element {
width: 200px;
height: 200px;
background-color: #fe8;
position: absolute;
z-index: 6;
}
.target {
width: 300px;
height: 50px;
margin: 0 35%;
background-color: #4e9;
}
.container {
height: 600px;
overflow: scroll;
width: 600px;
border: 20px solid #CCC;
margin-top: 100px;
}
body {
padding: 15px;
}
body > .container {
margin: 0 auto;
}
.pad {
height: 400px;
width: 100px;
}
.instructions {
width: 100%;
text-align: center;
font-size: 24px;
padding: 15px;
background-color: rgba(210, 180, 140, 0.4);
margin: -15px -15px 0 -15px;
}
+64
View File
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../resources/css/base.css" />
</head>
<body>
<div class="instructions">Scroll the page</div>
<style>
.instructions {
width: 100%;
text-align: center;
font-size: 24px;
padding: 15px;
background-color: rgba(210, 180, 140, 0.4);
}
* {
box-sizing: border-box;
}
body {
min-height: 1200vh;
height: 100%;
}
.content-box {
width: 600px;
border: 10px solid #999;
height: 600vh;
background-color: #439CCC;
margin: 200vh auto;
}
.element {
border: 10px solid #999;
background-color: #FFDC00;
width: 300px;
height: 200px;
padding: 0 15px;
font-size: 20px;
font-weight: bold;
}
</style>
<div class="content-box">
<div class="element">
<p>This is some sort of crazy dialog.</p>
<p>It's setup to align with the center of the visible part of the blue area.</p>
</div>
</div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.element',
target: '.content-box',
attachment: 'middle center',
targetAttachment: 'middle center',
targetModifier: 'visible'
});
</script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
.tether-element, .tether-target {
width: 200px;
height: 50px;
background-color: #4cc;
position: absolute;
}
body {
width: 100%;
height: 100%;
overflow: scroll;
}
.scroll {
width: 400%;
height: 400%;
}
.tether-target:not(.tether-element) {
cursor: move;
}
+83
View File
@@ -0,0 +1,83 @@
var tethers = [];
document.addEventListener('DOMContentLoaded', function(){
dragging = null;
document.body.addEventListener('mouseup', function(){
dragging = null;
});
document.body.addEventListener('mousemove', function(e){
if (dragging){
dragging.style.top = e.clientY + 'px';
dragging.style.left = e.clientX + 'px';
Tether.position()
}
});
document.body.addEventListener('mousedown', function(e){
if (e.target.getAttribute('data-index'))
dragging = e.target;
})
var count = 60;
var parent = null;
var dir = 'left';
var first = null;
while (count--){
var el = document.createElement('div');
el.setAttribute('data-index', count);
document.querySelector('.scroll').appendChild(el);
if (!first)
first = el;
if (count % 10 === 0)
dir = dir == 'right' ? 'left' : 'right';
if (parent){
tethers.push(new Tether({
element: el,
target: parent,
attachment: 'middle ' + dir,
targetOffset: (dir == 'left' ? '10px 10px' : '10px -10px')
}));
}
parent = el;
}
initAnim(first);
});
function initAnim(el){
var start = performance.now()
var last = 0;
var lastTop = 0;
var tick = function(){
var diff = performance.now() - last;
if (!last || diff > 50){
last = performance.now();
var nextTop = 50 * Math.sin((last - start) / 1000);
var curTop = parseFloat(el.style.top || 0);
var topChange = nextTop - lastTop;
lastTop = nextTop;
var top = curTop + topChange;
el.style.top = top + 'px';
Tether.position();
}
requestAnimationFrame(tick);
};
tick();
}
+7
View File
@@ -0,0 +1,7 @@
<link rel="stylesheet" href="./dolls.css" />
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script src="./dolls.js"></script>
<body>
<div class="scroll">
</div>
</body>
+499
View File
@@ -0,0 +1,499 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../resources/css/base.css" />
</head>
<body>
<div class="scroll">
<p>For a long time after the course of the steamer <em>Sofala</em> had been
altered for the land, the low swampy coast had retained its appearance
of a mere smudge of darkness beyond a belt of glitter. The sunrays
seemed to fall violently upon the calm sea--seemed to shatter themselves
upon an adamantine surface into sparkling dust, into a dazzling vapor
of light that blinded the eye and wearied the brain with its unsteady
brightness.</p>
<p>Captain Whalley did not look at it. When his Serang, approaching the
roomy cane arm-chair which he filled capably, had informed him in a low
voice that the course was to be altered, he had risen at once and had
remained on his feet, face forward, while the head of his ship swung
through a quarter of a circle. He had not uttered a single word, not
even the word to steady the helm. It was the Serang, an elderly, alert,
little Malay, with a very dark skin, who murmured the order to the
helmsman. And then slowly Captain Whalley sat down again in the
arm-chair on the bridge and fixed his eyes on the deck between his feet.</p>
<p>He could not hope to see anything new upon this lane of the sea. He had
been on these coasts for the last three years. From Low Cape to Malantan
the distance was fifty miles, six hours' steaming for the old ship with
the tide, or seven against. Then you steered straight for the land, and
by-and-by three palms would appear on the sky, tall and slim, and with
their disheveled heads in a bunch, as if in confidential criticism of
the dark mangroves. The Sofala would be headed towards the somber
strip of the coast, which at a given moment, as the ship closed with
it obliquely, would show several clean shining fractures--the brimful
estuary of a river. Then on through a brown liquid, three parts water
and one part black earth, on and on between the low shores, three parts
black earth and one part brackish water, the Sofala would plow her way
up-stream, as she had done once every month for these seven years or
more, long before he was aware of her existence, long before he had ever
thought of having anything to do with her and her invariable voyages.
The old ship ought to have known the road better than her men, who had
not been kept so long at it without a change; better than the faithful
Serang, whom he had brought over from his last ship to keep the
captain's watch; better than he himself, who had been her captain for
the last three years only. She could always be depended upon to make her
courses. Her compasses were never out. She was no trouble at all to
take about, as if her great age had given her knowledge, wisdom, and
steadiness. She made her landfalls to a degree of the bearing, and
almost to a minute of her allowed time. At any moment, as he sat on
the bridge without looking up, or lay sleepless in his bed, simply by
reckoning the days and the hours he could tell where he was--the precise
spot of the beat. He knew it well too, this monotonous huckster's
round, up and down the Straits; he knew its order and its sights and its
people. Malacca to begin with, in at daylight and out at dusk, to cross
over with a rigid phosphorescent wake this highway of the Far East.
Darkness and gleams on the water, clear stars on a black sky, perhaps
the lights of a home steamer keeping her unswerving course in the
middle, or maybe the elusive shadow of a native craft with her mat sails
flitting by silently--and the low land on the other side in sight
at daylight. At noon the three palms of the next place of call, up a
sluggish river. The only white man residing there was a retired young
sailor, with whom he had become friendly in the course of many voyages.
Sixty miles farther on there was another place of call, a deep bay with
only a couple of houses on the beach. And so on, in and out, picking
up coastwise cargo here and there, and finishing with a hundred miles'
steady steaming through the maze of an archipelago of small islands up
to a large native town at the end of the beat. There was a three days'
rest for the old ship before he started her again in inverse order,
seeing the same shores from another bearing, hearing the same voices
in the same places, back again to the Sofala's port of registry on
the great highway to the East, where he would take up a berth nearly
opposite the big stone pile of the harbor office till it was time to
start again on the old round of 1600 miles and thirty days. Not a very
enterprising life, this, for Captain Whalley, Henry Whalley, otherwise
Dare-devil Harry--Whalley of the Condor, a famous clipper in her day.
No. Not a very enterprising life for a man who had served famous firms,
who had sailed famous ships (more than one or two of them his own); who
had made famous passages, had been the pioneer of new routes and new
trades; who had steered across the unsurveyed tracts of the South Seas,
and had seen the sun rise on uncharted islands. Fifty years at sea, and
forty out in the East ("a pretty thorough apprenticeship," he used
to remark smilingly), had made him honorably known to a generation of
shipowners and merchants in all the ports from Bombay clear over to
where the East merges into the West upon the coast of the two Americas.
His fame remained writ, not very large but plain enough, on the
Admiralty charts. Was there not somewhere between Australia and China a
Whalley Island and a Condor Reef? On that dangerous coral formation the
celebrated clipper had hung stranded for three days, her captain and
crew throwing her cargo overboard with one hand and with the other, as
it were, keeping off her a flotilla of savage war-canoes. At that time
neither the island nor the reef had any official existence. Later the
officers of her Majesty's steam vessel Fusilier, dispatched to make a
survey of the route, recognized in the adoption of these two names the
enterprise of the man and the solidity of the ship. Besides, as anyone
who cares may see, the "General Directory," vol. ii. p. 410, begins the
description of the "Malotu or Whalley Passage" with the words: "This
advantageous route, first discovered in 1850 by Captain Whalley in the
ship Condor," &amp;c., and ends by recommending it warmly to sailing vessels
leaving the China ports for the south in the months from December to
April inclusive.</p>
<p>This was the clearest gain he had out of life. Nothing could rob him
of this kind of fame. The piercing of the Isthmus of Suez, like the
breaking of a dam, had let in upon the East a flood of new ships, new
men, new methods of trade. It had changed the face of the Eastern seas
and the very spirit of their life; so that his early experiences meant
nothing whatever to the new generation of seamen.</p>
<p>In those bygone days he had handled many thousands of pounds of his
employers' money and of his own; he had attended faithfully, as by law
a shipmaster is expected to do, to the conflicting interests of owners,
charterers, and underwriters. He had never lost a ship or consented to
a shady transaction; and he had lasted well, outlasting in the end the
conditions that had gone to the making of his name. He had buried his
wife (in the Gulf of Petchili), had married off his daughter to the man
of her unlucky choice, and had lost more than an ample competence in the
crash of the notorious Travancore and Deccan Banking Corporation, whose
downfall had shaken the East like an earthquake. And he was sixty-five
years old.</p>
<p>His age sat lightly enough on him; and of his ruin he was not ashamed.
He had not been alone to believe in the stability of the Banking
Corporation. Men whose judgment in matters of finance was as expert as
his seamanship had commended the prudence of his investments, and had
themselves lost much money in the great failure. The only difference
between him and them was that he had lost his all. And yet not his all.
There had remained to him from his lost fortune a very pretty little
bark, Fair Maid, which he had bought to occupy his leisure of a retired
sailor--"to play with," as he expressed it himself.</p>
<p>He had formally declared himself tired of the sea the year preceding his
daughter's marriage. But after the young couple had gone to settle in
Melbourne he found out that he could not make himself happy on shore. He
was too much of a merchant sea-captain for mere yachting to satisfy him.
He wanted the illusion of affairs; and his acquisition of the Fair
Maid preserved the continuity of his life. He introduced her to his
acquaintances in various ports as "my last command." When he grew too
old to be trusted with a ship, he would lay her up and go ashore to be
buried, leaving directions in his will to have the bark towed out and
scuttled decently in deep water on the day of the funeral. His daughter
would not grudge him the satisfaction of knowing that no stranger would
handle his last command after him. With the fortune he was able to leave
her, the value of a 500-ton bark was neither here nor there. All this
would be said with a jocular twinkle in his eye: the vigorous old man
had too much vitality for the sentimentalism of regret; and a little
wistfully withal, because he was at home in life, taking a genuine
pleasure in its feelings and its possessions; in the dignity of his
reputation and his wealth, in his love for his daughter, and in his
satisfaction with the ship--the plaything of his lonely leisure.</p>
<p>He had the cabin arranged in accordance with his simple ideal of comfort
at sea. A big bookcase (he was a great reader) occupied one side of his
stateroom; the portrait of his late wife, a flat bituminous oil-painting
representing the profile and one long black ringlet of a young woman,
faced his bed-place. Three chronometers ticked him to sleep and greeted
him on waking with the tiny competition of their beats. He rose at five
every day. The officer of the morning watch, drinking his early cup
of coffee aft by the wheel, would hear through the wide orifice of the
copper ventilators all the splashings, blowings, and splutterings of
his captain's toilet. These noises would be followed by a sustained
deep murmur of the Lord's Prayer recited in a loud earnest voice. Five
minutes afterwards the head and shoulders of Captain Whalley emerged
out of the companion-hatchway. Invariably he paused for a while on the
stairs, looking all round at the horizon; upwards at the trim of the
sails; inhaling deep draughts of the fresh air. Only then he would step
out on the poop, acknowledging the hand raised to the peak of the cap
with a majestic and benign "Good morning to you." He walked the deck
till eight scrupulously. Sometimes, not above twice a year, he had to
use a thick cudgel-like stick on account of a stiffness in the hip--a
slight touch of rheumatism, he supposed. Otherwise he knew nothing of
the ills of the flesh. At the ringing of the breakfast bell he went
below to feed his canaries, wind up the chronometers, and take the
head of the table. From there he had before his eyes the big carbon
photographs of his daughter, her husband, and two fat-legged babies
--his grandchildren--set in black frames into the maplewood bulkheads
of the cuddy. After breakfast he dusted the glass over these portraits
himself with a cloth, and brushed the oil painting of his wife with a
plumate kept suspended from a small brass hook by the side of the heavy
gold frame. Then with the door of his stateroom shut, he would sit down
on the couch under the portrait to read a chapter out of a thick pocket
Bible--her Bible. But on some days he only sat there for half an hour
with his finger between the leaves and the closed book resting on his
knees. Perhaps he had remembered suddenly how fond of boat-sailing she
used to be.</p>
<p>She had been a real shipmate and a true woman too. It was like an
article of faith with him that there never had been, and never could be,
a brighter, cheerier home anywhere afloat or ashore than his home under
the poop-deck of the Condor, with the big main cabin all white and gold,
garlanded as if for a perpetual festival with an unfading wreath. She
had decorated the center of every panel with a cluster of home flowers.
It took her a twelvemonth to go round the cuddy with this labor of love.
To him it had remained a marvel of painting, the highest achievement of
taste and skill; and as to old Swinburne, his mate, every time he
came down to his meals he stood transfixed with admiration before the
progress of the work. You could almost smell these roses, he declared,
sniffing the faint flavor of turpentine which at that time pervaded the
saloon, and (as he confessed afterwards) made him somewhat less hearty
than usual in tackling his food. But there was nothing of the sort to
interfere with his enjoyment of her singing. "Mrs. Whalley is a regular
out-and-out nightingale, sir," he would pronounce with a judicial air
after listening profoundly over the skylight to the very end of the
piece. In fine weather, in the second dog-watch, the two men could hear
her trills and roulades going on to the accompaniment of the piano in
the cabin. On the very day they got engaged he had written to London
for the instrument; but they had been married for over a year before it
reached them, coming out round the Cape. The big case made part of the
first direct general cargo landed in Hong-kong harbor--an event that to
the men who walked the busy quays of to-day seemed as hazily remote as
the dark ages of history. But Captain Whalley could in a half hour of
solitude live again all his life, with its romance, its idyl, and its
sorrow. He had to close her eyes himself. She went away from under the
ensign like a sailor's wife, a sailor herself at heart. He had read
the service over her, out of her own prayer-book, without a break in his
voice. When he raised his eyes he could see old Swinburne facing him
with his cap pressed to his breast, and his rugged, weather-beaten,
impassive face streaming with drops of water like a lump of chipped red
granite in a shower. It was all very well for that old sea-dog to cry.
He had to read on to the end; but after the splash he did not remember
much of what happened for the next few days. An elderly sailor of the
crew, deft at needlework, put together a mourning frock for the child
out of one of her black skirts.</p>
<p>He was not likely to forget; but you cannot dam up life like a sluggish
stream. It will break out and flow over a man's troubles, it will close
upon a sorrow like the sea upon a dead body, no matter how much love has
gone to the bottom. And the world is not bad. People had been very
kind to him; especially Mrs. Gardner, the wife of the senior partner
in Gardner, Patteson, &amp; Co., the owners of the Condor. It was she who
volunteered to look after the little one, and in due course took her to
England (something of a journey in those days, even by the overland
mail route) with her own girls to finish her education. It was ten years
before he saw her again.</p>
<p>As a little child she had never been frightened of bad weather; she
would beg to be taken up on deck in the bosom of his oilskin coat to
watch the big seas hurling themselves upon the Condor. The swirl and
crash of the waves seemed to fill her small soul with a breathless
delight. "A good boy spoiled," he used to say of her in joke. He had
named her Ivy because of the sound of the word, and obscurely fascinated
by a vague association of ideas. She had twined herself tightly round
his heart, and he intended her to cling close to her father as to a
tower of strength; forgetting, while she was little, that in the nature
of things she would probably elect to cling to someone else. But
he loved life well enough for even that event to give him a certain
satisfaction, apart from his more intimate feeling of loss.</p>
<p>After he had purchased the Fair Maid to occupy his loneliness, he
hastened to accept a rather unprofitable freight to Australia simply for
the opportunity of seeing his daughter in her own home. What made him
dissatisfied there was not to see that she clung now to somebody else,
but that the prop she had selected seemed on closer examination "a
rather poor stick"--even in the matter of health. He disliked his
son-in-law's studied civility perhaps more than his method of
handling the sum of money he had given Ivy at her marriage. But of his
apprehensions he said nothing. Only on the day of his departure, with
the hall-door open already, holding her hands and looking steadily into
her eyes, he had said, "You know, my dear, all I have is for you and the
chicks. Mind you write to me openly." She had answered him by an almost
imperceptible movement of her head. She resembled her mother in
the color of her eyes, and in character--and also in this, that she
understood him without many words.</p>
<p>Sure enough she had to write; and some of these letters made Captain
Whalley lift his white eye-brows. For the rest he considered he was
reaping the true reward of his life by being thus able to produce on
demand whatever was needed. He had not enjoyed himself so much in a
way since his wife had died. Characteristically enough his son-in-law's
punctuality in failure caused him at a distance to feel a sort of
kindness towards the man. The fellow was so perpetually being jammed on
a lee shore that to charge it all to his reckless navigation would be
manifestly unfair. No, no! He knew well what that meant. It was bad
luck. His own had been simply marvelous, but he had seen in his life too
many good men--seamen and others--go under with the sheer weight of bad
luck not to recognize the fatal signs. For all that, he was cogitating
on the best way of tying up very strictly every penny he had to leave,
when, with a preliminary rumble of rumors (whose first sound reached
him in Shanghai as it happened), the shock of the big failure came;
and, after passing through the phases of stupor, of incredulity, of
indignation, he had to accept the fact that he had nothing to speak of
to leave.</p>
<p>Upon that, as if he had only waited for this catastrophe, the unlucky
man, away there in Melbourne, gave up his unprofitable game, and sat
down--in an invalid's bath-chair at that too. "He will never walk
again," wrote the wife. For the first time in his life Captain Whalley
was a bit staggered.</p>
<p>The Fair Maid had to go to work in bitter earnest now. It was no longer
a matter of preserving alive the memory of Dare-devil Harry Whalley in
the Eastern Seas, or of keeping an old man in pocket-money and clothes,
with, perhaps, a bill for a few hundred first-class cigars thrown in at
the end of the year. He would have to buckle-to, and keep her going hard
on a scant allowance of gilt for the ginger-bread scrolls at her stem
and stern.</p>
<p>This necessity opened his eyes to the fundamental changes of the world.
Of his past only the familiar names remained, here and there, but
the things and the men, as he had known them, were gone. The name of
Gardner, Patteson, &amp; Co. was still displayed on the walls of warehouses
by the waterside, on the brass plates and window-panes in the business
quarters of more than one Eastern port, but there was no longer a
Gardner or a Patteson in the firm. There was no longer for Captain
Whalley an arm-chair and a welcome in the private office, with a bit of
business ready to be put in the way of an old friend, for the sake of
bygone services. The husbands of the Gardner girls sat behind the desks
in that room where, long after he had left the employ, he had kept his
right of entrance in the old man's time. Their ships now had yellow
funnels with black tops, and a time-table of appointed routes like a
confounded service of tramways. The winds of December and June were all
one to them; their captains (excellent young men he doubted not) were,
to be sure, familiar with Whalley Island, because of late years the
Government had established a white fixed light on the north end (with
a red danger sector over the Condor Reef), but most of them would have
been extremely surprised to hear that a flesh-and-blood Whalley still
existed--an old man going about the world trying to pick up a cargo here
and there for his little bark.</p>
<p>And everywhere it was the same. Departed the men who would have nodded
appreciatively at the mention of his name, and would have thought
themselves bound in honor to do something for Dare-devil Harry Whalley.
Departed the opportunities which he would have known how to seize; and
gone with them the white-winged flock of clippers that lived in the
boisterous uncertain life of the winds, skimming big fortunes out of
the foam of the sea. In a world that pared down the profits to an
irreducible minimum, in a world that was able to count its disengaged
tonnage twice over every day, and in which lean charters were snapped up
by cable three months in advance, there were no chances of fortune for
an individual wandering haphazard with a little bark--hardly indeed any
room to exist.</p>
<p>He found it more difficult from year to year. He suffered greatly from
the smallness of remittances he was able to send his daughter. Meantime
he had given up good cigars, and even in the matter of inferior cheroots
limited himself to six a day. He never told her of his difficulties, and
she never enlarged upon her struggle to live. Their confidence in each
other needed no explanations, and their perfect understanding endured
without protestations of gratitude or regret. He would have been shocked
if she had taken it into her head to thank him in so many words, but
he found it perfectly natural that she should tell him she needed two
hundred pounds.</p>
<p>He had come in with the Fair Maid in ballast to look for a freight in
the Sofala's port of registry, and her letter met him there. Its tenor
was that it was no use mincing matters. Her only resource was in opening
a boarding-house, for which the prospects, she judged, were good. Good
enough, at any rate, to make her tell him frankly that with two hundred
pounds she could make a start. He had torn the envelope open, hastily,
on deck, where it was handed to him by the ship-chandler's runner, who
had brought his mail at the moment of anchoring. For the second time
in his life he was appalled, and remained stock-still at the cabin door
with the paper trembling between his fingers. Open a boarding-house! Two
hundred pounds for a start! The only resource! And he did not know where
to lay his hands on two hundred pence.</p>
<p>All that night Captain Whalley walked the poop of his anchored ship, as
though he had been about to close with the land in thick weather, and
uncertain of his position after a run of many gray days without a sight
of sun, moon, or stars. The black night twinkled with the guiding lights
of seamen and the steady straight lines of lights on shore; and all
around the Fair Maid the riding lights of ships cast trembling trails
upon the water of the roadstead. Captain Whalley saw not a gleam
anywhere till the dawn broke and he found out that his clothing was
soaked through with the heavy dew.</p>
<p>His ship was awake. He stopped short, stroked his wet beard, and
descended the poop ladder backwards, with tired feet. At the sight
of him the chief officer, lounging about sleepily on the quarterdeck,
remained open-mouthed in the middle of a great early-morning yawn.</p>
<p>"Good morning to you," pronounced Captain Whalley solemnly, passing into
the cabin. But he checked himself in the doorway, and without looking
back, "By the bye," he said, "there should be an empty wooden case put
away in the lazarette. It has not been broken up--has it?"</p>
<p>The mate shut his mouth, and then asked as if dazed, "What empty case,
sir?"</p>
<p>"A big flat packing-case belonging to that painting in my room. Let it
be taken up on deck and tell the carpenter to look it over. I may want
to use it before long."</p>
<p>The chief officer did not stir a limb till he had heard the door of the
captain's state-room slam within the cuddy. Then he beckoned aft the
second mate with his forefinger to tell him that there was something "in
the wind."</p>
<p>When the bell rang Captain Whalley's authoritative voice boomed out
through a closed door, "Sit down and don't wait for me." And his
impressed officers took their places, exchanging looks and whispers
across the table. What! No breakfast? And after apparently knocking
about all night on deck, too! Clearly, there was something in the wind.
In the skylight above their heads, bowed earnestly over the plates,
three wire cages rocked and rattled to the restless jumping of the
hungry canaries; and they could detect the sounds of their "old
man's" deliberate movements within his state-room. Captain Whalley was
methodically winding up the chronometers, dusting the portrait of
his late wife, getting a clean white shirt out of the drawers, making
himself ready in his punctilious unhurried manner to go ashore. He could
not have swallowed a single mouthful of food that morning. He had made
up his mind to sell the Fair Maid.</p>
</div>
<div class="pointer"></div>
<style>
body {
cursor: pointer;
}
.scroll {
height: 80vh;
width: 80vw;
max-height: 600px;
position: fixed;
top: 5em;
left: 10vw;
overflow-y: scroll;
padding: 4em;
box-sizing: border-box;
line-height: 1.2;
}
.scroll::-webkit-scrollbar, .scroll::-webkit-scrollbar-track, .scroll::-webkit-scrollbar-thumb {
display: none;
}
.pointer {
height: 3.6em;
width: 77vw;
border: 5px solid #CCC;
border-radius: 15px;
background-color: rgba(0, 0, 0, 0.05);
pointer-events: none;
}
.highlight {
background-color: rgba(255, 255, 0, 0.3);
}
.hover {
background-color: rgba(0, 255, 255, 0.2);
}
</style>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
var pointer = document.querySelector('.pointer');
var scroll = document.querySelector('.scroll');
// This creates the pointer tether and links it up
// with the scroll handle
new Tether({
element: pointer,
target: scroll,
attachment: 'middle right',
targetAttachment: 'middle left',
targetModifier: 'scroll-handle'
});
// Everything after this is for the highlighting effect
var paras = document.querySelectorAll('p');
for(var i=paras.length; i--;){
var sents = paras[i].innerHTML.split('.');
for (var j=sents.length; j--;){
if (sents[j].trim().length)
sents[j] = '<span>' + sents[j] + '.</span>';
}
paras[i].innerHTML = sents.join('');
}
var spans = document.querySelectorAll('p span');
function highlight(){
if (!spans) return;
var bar = pointer.getBoundingClientRect();
for (var i=spans.length; i--;){
var coord = spans[i].getBoundingClientRect();
if (bar.top < coord.top && bar.bottom > coord.top){
spans[i].classList.add('hover');
} else if (spans[i].classList.contains('hover')) {
spans[i].classList.remove('hover');
}
}
requestAnimationFrame(highlight);
}
highlight();
document.body.addEventListener('click', function(){
var els = document.querySelectorAll('.hover');
for (var i=els.length; i--;)
els[i].classList.toggle('highlight');
});
</script>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
</head>
<body>
<div class="instructions">Click the green target to enable/disable the tethering.</div>
<div class="element"></div>
<div class="container">
<div class="pad"></div>
<div class="target"></div>
<div class="pad"></div>
</div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
var tether = new Tether({
element: '.element',
target: '.target',
attachment: 'top left',
targetAttachment: 'top right'
});
document.querySelector('.target').addEventListener('click', function(){
if (tether.enabled)
tether.disable();
else
tether.enable();
});
</script>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
.drop-target.drop-open {
outline: 2px solid;
}
.body {
position: relative;
margin-right: 300px;
}
.page {
max-width: 100%;
width: 1080px;
padding: 0 10px;
box-sizing: border-box;
margin: 0 auto;
}
.navigation {
background: blue;
color: #fff;
margin-right: 300px;
margin-bottom: 30px;
}
.navigation .item a {
padding: 30px 20px;
display: inline-block;
}
.navigation .item a {
color: inherit;
}
.navigation .drop-target.drop-open {
background: #fff;
color: blue;
outline: none;
}
.right-sidebar {
position: fixed;
height: 50%;
width: 300px;
background: #eee;
overflow: auto;
right: 0;
}
.right-sidebar .drop-target.drop-open {
background: blue;
color: #fff;
outline: none;
}
.right-sidebar .item a {
display: block;
padding: 20px;
margin-bottom: 10px;
background: rgba(0, 0, 0, .1);
}
.right-sidebar-top {
top: 0;
}
.right-sidebar-bottom {
top: 50%;
background: #ccc;
}
.scroll-container {
position: relative;
overflow: auto;
background: #eee;
padding: 20px;
margin-bottom: 20px;
margin-right: 20px;
height: 200px;
width: 200px;
}
.absolute-container {
position: absolute;
top: 20px;
right: 300px;
}
+209
View File
@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../../css/drop.css" />
<link rel="stylesheet" href="../../css/drop-theme-default.css" />
<link rel="stylesheet" href="../../css/drop-theme-arrows.css" />
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="facebook.css" />
</head>
<body>
<div class="navigation">
<div class="page">
<span class="item">DROPBOOK</span>
<span class="item">
<a class="drop-target" data-constrain="false" data-attach="bottom left">DROP</a>
</span>
<span class="item">
<a class="drop-target" data-constrain="false" data-attach="bottom left">DROP</a>
</span>
<span class="item">
<a class="drop-target" data-constrain="false" data-attach="bottom left">DROP</a>
</span>
</div>
</div>
<div class="right-sidebar right-sidebar-top">
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="true" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left top">DROP</a>
</div>
</div>
<div class="right-sidebar right-sidebar-bottom">
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="true" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
<div class="item">
<a class="drop-target" data-constrain="false" data-class="drop-theme-arrows" data-attach="left bottom">DROP</a>
</div>
</div>
<div class="body">
<div class="page">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor<br/>
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud<br/>
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor<br/>
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud<br/>
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor<br/>
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud<br/>
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor<br/>
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud<br/>
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/><br/>
<a class="drop-target" data-constrain="true" data-attach="bottom left">DROP</a><br/><br/>
<div class="scroll-container">
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
<br/><br/><a class="drop-target" data-constrain="true" data-attach="bottom left">DROP</a><br/><br/> sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/>
</div>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/><br/>
<div class="scroll-container">
<br/>
<a class="drop-target" data-constrain="true" data-attach="bottom left">DROP</a><br/>
<br/>
<div style="width: 150%; background: #ccc; height: 1px"></div>
</div>
<div class="scroll-container">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
<br/><br/>
<div style="width: 150%; background: #ccc; height: 1px">
<div style="margin-left: 100%">
<a class="drop-target" data-constrain="true" data-attach="bottom left">DROP</a>
</div>
</div>
<br/><br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/>
</div>
<div class="scroll-container absolute-container">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, <br/><br/><a class="drop-target" data-constrain="false" data-attach="left top">DROP</a><br/><br/> sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<br/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<br/>
</div>
</div>
</div>
<script src="../resources/js/log.js"></script>
<script src="../resources/js/jquery.js"></script>
<script src="../../utils.js"></script>
<script src="../../tether.js"></script>
<script src="../../drop.js"></script>
<script src="../../constraint.js"></script>
<style>
.drop .drop-content {
min-height: 100px;
min-width: 100px;
}
</style>
<script>
$('.drop-target').each(function(){
new Drop({
target: this,
className: $(this).data().class || 'drop-theme-default',
attach: $(this).data().attach,
constrainToScrollParent: $(this).data().constrain,
openOn: 'click'
});
});
</script>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
<style>
.tether-element.tether-out-of-bounds {
display: none;
}
</style>
</head>
<body>
<div class="instructions">Resize the screen to see the tethered element disappear when it can't fit.</div>
<div class="element"></div>
<div class="target"></div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
var tether = new Tether({
element: '.element',
target: '.target',
attachment: 'top left',
targetAttachment: 'top right',
constraints: [{
to: 'window',
attachment: 'together'
}]
});
tether.on('update', function(event) {
console.log(event);
});
</script>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
</head>
<body>
<div class="instructions">Resize the screen to see the tethered element stick to the edges of the screen when it's resized.</div>
<div class="element"></div>
<div class="target"></div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.element',
target: '.target',
attachment: 'top left',
targetAttachment: 'top right',
constraints: [{
to: 'window',
pin: true
}]
});
</script>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
body {
font-family: "Helvetica Neue", sans-serif;
color: #444;
margin: 0px;
}
a {
cursor: pointer;
color: blue;
}
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
(function() {
var ffSupport, formats, getOrderedMatches, hasMatches, isFF, isIE, isOpera, isSafari, log, makeArray, operaSupport, safariSupport, stringToArgs, _log;
if (!(window.console && window.console.log)) {
return;
}
log = function() {
var args;
args = [];
makeArray(arguments).forEach(function(arg) {
if (typeof arg === 'string') {
return args = args.concat(stringToArgs(arg));
} else {
return args.push(arg);
}
});
return _log.apply(window, args);
};
_log = function() {
return console.log.apply(console, makeArray(arguments));
};
makeArray = function(arrayLikeThing) {
return Array.prototype.slice.call(arrayLikeThing);
};
formats = [
{
regex: /\*([^\*]+)\*/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['font-style: italic', ''];
}
}, {
regex: /\_([^\_]+)\_/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['font-weight: bold', ''];
}
}, {
regex: /\`([^\`]+)\`/,
replacer: function(m, p1) {
return "%c" + p1 + "%c";
},
styles: function() {
return ['background: rgb(255, 255, 219); padding: 1px 5px; border: 1px solid rgba(0, 0, 0, 0.1)', ''];
}
}, {
regex: /\[c\=(?:\"|\')?((?:(?!(?:\"|\')\]).)*)(?:\"|\')?\]((?:(?!\[c\]).)*)\[c\]/,
replacer: function(m, p1, p2) {
return "%c" + p2 + "%c";
},
styles: function(match) {
return [match[1], ''];
}
}
];
hasMatches = function(str) {
var _hasMatches;
_hasMatches = false;
formats.forEach(function(format) {
if (format.regex.test(str)) {
return _hasMatches = true;
}
});
return _hasMatches;
};
getOrderedMatches = function(str) {
var matches;
matches = [];
formats.forEach(function(format) {
var match;
match = str.match(format.regex);
if (match) {
return matches.push({
format: format,
match: match
});
}
});
return matches.sort(function(a, b) {
return a.match.index - b.match.index;
});
};
stringToArgs = function(str) {
var firstMatch, matches, styles;
styles = [];
while (hasMatches(str)) {
matches = getOrderedMatches(str);
firstMatch = matches[0];
str = str.replace(firstMatch.format.regex, firstMatch.format.replacer);
styles = styles.concat(firstMatch.format.styles(firstMatch.match));
}
return [str].concat(styles);
};
isSafari = function() {
return /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
};
isOpera = function() {
return /OPR/.test(navigator.userAgent) && /Opera/.test(navigator.vendor);
};
isFF = function() {
return /Firefox/.test(navigator.userAgent);
};
isIE = function() {
return /MSIE/.test(navigator.userAgent);
};
safariSupport = function() {
var m;
m = navigator.userAgent.match(/AppleWebKit\/(\d+)\.(\d+)(\.|\+|\s)/);
if (!m) {
return false;
}
return 537.38 <= parseInt(m[1], 10) + (parseInt(m[2], 10) / 100);
};
operaSupport = function() {
var m;
m = navigator.userAgent.match(/OPR\/(\d+)\./);
if (!m) {
return false;
}
return 15 <= parseInt(m[1], 10);
};
ffSupport = function() {
return window.console.firebug || window.console.exception;
};
if (isIE() || (isFF() && !ffSupport()) || (isOpera() && !operaSupport()) || (isSafari() && !safariSupport())) {
window.log = _log;
} else {
window.log = log;
}
window.log.l = _log;
}).call(this);
+922
View File
@@ -0,0 +1,922 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
</head>
<body>
<div class="instructions">Scroll the page</div>
<h2>THE END OF THE TETHER</h2>
<p>By Joseph Conrad</p>
<h3>Chapter I</h3>
<p>For a long time after the course of the steamer <em>Sofala</em> had been
altered for the land, the low swampy coast had retained its appearance
of a mere smudge of darkness beyond a belt of glitter. The sunrays
seemed to fall violently upon the calm sea--seemed to shatter themselves
upon an adamantine surface into sparkling dust, into a dazzling vapor
of light that blinded the eye and wearied the brain with its unsteady
brightness.</p>
<p>Captain Whalley did not look at it. When his Serang, approaching the
roomy cane arm-chair which he filled capably, had informed him in a low
voice that the course was to be altered, he had risen at once and had
remained on his feet, face forward, while the head of his ship swung
through a quarter of a circle. He had not uttered a single word, not
even the word to steady the helm. It was the Serang, an elderly, alert,
little Malay, with a very dark skin, who murmured the order to the
helmsman. And then slowly Captain Whalley sat down again in the
arm-chair on the bridge and fixed his eyes on the deck between his feet.</p>
<p>He could not hope to see anything new upon this lane of the sea. He had
been on these coasts for the last three years. From Low Cape to Malantan
the distance was fifty miles, six hours' steaming for the old ship with
the tide, or seven against. Then you steered straight for the land, and
by-and-by three palms would appear on the sky, tall and slim, and with
their disheveled heads in a bunch, as if in confidential criticism of
the dark mangroves. The Sofala would be headed towards the somber
strip of the coast, which at a given moment, as the ship closed with
it obliquely, would show several clean shining fractures--the brimful
estuary of a river. Then on through a brown liquid, three parts water
and one part black earth, on and on between the low shores, three parts
black earth and one part brackish water, the Sofala would plow her way
up-stream, as she had done once every month for these seven years or
more, long before he was aware of her existence, long before he had ever
thought of having anything to do with her and her invariable voyages.
The old ship ought to have known the road better than her men, who had
not been kept so long at it without a change; better than the faithful
Serang, whom he had brought over from his last ship to keep the
captain's watch; better than he himself, who had been her captain for
the last three years only. She could always be depended upon to make her
courses. Her compasses were never out. She was no trouble at all to
take about, as if her great age had given her knowledge, wisdom, and
steadiness. She made her landfalls to a degree of the bearing, and
almost to a minute of her allowed time. At any moment, as he sat on
the bridge without looking up, or lay sleepless in his bed, simply by
reckoning the days and the hours he could tell where he was--the precise
spot of the beat. He knew it well too, this monotonous huckster's
round, up and down the Straits; he knew its order and its sights and its
people. Malacca to begin with, in at daylight and out at dusk, to cross
over with a rigid phosphorescent wake this highway of the Far East.
Darkness and gleams on the water, clear stars on a black sky, perhaps
the lights of a home steamer keeping her unswerving course in the
middle, or maybe the elusive shadow of a native craft with her mat sails
flitting by silently--and the low land on the other side in sight
at daylight. At noon the three palms of the next place of call, up a
sluggish river. The only white man residing there was a retired young
sailor, with whom he had become friendly in the course of many voyages.
Sixty miles farther on there was another place of call, a deep bay with
only a couple of houses on the beach. And so on, in and out, picking
up coastwise cargo here and there, and finishing with a hundred miles'
steady steaming through the maze of an archipelago of small islands up
to a large native town at the end of the beat. There was a three days'
rest for the old ship before he started her again in inverse order,
seeing the same shores from another bearing, hearing the same voices
in the same places, back again to the Sofala's port of registry on
the great highway to the East, where he would take up a berth nearly
opposite the big stone pile of the harbor office till it was time to
start again on the old round of 1600 miles and thirty days. Not a very
enterprising life, this, for Captain Whalley, Henry Whalley, otherwise
Dare-devil Harry--Whalley of the Condor, a famous clipper in her day.
No. Not a very enterprising life for a man who had served famous firms,
who had sailed famous ships (more than one or two of them his own); who
had made famous passages, had been the pioneer of new routes and new
trades; who had steered across the unsurveyed tracts of the South Seas,
and had seen the sun rise on uncharted islands. Fifty years at sea, and
forty out in the East ("a pretty thorough apprenticeship," he used
to remark smilingly), had made him honorably known to a generation of
shipowners and merchants in all the ports from Bombay clear over to
where the East merges into the West upon the coast of the two Americas.
His fame remained writ, not very large but plain enough, on the
Admiralty charts. Was there not somewhere between Australia and China a
Whalley Island and a Condor Reef? On that dangerous coral formation the
celebrated clipper had hung stranded for three days, her captain and
crew throwing her cargo overboard with one hand and with the other, as
it were, keeping off her a flotilla of savage war-canoes. At that time
neither the island nor the reef had any official existence. Later the
officers of her Majesty's steam vessel Fusilier, dispatched to make a
survey of the route, recognized in the adoption of these two names the
enterprise of the man and the solidity of the ship. Besides, as anyone
who cares may see, the "General Directory," vol. ii. p. 410, begins the
description of the "Malotu or Whalley Passage" with the words: "This
advantageous route, first discovered in 1850 by Captain Whalley in the
ship Condor," &amp;c., and ends by recommending it warmly to sailing vessels
leaving the China ports for the south in the months from December to
April inclusive.</p>
<p>This was the clearest gain he had out of life. Nothing could rob him
of this kind of fame. The piercing of the Isthmus of Suez, like the
breaking of a dam, had let in upon the East a flood of new ships, new
men, new methods of trade. It had changed the face of the Eastern seas
and the very spirit of their life; so that his early experiences meant
nothing whatever to the new generation of seamen.</p>
<p>In those bygone days he had handled many thousands of pounds of his
employers' money and of his own; he had attended faithfully, as by law
a shipmaster is expected to do, to the conflicting interests of owners,
charterers, and underwriters. He had never lost a ship or consented to
a shady transaction; and he had lasted well, outlasting in the end the
conditions that had gone to the making of his name. He had buried his
wife (in the Gulf of Petchili), had married off his daughter to the man
of her unlucky choice, and had lost more than an ample competence in the
crash of the notorious Travancore and Deccan Banking Corporation, whose
downfall had shaken the East like an earthquake. And he was sixty-five
years old.</p>
<h3>Chapter II</h3>
<p>His age sat lightly enough on him; and of his ruin he was not ashamed.
He had not been alone to believe in the stability of the Banking
Corporation. Men whose judgment in matters of finance was as expert as
his seamanship had commended the prudence of his investments, and had
themselves lost much money in the great failure. The only difference
between him and them was that he had lost his all. And yet not his all.
There had remained to him from his lost fortune a very pretty little
bark, Fair Maid, which he had bought to occupy his leisure of a retired
sailor--"to play with," as he expressed it himself.</p>
<p>He had formally declared himself tired of the sea the year preceding his
daughter's marriage. But after the young couple had gone to settle in
Melbourne he found out that he could not make himself happy on shore. He
was too much of a merchant sea-captain for mere yachting to satisfy him.
He wanted the illusion of affairs; and his acquisition of the Fair
Maid preserved the continuity of his life. He introduced her to his
acquaintances in various ports as "my last command." When he grew too
old to be trusted with a ship, he would lay her up and go ashore to be
buried, leaving directions in his will to have the bark towed out and
scuttled decently in deep water on the day of the funeral. His daughter
would not grudge him the satisfaction of knowing that no stranger would
handle his last command after him. With the fortune he was able to leave
her, the value of a 500-ton bark was neither here nor there. All this
would be said with a jocular twinkle in his eye: the vigorous old man
had too much vitality for the sentimentalism of regret; and a little
wistfully withal, because he was at home in life, taking a genuine
pleasure in its feelings and its possessions; in the dignity of his
reputation and his wealth, in his love for his daughter, and in his
satisfaction with the ship--the plaything of his lonely leisure.</p>
<p>He had the cabin arranged in accordance with his simple ideal of comfort
at sea. A big bookcase (he was a great reader) occupied one side of his
stateroom; the portrait of his late wife, a flat bituminous oil-painting
representing the profile and one long black ringlet of a young woman,
faced his bed-place. Three chronometers ticked him to sleep and greeted
him on waking with the tiny competition of their beats. He rose at five
every day. The officer of the morning watch, drinking his early cup
of coffee aft by the wheel, would hear through the wide orifice of the
copper ventilators all the splashings, blowings, and splutterings of
his captain's toilet. These noises would be followed by a sustained
deep murmur of the Lord's Prayer recited in a loud earnest voice. Five
minutes afterwards the head and shoulders of Captain Whalley emerged
out of the companion-hatchway. Invariably he paused for a while on the
stairs, looking all round at the horizon; upwards at the trim of the
sails; inhaling deep draughts of the fresh air. Only then he would step
out on the poop, acknowledging the hand raised to the peak of the cap
with a majestic and benign "Good morning to you." He walked the deck
till eight scrupulously. Sometimes, not above twice a year, he had to
use a thick cudgel-like stick on account of a stiffness in the hip--a
slight touch of rheumatism, he supposed. Otherwise he knew nothing of
the ills of the flesh. At the ringing of the breakfast bell he went
below to feed his canaries, wind up the chronometers, and take the
head of the table. From there he had before his eyes the big carbon
photographs of his daughter, her husband, and two fat-legged babies
--his grandchildren--set in black frames into the maplewood bulkheads
of the cuddy. After breakfast he dusted the glass over these portraits
himself with a cloth, and brushed the oil painting of his wife with a
plumate kept suspended from a small brass hook by the side of the heavy
gold frame. Then with the door of his stateroom shut, he would sit down
on the couch under the portrait to read a chapter out of a thick pocket
Bible--her Bible. But on some days he only sat there for half an hour
with his finger between the leaves and the closed book resting on his
knees. Perhaps he had remembered suddenly how fond of boat-sailing she
used to be.</p>
<p>She had been a real shipmate and a true woman too. It was like an
article of faith with him that there never had been, and never could be,
a brighter, cheerier home anywhere afloat or ashore than his home under
the poop-deck of the Condor, with the big main cabin all white and gold,
garlanded as if for a perpetual festival with an unfading wreath. She
had decorated the center of every panel with a cluster of home flowers.
It took her a twelvemonth to go round the cuddy with this labor of love.
To him it had remained a marvel of painting, the highest achievement of
taste and skill; and as to old Swinburne, his mate, every time he
came down to his meals he stood transfixed with admiration before the
progress of the work. You could almost smell these roses, he declared,
sniffing the faint flavor of turpentine which at that time pervaded the
saloon, and (as he confessed afterwards) made him somewhat less hearty
than usual in tackling his food. But there was nothing of the sort to
interfere with his enjoyment of her singing. "Mrs. Whalley is a regular
out-and-out nightingale, sir," he would pronounce with a judicial air
after listening profoundly over the skylight to the very end of the
piece. In fine weather, in the second dog-watch, the two men could hear
her trills and roulades going on to the accompaniment of the piano in
the cabin. On the very day they got engaged he had written to London
for the instrument; but they had been married for over a year before it
reached them, coming out round the Cape. The big case made part of the
first direct general cargo landed in Hong-kong harbor--an event that to
the men who walked the busy quays of to-day seemed as hazily remote as
the dark ages of history. But Captain Whalley could in a half hour of
solitude live again all his life, with its romance, its idyl, and its
sorrow. He had to close her eyes himself. She went away from under the
ensign like a sailor's wife, a sailor herself at heart. He had read
the service over her, out of her own prayer-book, without a break in his
voice. When he raised his eyes he could see old Swinburne facing him
with his cap pressed to his breast, and his rugged, weather-beaten,
impassive face streaming with drops of water like a lump of chipped red
granite in a shower. It was all very well for that old sea-dog to cry.
He had to read on to the end; but after the splash he did not remember
much of what happened for the next few days. An elderly sailor of the
crew, deft at needlework, put together a mourning frock for the child
out of one of her black skirts.</p>
<p>He was not likely to forget; but you cannot dam up life like a sluggish
stream. It will break out and flow over a man's troubles, it will close
upon a sorrow like the sea upon a dead body, no matter how much love has
gone to the bottom. And the world is not bad. People had been very
kind to him; especially Mrs. Gardner, the wife of the senior partner
in Gardner, Patteson, &amp; Co., the owners of the Condor. It was she who
volunteered to look after the little one, and in due course took her to
England (something of a journey in those days, even by the overland
mail route) with her own girls to finish her education. It was ten years
before he saw her again.</p>
<p>As a little child she had never been frightened of bad weather; she
would beg to be taken up on deck in the bosom of his oilskin coat to
watch the big seas hurling themselves upon the Condor. The swirl and
crash of the waves seemed to fill her small soul with a breathless
delight. "A good boy spoiled," he used to say of her in joke. He had
named her Ivy because of the sound of the word, and obscurely fascinated
by a vague association of ideas. She had twined herself tightly round
his heart, and he intended her to cling close to her father as to a
tower of strength; forgetting, while she was little, that in the nature
of things she would probably elect to cling to someone else. But
he loved life well enough for even that event to give him a certain
satisfaction, apart from his more intimate feeling of loss.</p>
<p>After he had purchased the Fair Maid to occupy his loneliness, he
hastened to accept a rather unprofitable freight to Australia simply for
the opportunity of seeing his daughter in her own home. What made him
dissatisfied there was not to see that she clung now to somebody else,
but that the prop she had selected seemed on closer examination "a
rather poor stick"--even in the matter of health. He disliked his
son-in-law's studied civility perhaps more than his method of
handling the sum of money he had given Ivy at her marriage. But of his
apprehensions he said nothing. Only on the day of his departure, with
the hall-door open already, holding her hands and looking steadily into
her eyes, he had said, "You know, my dear, all I have is for you and the
chicks. Mind you write to me openly." She had answered him by an almost
imperceptible movement of her head. She resembled her mother in
the color of her eyes, and in character--and also in this, that she
understood him without many words.</p>
<p>Sure enough she had to write; and some of these letters made Captain
Whalley lift his white eye-brows. For the rest he considered he was
reaping the true reward of his life by being thus able to produce on
demand whatever was needed. He had not enjoyed himself so much in a
way since his wife had died. Characteristically enough his son-in-law's
punctuality in failure caused him at a distance to feel a sort of
kindness towards the man. The fellow was so perpetually being jammed on
a lee shore that to charge it all to his reckless navigation would be
manifestly unfair. No, no! He knew well what that meant. It was bad
luck. His own had been simply marvelous, but he had seen in his life too
many good men--seamen and others--go under with the sheer weight of bad
luck not to recognize the fatal signs. For all that, he was cogitating
on the best way of tying up very strictly every penny he had to leave,
when, with a preliminary rumble of rumors (whose first sound reached
him in Shanghai as it happened), the shock of the big failure came;
and, after passing through the phases of stupor, of incredulity, of
indignation, he had to accept the fact that he had nothing to speak of
to leave.</p>
<p>Upon that, as if he had only waited for this catastrophe, the unlucky
man, away there in Melbourne, gave up his unprofitable game, and sat
down--in an invalid's bath-chair at that too. "He will never walk
again," wrote the wife. For the first time in his life Captain Whalley
was a bit staggered.</p>
<p>The Fair Maid had to go to work in bitter earnest now. It was no longer
a matter of preserving alive the memory of Dare-devil Harry Whalley in
the Eastern Seas, or of keeping an old man in pocket-money and clothes,
with, perhaps, a bill for a few hundred first-class cigars thrown in at
the end of the year. He would have to buckle-to, and keep her going hard
on a scant allowance of gilt for the ginger-bread scrolls at her stem
and stern.</p>
<p>This necessity opened his eyes to the fundamental changes of the world.
Of his past only the familiar names remained, here and there, but
the things and the men, as he had known them, were gone. The name of
Gardner, Patteson, &amp; Co. was still displayed on the walls of warehouses
by the waterside, on the brass plates and window-panes in the business
quarters of more than one Eastern port, but there was no longer a
Gardner or a Patteson in the firm. There was no longer for Captain
Whalley an arm-chair and a welcome in the private office, with a bit of
business ready to be put in the way of an old friend, for the sake of
bygone services. The husbands of the Gardner girls sat behind the desks
in that room where, long after he had left the employ, he had kept his
right of entrance in the old man's time. Their ships now had yellow
funnels with black tops, and a time-table of appointed routes like a
confounded service of tramways. The winds of December and June were all
one to them; their captains (excellent young men he doubted not) were,
to be sure, familiar with Whalley Island, because of late years the
Government had established a white fixed light on the north end (with
a red danger sector over the Condor Reef), but most of them would have
been extremely surprised to hear that a flesh-and-blood Whalley still
existed--an old man going about the world trying to pick up a cargo here
and there for his little bark.</p>
<p>And everywhere it was the same. Departed the men who would have nodded
appreciatively at the mention of his name, and would have thought
themselves bound in honor to do something for Dare-devil Harry Whalley.
Departed the opportunities which he would have known how to seize; and
gone with them the white-winged flock of clippers that lived in the
boisterous uncertain life of the winds, skimming big fortunes out of
the foam of the sea. In a world that pared down the profits to an
irreducible minimum, in a world that was able to count its disengaged
tonnage twice over every day, and in which lean charters were snapped up
by cable three months in advance, there were no chances of fortune for
an individual wandering haphazard with a little bark--hardly indeed any
room to exist.</p>
<p>He found it more difficult from year to year. He suffered greatly from
the smallness of remittances he was able to send his daughter. Meantime
he had given up good cigars, and even in the matter of inferior cheroots
limited himself to six a day. He never told her of his difficulties, and
she never enlarged upon her struggle to live. Their confidence in each
other needed no explanations, and their perfect understanding endured
without protestations of gratitude or regret. He would have been shocked
if she had taken it into her head to thank him in so many words, but
he found it perfectly natural that she should tell him she needed two
hundred pounds.</p>
<p>He had come in with the Fair Maid in ballast to look for a freight in
the Sofala's port of registry, and her letter met him there. Its tenor
was that it was no use mincing matters. Her only resource was in opening
a boarding-house, for which the prospects, she judged, were good. Good
enough, at any rate, to make her tell him frankly that with two hundred
pounds she could make a start. He had torn the envelope open, hastily,
on deck, where it was handed to him by the ship-chandler's runner, who
had brought his mail at the moment of anchoring. For the second time
in his life he was appalled, and remained stock-still at the cabin door
with the paper trembling between his fingers. Open a boarding-house! Two
hundred pounds for a start! The only resource! And he did not know where
to lay his hands on two hundred pence.</p>
<p>All that night Captain Whalley walked the poop of his anchored ship, as
though he had been about to close with the land in thick weather, and
uncertain of his position after a run of many gray days without a sight
of sun, moon, or stars. The black night twinkled with the guiding lights
of seamen and the steady straight lines of lights on shore; and all
around the Fair Maid the riding lights of ships cast trembling trails
upon the water of the roadstead. Captain Whalley saw not a gleam
anywhere till the dawn broke and he found out that his clothing was
soaked through with the heavy dew.</p>
<p>His ship was awake. He stopped short, stroked his wet beard, and
descended the poop ladder backwards, with tired feet. At the sight
of him the chief officer, lounging about sleepily on the quarterdeck,
remained open-mouthed in the middle of a great early-morning yawn.</p>
<p>"Good morning to you," pronounced Captain Whalley solemnly, passing into
the cabin. But he checked himself in the doorway, and without looking
back, "By the bye," he said, "there should be an empty wooden case put
away in the lazarette. It has not been broken up--has it?"</p>
<p>The mate shut his mouth, and then asked as if dazed, "What empty case,
sir?"</p>
<p>"A big flat packing-case belonging to that painting in my room. Let it
be taken up on deck and tell the carpenter to look it over. I may want
to use it before long."</p>
<p>The chief officer did not stir a limb till he had heard the door of the
captain's state-room slam within the cuddy. Then he beckoned aft the
second mate with his forefinger to tell him that there was something "in
the wind."</p>
<p>When the bell rang Captain Whalley's authoritative voice boomed out
through a closed door, "Sit down and don't wait for me." And his
impressed officers took their places, exchanging looks and whispers
across the table. What! No breakfast? And after apparently knocking
about all night on deck, too! Clearly, there was something in the wind.
In the skylight above their heads, bowed earnestly over the plates,
three wire cages rocked and rattled to the restless jumping of the
hungry canaries; and they could detect the sounds of their "old
man's" deliberate movements within his state-room. Captain Whalley was
methodically winding up the chronometers, dusting the portrait of
his late wife, getting a clean white shirt out of the drawers, making
himself ready in his punctilious unhurried manner to go ashore. He could
not have swallowed a single mouthful of food that morning. He had made
up his mind to sell the Fair Maid.</p>
<h3>Chapter III</h3>
<p>Just at that time the Japanese were casting far and wide for ships
of European build, and he had no difficulty in finding a purchaser, a
speculator who drove a hard bargain, but paid cash down for the Fair
Maid, with a view to a profitable resale. Thus it came about that
Captain Whalley found himself on a certain afternoon descending the
steps of one of the most important post-offices of the East with a slip
of bluish paper in his hand. This was the receipt of a registered letter
enclosing a draft for two hundred pounds, and addressed to Melbourne.
Captain Whalley pushed the paper into his waistcoat-pocket, took his
stick from under his arm, and walked down the street.</p>
<p>It was a recently opened and untidy thoroughfare with rudimentary
side-walks and a soft layer of dust cushioning the whole width of
the road. One end touched the slummy street of Chinese shops near the
harbor, the other drove straight on, without houses, for a couple of
miles, through patches of jungle-like vegetation, to the yard gates
of the new Consolidated Docks Company. The crude frontages of the new
Government buildings alternated with the blank fencing of vacant plots,
and the view of the sky seemed to give an added spaciousness to the
broad vista. It was empty and shunned by natives after business
hours, as though they had expected to see one of the tigers from the
neighborhood of the New Waterworks on the hill coming at a loping canter
down the middle to get a Chinese shopkeeper for supper. Captain Whalley
was not dwarfed by the solitude of the grandly planned street. He
had too fine a presence for that. He was only a lonely figure walking
purposefully, with a great white beard like a pilgrim, and with a thick
stick that resembled a weapon. On one side the new Courts of Justice had
a low and unadorned portico of squat columns half concealed by a few old
trees left in the approach. On the other the pavilion wings of the
new Colonial Treasury came out to the line of the street. But Captain
Whalley, who had now no ship and no home, remembered in passing that
on that very site when he first came out from England there had stood a
fishing village, a few mat huts erected on piles between a muddy tidal
creek and a miry pathway that went writhing into a tangled wilderness
without any docks or waterworks.</p>
<p>No ship--no home. And his poor Ivy away there had no home either. A
boarding-house is no sort of home though it may get you a living. His
feelings were horribly rasped by the idea of the boarding-house. In his
rank of life he had that truly aristocratic temperament characterized by
a scorn of vulgar gentility and by prejudiced views as to the derogatory
nature of certain occupations. For his own part he had always preferred
sailing merchant ships (which is a straightforward occupation) to buying
and selling merchandise, of which the essence is to get the better of
somebody in a bargain--an undignified trial of wits at best. His father
had been Colonel Whalley (retired) of the H. E. I. Company's service,
with very slender means besides his pension, but with distinguished
connections. He could remember as a boy how frequently waiters at the
inns, country tradesmen and small people of that sort, used to "My lord"
the old warrior on the strength of his appearance.</p>
<p>Captain Whalley himself (he would have entered the Navy if his father
had not died before he was fourteen) had something of a grand air which
would have suited an old and glorious admiral; but he became lost like
a straw in the eddy of a brook amongst the swarm of brown and yellow
humanity filling a thoroughfare, that by contrast with the vast and
empty avenue he had left seemed as narrow as a lane and absolutely
riotous with life. The walls of the houses were blue; the shops of the
Chinamen yawned like cavernous lairs; heaps of nondescript merchandise
overflowed the gloom of the long range of arcades, and the fiery
serenity of sunset took the middle of the street from end to end with a
glow like the reflection of a fire. It fell on the bright colors and the
dark faces of the bare-footed crowd, on the pallid yellow backs of the
half-naked jostling coolies, on the accouterments of a tall Sikh trooper
with a parted beard and fierce mustaches on sentry before the gate of
the police compound. Looming very big above the heads in a red haze of
dust, the tightly packed car of the cable tramway navigated cautiously
up the human stream, with the incessant blare of its horn, in the manner
of a steamer groping in a fog.</p>
<p>Captain Whalley emerged like a diver on the other side, and in the
desert shade between the walls of closed warehouses removed his hat to
cool his brow. A certain disrepute attached to the calling of a
landlady of a boarding-house. These women were said to be rapacious,
unscrupulous, untruthful; and though he contemned no class of his
fellow-creatures--God forbid!--these were suspicions to which it was
unseemly that a Whalley should lay herself open. He had not expostulated
with her, however. He was confident she shared his feelings; he was
sorry for her; he trusted her judgment; he considered it a merciful
dispensation that he could help her once more,--but in his aristocratic
heart of hearts he would have found it more easy to reconcile himself to
the idea of her turning seamstress. Vaguely he remembered reading years
ago a touching piece called the "Song of the Shirt." It was all very
well making songs about poor women. The granddaughter of Colonel
Whalley, the landlady of a boarding-house! Pooh! He replaced his hat,
dived into two pockets, and stopping a moment to apply a flaring match
to the end of a cheap cheroot, blew an embittered cloud of smoke at a
world that could hold such surprises.</p>
<p>Of one thing he was certain--that she was the own child of a clever
mother. Now he had got over the wrench of parting with his ship, he
perceived clearly that such a step had been unavoidable. Perhaps he had
been growing aware of it all along with an unconfessed knowledge. But
she, far away there, must have had an intuitive perception of it, with
the pluck to face that truth and the courage to speak out--all the
qualities which had made her mother a woman of such excellent counsel.</p>
<p>It would have had to come to that in the end! It was fortunate she had
forced his hand. In another year or two it would have been an utterly
barren sale. To keep the ship going he had been involving himself deeper
every year. He was defenseless before the insidious work of adversity,
to whose more open assaults he could present a firm front; like a
cliff that stands unmoved the open battering of the sea, with a lofty
ignorance of the treacherous backwash undermining its base. As it was,
every liability satisfied, her request answered, and owing no man a
penny, there remained to him from the proceeds a sum of five hundred
pounds put away safely. In addition he had upon his person some forty
odd dollars--enough to pay his hotel bill, providing he did not linger
too long in the modest bedroom where he had taken refuge.</p>
<p>Scantily furnished, and with a waxed floor, it opened into one of
the side-verandas. The straggling building of bricks, as airy as a
bird-cage, resounded with the incessant flapping of rattan screens
worried by the wind between the white-washed square pillars of the
sea-front. The rooms were lofty, a ripple of sunshine flowed over the
ceilings; and the periodical invasions of tourists from some passenger
steamer in the harbor flitted through the wind-swept dusk of the
apartments with the tumult of their unfamiliar voices and impermanent
presences, like relays of migratory shades condemned to speed headlong
round the earth without leaving a trace. The babble of their irruptions
ebbed out as suddenly as it had arisen; the draughty corridors and
the long chairs of the verandas knew their sight-seeing hurry or
their prostrate repose no more; and Captain Whalley, substantial and
dignified, left well-nigh alone in the vast hotel by each light-hearted
skurry, felt more and more like a stranded tourist with no aim in view,
like a forlorn traveler without a home. In the solitude of his room he
smoked thoughtfully, gazing at the two sea-chests which held all that he
could call his own in this world. A thick roll of charts in a sheath
of sailcloth leaned in a corner; the flat packing-case containing the
portrait in oils and the three carbon photographs had been pushed under
the bed. He was tired of discussing terms, of assisting at surveys, of
all the routine of the business. What to the other parties was merely
the sale of a ship was to him a momentous event involving a radically
new view of existence. He knew that after this ship there would be no
other; and the hopes of his youth, the exercise of his abilities, every
feeling and achievement of his manhood, had been indissolubly connected
with ships. He had served ships; he had owned ships; and even the years
of his actual retirement from the sea had been made bearable by the idea
that he had only to stretch out his hand full of money to get a ship. He
had been at liberty to feel as though he were the owner of all the
ships in the world. The selling of this one was weary work; but when
she passed from him at last, when he signed the last receipt, it was as
though all the ships had gone out of the world together, leaving him on
the shore of inaccessible oceans with seven hundred pounds in his hands.</p>
<p>Striding firmly, without haste, along the quay, Captain Whalley averted
his glances from the familiar roadstead. Two generations of seamen born
since his first day at sea stood between him and all these ships at the
anchorage. His own was sold, and he had been asking himself, What next?</p>
<p>From the feeling of loneliness, of inward emptiness,--and of loss
too, as if his very soul had been taken out of him forcibly,--there had
sprung at first a desire to start right off and join his daughter.
"Here are the last pence," he would say to her; "take them, my dear. And
here's your old father: you must take him too."</p>
<p>His soul recoiled, as if afraid of what lay hidden at the bottom of
this impulse. Give up! Never! When one is thoroughly weary all sorts of
nonsense come into one's head. A pretty gift it would have been for a
poor woman--this seven hundred pounds with the incumbrance of a hale old
fellow more than likely to last for years and years to come. Was he not
as fit to die in harness as any of the youngsters in charge of these
anchored ships out yonder? He was as solid now as ever he had been. But
as to who would give him work to do, that was another matter. Were he,
with his appearance and antecedents, to go about looking for a junior's
berth, people, he was afraid, would not take him seriously; or else if
he succeeded in impressing them, he would maybe obtain their pity, which
would be like stripping yourself naked to be kicked. He was not anxious
to give himself away for less than nothing. He had no use for anybody's
pity. On the other hand, a command--the only thing he could try for with
due regard for common decency--was not likely to be lying in wait
for him at the corner of the next street. Commands don't go a-begging
nowadays. Ever since he had come ashore to carry out the business of
the sale he had kept his ears open, but had heard no hint of one being
vacant in the port. And even if there had been one, his successful past
itself stood in his way. He had been his own employer too long. The only
credential he could produce was the testimony of his whole life. What
better recommendation could anyone require? But vaguely he felt that
the unique document would be looked upon as an archaic curiosity of the
Eastern waters, a screed traced in obsolete words--in a half-forgotten
language.</p>
<h3>Chapter IV</h3>
<p>Revolving these thoughts, he strolled on near the railings of the quay,
broad-chested, without a stoop, as though his big shoulders had never
felt the burden of the loads that must be carried between the cradle
and the grave. No single betraying fold or line of care disfigured the
reposeful modeling of his face. It was full and untanned; and the upper
part emerged, massively quiet, out of the downward flow of silvery hair,
with the striking delicacy of its clear complexion and the powerful
width of the forehead. The first cast of his glance fell on you candid
and swift, like a boy's; but because of the ragged snowy thatch of the
eyebrows the affability of his attention acquired the character of a
dark and searching scrutiny. With age he had put on flesh a little, had
increased his girth like an old tree presenting no symptoms of decay;
and even the opulent, lustrous ripple of white hairs upon his chest
seemed an attribute of unquenchable vitality and vigor.</p>
<p>Once rather proud of his great bodily strength, and even of his personal
appearance, conscious of his worth, and firm in his rectitude, there had
remained to him, like the heritage of departed prosperity, the tranquil
bearing of a man who had proved himself fit in every sort of way for the
life of his choice. He strode on squarely under the projecting brim of
an ancient Panama hat. It had a low crown, a crease through its whole
diameter, a narrow black ribbon. Imperishable and a little discolored,
this headgear made it easy to pick him out from afar on thronged wharves
and in the busy streets. He had never adopted the comparatively modern
fashion of pipeclayed cork helmets. He disliked the form; and he hoped
he could manage to keep a cool head to the end of his life without all
these contrivances for hygienic ventilation. His hair was cropped close,
his linen always of immaculate whiteness; a suit of thin gray flannel,
worn threadbare but scrupulously brushed, floated about his burly limbs,
adding to his bulk by the looseness of its cut. The years had mellowed
the good-humored, imperturbable audacity of his prime into a temper
carelessly serene; and the leisurely tapping of his iron-shod stick
accompanied his footfalls with a self-confident sound on the flagstones.
It was impossible to connect such a fine presence and this unruffled
aspect with the belittling troubles of poverty; the man's whole
existence appeared to pass before you, facile and large, in the freedom
of means as ample as the clothing of his body.</p>
<p>The irrational dread of having to break into his five hundred pounds for
personal expenses in the hotel disturbed the steady poise of his mind.
There was no time to lose. The bill was running up. He nourished the
hope that this five hundred would perhaps be the means, if everything
else failed, of obtaining some work which, keeping his body and soul
together (not a matter of great outlay), would enable him to be of use
to his daughter. To his mind it was her own money which he employed, as
it were, in backing her father and solely for her benefit. Once at work,
he would help her with the greater part of his earnings; he was good for
many years yet, and this boarding-house business, he argued to himself,
whatever the prospects, could not be much of a gold-mine from the first
start. But what work? He was ready to lay hold of anything in an honest
way so that it came quickly to his hand; because the five hundred pounds
must be preserved intact for eventual use. That was the great point.
With the entire five hundred one felt a substance at one's back; but
it seemed to him that should he let it dwindle to four-fifty or even
four-eighty, all the efficiency would be gone out of the money, as though
there were some magic power in the round figure. But what sort of work?</p>
<p>Confronted by that haunting question as by an uneasy ghost, for whom he
had no exorcising formula, Captain Whalley stopped short on the apex
of a small bridge spanning steeply the bed of a canalized creek with
granite shores. Moored between the square blocks a seagoing Malay prau
floated half hidden under the arch of masonry, with her spars lowered
down, without a sound of life on board, and covered from stem to stern
with a ridge of palm-leaf mats. He had left behind him the overheated
pavements bordered by the stone frontages that, like the sheer face of
cliffs, followed the sweep of the quays; and an unconfined spaciousness
of orderly and sylvan aspect opened before him its wide plots of rolled
grass, like pieces of green carpet smoothly pegged out, its long ranges
of trees lined up in colossal porticos of dark shafts roofed with a
vault of branches.</p>
<p>Some of these avenues ended at the sea. It was a terraced shore; and
beyond, upon the level expanse, profound and glistening like the gaze
of a dark-blue eye, an oblique band of stippled purple lengthened itself
indefinitely through the gap between a couple of verdant twin islets.
The masts and spars of a few ships far away, hull down in the outer
roads, sprang straight from the water in a fine maze of rosy lines
penciled on the clear shadow of the eastern board. Captain Whalley gave
them a long glance. The ship, once his own, was anchored out there. It
was staggering to think that it was open to him no longer to take a boat
at the jetty and get himself pulled off to her when the evening came. To
no ship. Perhaps never more. Before the sale was concluded, and till the
purchase-money had been paid, he had spent daily some time on board the
Fair Maid. The money had been paid this very morning, and now, all at
once, there was positively no ship that he could go on board of when he
liked; no ship that would need his presence in order to do her work--to
live. It seemed an incredible state of affairs, something too bizarre
to last. And the sea was full of craft of all sorts. There was that prau
lying so still swathed in her shroud of sewn palm-leaves--she too had
her indispensable man. They lived through each other, this Malay he had
never seen, and this high-sterned thing of no size that seemed to be
resting after a long journey. And of all the ships in sight, near and
far, each was provided with a man, the man without whom the finest ship
is a dead thing, a floating and purposeless log.</p>
<p>After his one glance at the roadstead he went on, since there was
nothing to turn back for, and the time must be got through somehow. The
avenues of big trees ran straight over the Esplanade, cutting each other
at diverse angles, columnar below and luxuriant above. The interlaced
boughs high up there seemed to slumber; not a leaf stirred overhead:
and the reedy cast-iron lampposts in the middle of the road, gilt like
scepters, diminished in a long perspective, with their globes of white
porcelain atop, resembling a barbarous decoration of ostriches' eggs
displayed in a row. The flaming sky kindled a tiny crimson spark upon
the glistening surface of each glassy shell.</p>
<p>With his chin sunk a little, his hands behind his back, and the end of
his stick marking the gravel with a faint wavering line at his heels,
Captain Whalley reflected that if a ship without a man was like a body
without a soul, a sailor without a ship was of not much more account
in this world than an aimless log adrift upon the sea. The log might be
sound enough by itself, tough of fiber, and hard to destroy--but what of
that! And a sudden sense of irremediable idleness weighted his feet like
a great fatigue.</p>
<p>A succession of open carriages came bowling along the newly opened
sea-road. You could see across the wide grass-plots the discs of
vibration made by the spokes. The bright domes of the parasols swayed
lightly outwards like full-blown blossoms on the rim of a vase; and
the quiet sheet of dark-blue water, crossed by a bar of purple, made a
background for the spinning wheels and the high action of the horses,
whilst the turbaned heads of the Indian servants elevated above the line
of the sea horizon glided rapidly on the paler blue of the sky. In an
open space near the little bridge each turn-out trotted smartly in a
wide curve away from the sunset; then pulling up sharp, entered the main
alley in a long slow-moving file with the great red stillness of the sky
at the back. The trunks of mighty trees stood all touched with red on
the same side, the air seemed aflame under the high foliage, the
very ground under the hoofs of the horses was red. The wheels turned
solemnly; one after another the sunshades drooped, folding their colors
like gorgeous flowers shutting their petals at the end of the day. In
the whole half-mile of human beings no voice uttered a distinct word,
only a faint thudding noise went on mingled with slight jingling sounds,
and the motionless heads and shoulders of men and women sitting in
couples emerged stolidly above the lowered hoods--as if wooden. But one
carriage and pair coming late did not join the line.</p>
<p>It fled along in a noiseless roll; but on entering the avenue one of the
dark bays snorted, arching his neck and shying against the steel-tipped
pole; a flake of foam fell from the bit upon the point of a satiny
shoulder, and the dusky face of the coachman leaned forward at once over
the hands taking a fresh grip of the reins. It was a long dark-green
landau, having a dignified and buoyant motion between the sharply
curved C-springs, and a sort of strictly official majesty in its supreme
elegance. It seemed more roomy than is usual, its horses seemed slightly
bigger, the appointments a shade more perfect, the servants perched
somewhat higher on the box. The dresses of three women--two young
and pretty, and one, handsome, large, of mature age--seemed to fill
completely the shallow body of the carriage. The fourth face was that
of a man, heavy lidded, distinguished and sallow, with a somber, thick,
iron-gray imperial and mustaches, which somehow had the air of solid
appendages. His Excellency--</p>
<p>The rapid motion of that one equipage made all the others appear utterly
inferior, blighted, and reduced to crawl painfully at a snail's pace.
The landau distanced the whole file in a sort of sustained rush; the
features of the occupant whirling out of sight left behind an impression
of fixed stares and impassive vacancy; and after it had vanished in full
flight as it were, notwithstanding the long line of vehicles hugging the
curb at a walk, the whole lofty vista of the avenue seemed to lie open
and emptied of life in the enlarged impression of an august solitude.</p>
<p>Captain Whalley had lifted his head to look, and his mind, disturbed in
its meditation, turned with wonder (as men's minds will do) to matters
of no importance. It struck him that it was to this port, where he had
just sold his last ship, that he had come with the very first he had
ever owned, and with his head full of a plan for opening a new trade
with a distant part of the Archipelago. The then governor had given
him no end of encouragement. No Excellency he--this Mr. Denham--this
governor with his jacket off; a man who tended night and day, so to
speak, the growing prosperity of the settlement with the self-forgetful
devotion of a nurse for a child she loves; a lone bachelor who lived as
in a camp with the few servants and his three dogs in what was called
then the Government Bungalow: a low-roofed structure on the half-cleared
slope of a hill, with a new flagstaff in front and a police orderly on
the veranda. He remembered toiling up that hill under a heavy sun for
his audience; the unfurnished aspect of the cool shaded room; the long
table covered at one end with piles of papers, and with two guns, a
brass telescope, a small bottle of oil with a feather stuck in the neck
at the other--and the flattering attention given to him by the man in
power. It was an undertaking full of risk he had come to expound, but a
twenty minutes' talk in the Government Bungalow on the hill had made it
go smoothly from the start. And as he was retiring Mr. Denham, already
seated before the papers, called out after him, "Next month the Dido
starts for a cruise that way, and I shall request her captain officially
to give you a look in and see how you get on." The Dido was one of the
smart frigates on the China station--and five-and-thirty years make a
big slice of time. Five-and-thirty years ago an enterprise like his had
for the colony enough importance to be looked after by a Queen's ship.
A big slice of time. Individuals were of some account then. Men like
himself; men, too, like poor Evans, for instance, with his red face,
his coal-black whiskers, and his restless eyes, who had set up the first
patent slip for repairing small ships, on the edge of the forest, in
a lonely bay three miles up the coast. Mr. Denham had encouraged that
enterprise too, and yet somehow poor Evans had ended by dying at
home deucedly hard up. His son, they said, was squeezing oil out of
cocoa-nuts for a living on some God-forsaken islet of the Indian Ocean;
but it was from that patent slip in a lonely wooded bay that had sprung
the workshops of the Consolidated Docks Company, with its three
graving basins carved out of solid rock, its wharves, its jetties,
its electric-light plant, its steam-power houses--with its gigantic
sheer-legs, fit to lift the heaviest weight ever carried afloat, and
whose head could be seen like the top of a queer white monument peeping
over bushy points of land and sandy promontories, as you approached the
New Harbor from the west.</p>
<p>There had been a time when men counted: there were not so many carriages
in the colony then, though Mr. Denham, he fancied, had a buggy. And
Captain Whalley seemed to be swept out of the great avenue by the swirl
of a mental backwash. He remembered muddy shores, a harbor without
quays, the one solitary wooden pier (but that was a public work) jutting
out crookedly, the first coal-sheds erected on Monkey Point, that caught
fire mysteriously and smoldered for days, so that amazed ships came
into a roadstead full of sulphurous smoke, and the sun hung blood-red
at midday. He remembered the things, the faces, and something more
besides--like the faint flavor of a cup quaffed to the bottom, like a
subtle sparkle of the air that was not to be found in the atmosphere of
to-day.</p>
<p>In this evocation, swift and full of detail like a flash of magnesium
light into the niches of a dark memorial hall, Captain Whalley
contemplated things once important, the efforts of small men, the growth
of a great place, but now robbed of all consequence by the greatness
of accomplished facts, by hopes greater still; and they gave him for a
moment such an almost physical grip upon time, such a comprehension of
our unchangeable feelings, that he stopped short, struck the ground with
his stick, and ejaculated mentally, "What the devil am I doing here!" He
seemed lost in a sort of surprise; but he heard his name called out in
wheezy tones once, twice--and turned on his heels slowly.</p>
<p>He beheld then, waddling towards him autocratically, a man of an
old-fashioned and gouty aspect, with hair as white as his own, but with
shaved, florid cheeks, wearing a necktie--almost a neckcloth--whose
stiff ends projected far beyond his chin; with round legs, round arms,
a round body, a round face--generally producing the effect of his short
figure having been distended by means of an air-pump as much as the
seams of his clothing would stand. This was the Master-Attendant of the
port. A master-attendant is a superior sort of harbor-master; a person,
out in the East, of some consequence in his sphere; a Government
official, a magistrate for the waters of the port, and possessed of vast
but ill-defined disciplinary authority over seamen of all classes.
This particular Master-Attendant was reported to consider it miserably
inadequate, on the ground that it did not include the power of life
and death. This was a jocular exaggeration. Captain Eliott was fairly
satisfied with his position, and nursed no inconsiderable sense of such
power as he had. His conceited and tyrannical disposition did not allow
him to let it dwindle in his hands for want of use. The uproarious,
choleric frankness of his comments on people's character and conduct
caused him to be feared at bottom; though in conversation many pretended
not to mind him in the least, others would only smile sourly at the
mention of his name, and there were even some who dared to pronounce him
"a meddlesome old ruffian." But for almost all of them one of Captain
Eliott's outbreaks was nearly as distasteful to face as a chance of
annihilation.</p>
<style>
body {
padding: 15px;
}
.pointer {
padding: 15px;
background-color: rgba(0, 0, 0, 0.4);
color: white;
border-radius: 10px;
pointer-events: none;
opacity: 0;
transition: opacity 300ms;
-webkit-transition: opacity 300ms;
}
.pointer.show {
opacity: 1;
}
</style>
<div class="pointer"></div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.pointer',
attachment: 'middle right',
targetAttachment: 'middle left',
targetModifier: 'scroll-handle',
target: document.body
});
var headers = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
var hideTimeout = null;
var pointer = document.querySelector('.pointer')
var getSection = function(){
var closest, closestTop;
for (var i=0; i < headers.length; i++){
var rect = headers[i].getBoundingClientRect();
if (closestTop === undefined || (rect.top < 0 && rect.top > closestTop)){
closestTop = rect.top;
closest = headers[i];
}
}
return closest.innerHTML;
}
document.addEventListener('scroll', function(){
var percentage = Math.floor((100 * Math.max(0, pageYOffset)) / (document.body.scrollHeight - innerHeight)) + '%'
pointer.innerHTML = getSection() + ' - ' + percentage
pointer.classList.add('show');
if (hideTimeout)
clearTimeout(hideTimeout);
hideTimeout = setTimeout(function(){
pointer.classList.remove('show');
}, 1000);
});
</script>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
</head>
<body>
<div class="instructions">Resize the page to see the Tether flip.</div>
<div class="element"></div>
<div class="target"></div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.element',
target: '.target',
attachment: 'top left',
targetAttachment: 'top right',
constraints: [{
to: 'window',
attachment: 'together'
}]
});
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
</head>
<body>
<div class="element">
</div>
<div class="container">
<div class="pad"></div>
<div class="target"></div>
<div class="pad"></div>
<div class="pad"></div>
</div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.element',
target: '.target',
attachment: 'top center',
targetAttachment: 'bottom center',
constraints: [{
to: 'scrollParent',
attachment: 'together'
}]
});
</script>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<script type="text/javascript" src="//use.typekit.net/jbn8qxr.js"></script>
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>
<link rel="stylesheet" href="../../css/drop.css" />
<link rel="stylesheet" href="../../css/drop-tooltip-theme-arrows.css" />
<style>
body {
font-family: "proxima-nova", "Helvetica Neue", sans-serif;
color: #444;
}
.scroll-parent {
margin: 200px;
height: 300px;
width: 300px;
display: inline-block;
overflow: auto;
border: 2px solid #eee;
padding: 40px;
}
</style>
</head>
<body>
<div class="scroll-parent">
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="top center">Tooltip on Top</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="bottom center">Tooltip on Bottom</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="left middle">Tooltip on Left</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="right middle">Tooltip on Right</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="bottom left">Tooltip on Bottom Left</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="bottom right">Tooltip on Bottom Right</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="top left">Tooltip on Top Left</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="top right">Tooltip on Top Right</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="left bottom">Tooltip on Left Bottom</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="left top">Tooltip on Left Top</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="right bottom">Tooltip on Right Bottom</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
<p><a href="javascript:;" class="drop-tooltip" data-tooltip-content="Whoa, I'm a tooltip" data-attach="right top">Tooltip on Right Top</a></p>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</p>
</div>
<script src="../resources/js/log.js"></script>
<script src="../resources/js/jquery.js"></script>
<script src="../../utils.js"></script>
<script src="../../tether.js"></script>
<script src="/drop/drop.min.js"></script>
<script src="../../tooltip.js"></script>
<script src="../../constraint.js"></script>
<script>
$('.drop-tooltip').each(function(){
new Tooltip({
el: this,
attach: $(this).data('attach')
});
});
</script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
@charset "UTF-8";
/****
colors.css v1.0 For a friendlier looking web
MIT License • http://clrs.cc • http://github.com/mrmrs/colors
Author: mrmrs
http://mrmrs.cc
@mrmrs_
****/
/*
SKINS
• Backgrounds
• Colors
*/
/* Backgrounds */
.bg-navy {
background-color: #001f3f; }
.bg-blue {
background-color: #0074d9; }
.bg-aqua {
background-color: #7fdbff; }
.bg-teal {
background-color: #39cccc; }
.bg-olive {
background-color: #3d9970; }
.bg-green {
background-color: #2ecc40; }
.bg-lime {
background-color: #01ff70; }
.bg-yellow {
background-color: #ffdc00; }
.bg-orange {
background-color: #ff851b; }
.bg-red {
background-color: #ff4136; }
.bg-fuchsia {
background-color: #f012be; }
.bg-purple {
background-color: #b10dc9; }
.bg-maroon {
background-color: #85144b; }
.bg-white {
background-color: white; }
.bg-gray {
background-color: #aaaaaa; }
.bg-silver {
background-color: #dddddd; }
.bg-black {
background-color: #111111; }
/* Colors */
.navy {
color: #001f3f; }
.blue {
color: #0074d9; }
.aqua {
color: #7fdbff; }
.teal {
color: #39cccc; }
.olive {
color: #3d9970; }
.green {
color: #2ecc40; }
.lime {
color: #01ff70; }
.yellow {
color: #ffdc00; }
.orange {
color: #ff851b; }
.red {
color: #ff4136; }
.fuchsia {
color: #f012be; }
.purple {
color: #b10dc9; }
.maroon {
color: #85144b; }
.white {
color: white; }
.silver {
color: #dddddd; }
.gray {
color: #aaaaaa; }
.black {
color: #111111; }
/* PRETTIER LINKS */
a {
text-decoration: none;
-webkit-transition: color .3s ease-in-out;
transition: color .3s ease-in-out; }
a:link {
color: #0074d9;
-webkit-transition: color .3s ease-in-out;
transition: color .3s ease-in-out; }
a:visited {
color: #b10dc9; }
a:hover {
color: #7fdbff;
-webkit-transition: color .3s ease-in-out;
transition: color .3s ease-in-out; }
a:active {
color: #ff851b;
-webkit-transition: color .3s ease-in-out;
transition: color .3s ease-in-out; }
+70
View File
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="./colors.css" />
<style>
* {
box-sizing: border-box;
}
.element {
background-color: #FFDC00;
width: 80%;
max-width: 300px;
padding: 0 15px;
font-size: 20px;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
}
@media (max-width: 380px) {
.element {
font-size: 16px;
}
}
.bit {
width: 10vw;
height: 10vw;
float: left;
}
</style>
</head>
<body>
<div class="element">
<p>This element is tethered to the middle of the visible part of the body.</p>
<p>Inspect the element to see how Tether decided
to use <code>position: fixed</code>.</p>
</div>
<script src="//github.hubspot.com/tether/dist/js/tether.js"></script>
<script>
new Tether({
element: '.element',
target: document.body,
attachment: 'middle center',
targetAttachment: 'middle center',
targetModifier: 'visible'
});
</script>
<script>
// Random colors bit, don't mind this
colors = ['navy', 'blue', 'aqua', 'teal', 'olive', 'green', 'lime',
'yellow', 'orange', 'red', 'fuchsia', 'purple', 'maroon'];
curColors = null;
for(var i=300; i--;){
if (!curColors || !curColors.length)
curColors = colors.slice(0);
var bit = document.createElement('div')
var index = (Math.random() * curColors.length)|0;
bit.className = 'bit bg-' + curColors[index]
curColors.splice(index, 1);
document.body.appendChild(bit);
}
</script>
</body>
</html>
+96
View File
@@ -0,0 +1,96 @@
var del = require('del');
var gulp = require('gulp');
var babel = require('gulp-babel');
var bump = require('gulp-bump');
var concat = require('gulp-concat');
var header = require('gulp-header');
var minify = require('gulp-minify-css');
var plumber = require('gulp-plumber');
var prefixer = require('gulp-autoprefixer');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');
var sass = require('gulp-sass');
var umd = require('gulp-wrap-umd');
// Variables
var distDir = './dist';
var pkg = require('./package.json');
var banner = ['/*!', pkg.name, pkg.version, '*/\n'].join(' ');
var umdOptions = {
exports: 'Tether',
namespace: 'Tether'
};
// Clean
gulp.task('clean', function() {
del.sync([distDir]);
});
// Javascript
gulp.task('js', function() {
gulp.src([
'./src/js/utils.js',
'./src/js/tether.js',
'./src/js/constraint.js',
'./src/js/abutment.js',
'./src/js/shift.js'
])
.pipe(plumber())
.pipe(babel())
.pipe(concat('tether.js'))
.pipe(umd(umdOptions))
.pipe(header(banner))
// Original
.pipe(gulp.dest(distDir + '/js'))
// Minified
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(distDir + '/js'));
});
// CSS
gulp.task('css', function() {
gulp.src('./src/css/**/*.sass')
.pipe(plumber())
.pipe(sass())
.pipe(prefixer())
// Original
.pipe(gulp.dest(distDir + '/css'))
// Minified
.pipe(minify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(distDir + '/css'));
});
// Version bump
var VERSIONS = ['patch', 'minor', 'major'];
for (var i = 0; i < VERSIONS.length; ++i){
(function(version) {
gulp.task('version:' + version, function() {
gulp.src(['package.json', 'bower.json', 'component.json'])
.pipe(bump({type: version}))
.pipe(gulp.dest('.'));
});
})(VERSIONS[i]);
}
// Watch
gulp.task('watch', ['js', 'css'], function() {
gulp.watch('./src/js/**/*', ['js']);
gulp.watch('./src/css/**/*', ['css']);
});
// Defaults
gulp.task('build', ['js', 'css']);
gulp.task('default', ['build']);
+109
View File
@@ -0,0 +1,109 @@
{
"_args": [
[
{
"raw": "tether@^1.4.0",
"scope": null,
"escapedName": "tether",
"name": "tether",
"rawSpec": "^1.4.0",
"spec": ">=1.4.0 <2.0.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel\\node_modules\\bootstrap"
]
],
"_from": "tether@>=1.4.0 <2.0.0",
"_id": "tether@1.4.0",
"_inCache": true,
"_location": "/tether",
"_nodeVersion": "6.9.1",
"_npmOperationalInternal": {
"host": "packages-12-west.internal.npmjs.com",
"tmp": "tmp/tether-1.4.0.tgz_1480717376793_0.8611385466065258"
},
"_npmUser": {
"name": "hs",
"email": "devteam@hubspot.com"
},
"_npmVersion": "3.10.8",
"_phantomChildren": {},
"_requested": {
"raw": "tether@^1.4.0",
"scope": null,
"escapedName": "tether",
"name": "tether",
"rawSpec": "^1.4.0",
"spec": ">=1.4.0 <2.0.0",
"type": "range"
},
"_requiredBy": [
"/bootstrap"
],
"_resolved": "https://registry.npmjs.org/tether/-/tether-1.4.0.tgz",
"_shasum": "0f9fa171f75bf58485d8149e94799d7ae74d1c1a",
"_shrinkwrap": null,
"_spec": "tether@^1.4.0",
"_where": "c:\\xampp\\htdocs\\laravel\\node_modules\\bootstrap",
"authors": [
"Zack Bloom <zackbloom@gmail.com>",
"Adam Schwartz <adam.flynn.schwartz@gmail.com>"
],
"bugs": {
"url": "https://github.com/HubSpot/tether/issues"
},
"dependencies": {},
"description": "A client-side library to make absolutely positioned elements attach to elements in the page efficiently.",
"devDependencies": {
"del": "^2.0.2",
"del-cli": "^0.2.0",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^3.0.1",
"gulp-babel": "^5.2.1",
"gulp-bump": "^0.3.1",
"gulp-concat": "^2.6.0",
"gulp-header": "^1.7.1",
"gulp-minify-css": "^1.2.1",
"gulp-plumber": "^1.0.1",
"gulp-rename": "^1.2.2",
"gulp-sass": "^2.0.4",
"gulp-uglify": "^1.4.1",
"gulp-wrap-umd": "^0.2.1"
},
"directories": {},
"dist": {
"shasum": "0f9fa171f75bf58485d8149e94799d7ae74d1c1a",
"tarball": "https://registry.npmjs.org/tether/-/tether-1.4.0.tgz"
},
"gitHead": "3d7119e590661f8c9e9e566c8a7640c189687215",
"homepage": "https://github.com/HubSpot/tether#readme",
"license": "MIT",
"main": "dist/js/tether.js",
"maintainers": [
{
"name": "hs",
"email": "devteam@hubspot.com"
},
{
"name": "timmfin",
"email": "timmfin@timmfin.net"
},
{
"name": "zackbloom",
"email": "zackbloom@gmail.com"
}
],
"name": "tether",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/HubSpot/tether.git"
},
"scripts": {
"build": "gulp build",
"reinstall": "del node_modules && npm install",
"watch": "gulp watch"
},
"version": "1.4.0"
}
+192
View File
@@ -0,0 +1,192 @@
=tether-theme-arrows($themePrefix: "tether", $themeName: "arrows", $arrowSize: 16px, $arrowPointerEvents: null, $backgroundColor: #fff, $color: inherit, $useDropShadow: false)
.#{ $themePrefix }-element.#{ $themePrefix }-theme-#{ $themeName }
max-width: 100%
max-height: 100%
.#{ $themePrefix }-content
border-radius: 5px
position: relative
font-family: inherit
background: $backgroundColor
color: $color
padding: 1em
font-size: 1.1em
line-height: 1.5em
@if $useDropShadow
transform: translateZ(0)
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, .2))
&:before
content: ""
display: block
position: absolute
width: 0
height: 0
border-color: transparent
border-width: $arrowSize
border-style: solid
pointer-events: $arrowPointerEvents
// Centers and middles
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-center .#{ $themePrefix }-content
margin-bottom: $arrowSize
&:before
top: 100%
left: 50%
margin-left: - $arrowSize
border-top-color: $backgroundColor
border-bottom: 0
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-center .#{ $themePrefix }-content
margin-top: $arrowSize
&:before
bottom: 100%
left: 50%
margin-left: - $arrowSize
border-bottom-color: $backgroundColor
border-top: 0
&.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-element-attached-middle .#{ $themePrefix }-content
margin-right: $arrowSize
&:before
left: 100%
top: 50%
margin-top: - $arrowSize
border-left-color: $backgroundColor
border-right: 0
&.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-element-attached-middle .#{ $themePrefix }-content
margin-left: $arrowSize
&:before
right: 100%
top: 50%
margin-top: - $arrowSize
border-right-color: $backgroundColor
border-left: 0
// Target middle/center, element corner
&.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-center .#{ $themePrefix }-content
left: - $arrowSize * 2
&.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-center .#{ $themePrefix }-content
left: $arrowSize * 2
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-middle .#{ $themePrefix }-content
margin-top: $arrowSize
&:before
bottom: 100%
left: $arrowSize
border-bottom-color: $backgroundColor
border-top: 0
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-middle .#{ $themePrefix }-content
margin-top: $arrowSize
&:before
bottom: 100%
right: $arrowSize
border-bottom-color: $backgroundColor
border-top: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-middle .#{ $themePrefix }-content
margin-bottom: $arrowSize
&:before
top: 100%
left: $arrowSize
border-top-color: $backgroundColor
border-bottom: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-middle .#{ $themePrefix }-content
margin-bottom: $arrowSize
&:before
top: 100%
right: $arrowSize
border-top-color: $backgroundColor
border-bottom: 0
// Top and bottom corners
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-bottom .#{ $themePrefix }-content
margin-top: $arrowSize
&:before
bottom: 100%
left: $arrowSize
border-bottom-color: $backgroundColor
border-top: 0
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-bottom .#{ $themePrefix }-content
margin-top: $arrowSize
&:before
bottom: 100%
right: $arrowSize
border-bottom-color: $backgroundColor
border-top: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-top .#{ $themePrefix }-content
margin-bottom: $arrowSize
&:before
top: 100%
left: $arrowSize
border-top-color: $backgroundColor
border-bottom: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-top .#{ $themePrefix }-content
margin-bottom: $arrowSize
&:before
top: 100%
right: $arrowSize
border-top-color: $backgroundColor
border-bottom: 0
// Side corners
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-left .#{ $themePrefix }-content
margin-right: $arrowSize
&:before
top: $arrowSize
left: 100%
border-left-color: $backgroundColor
border-right: 0
&.#{ $themePrefix }-element-attached-top.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-right .#{ $themePrefix }-content
margin-left: $arrowSize
&:before
top: $arrowSize
right: 100%
border-right-color: $backgroundColor
border-left: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-right.#{ $themePrefix }-target-attached-left .#{ $themePrefix }-content
margin-right: $arrowSize
&:before
bottom: $arrowSize
left: 100%
border-left-color: $backgroundColor
border-right: 0
&.#{ $themePrefix }-element-attached-bottom.#{ $themePrefix }-element-attached-left.#{ $themePrefix }-target-attached-right .#{ $themePrefix }-content
margin-left: $arrowSize
&:before
bottom: $arrowSize
right: 100%
border-right-color: $backgroundColor
border-left: 0
+14
View File
@@ -0,0 +1,14 @@
=tether-theme-basic($themePrefix: "tether", $themeName: "basic", $backgroundColor: #fff, $color: inherit)
.#{ $themePrefix }-element.#{ $themePrefix }-theme-#{ $themeName }
max-width: 100%
max-height: 100%
.#{ $themePrefix }-content
border-radius: 5px
box-shadow: 0 2px 8px rgba(0, 0, 0, .2)
font-family: inherit
background: $backgroundColor
color: $color
padding: 1em
font-size: 1.1em
line-height: 1.5em
+12
View File
@@ -0,0 +1,12 @@
=tether($themePrefix: "tether")
.#{ $themePrefix }-element, .#{ $themePrefix }-element *
&, &:after, &:before
box-sizing: border-box
.#{ $themePrefix }-element
position: absolute
display: none
&.#{ $themePrefix }-open
display: block
+6
View File
@@ -0,0 +1,6 @@
@mixin inline-block
display: inline-block
vertical-align: middle
*vertical-align: auto
*zoom: 1
*display: inline
+7
View File
@@ -0,0 +1,7 @@
@mixin pie-clearfix
*zoom: 1
&:after
content: ""
display: table
clear: both
+12
View File
@@ -0,0 +1,12 @@
@import helpers/tether
@import helpers/tether-theme-arrows
$themePrefix: "tether"
$themeName: "arrows-dark"
$arrowSize: 16px
$backgroundColor: #000
$color: #fff
$useDropShadow: false
+tether($themePrefix: $themePrefix)
+tether-theme-arrows($themePrefix: $themePrefix, $themeName: $themeName, $arrowSize: $arrowSize, $backgroundColor: $backgroundColor, $color: $color, $useDropShadow: $useDropShadow)
+12
View File
@@ -0,0 +1,12 @@
@import helpers/tether
@import helpers/tether-theme-arrows
$themePrefix: "tether"
$themeName: "arrows"
$arrowSize: 16px
$backgroundColor: #fff
$color: inherit
$useDropShadow: true
+tether($themePrefix: $themePrefix)
+tether-theme-arrows($themePrefix: $themePrefix, $themeName: $themeName, $arrowSize: $arrowSize, $backgroundColor: $backgroundColor, $color: $color, $useDropShadow: $useDropShadow)
+10
View File
@@ -0,0 +1,10 @@
@import helpers/tether
@import helpers/tether-theme-basic
$themePrefix: "tether"
$themeName: "basic"
$backgroundColor: #fff
$color: inherit
+tether($themePrefix: $themePrefix)
+tether-theme-basic($themePrefix: $themePrefix, $themeName: $themeName, $backgroundColor: $backgroundColor, $color: $color)
+5
View File
@@ -0,0 +1,5 @@
@import helpers/tether
$themePrefix: "tether"
+tether($themePrefix: $themePrefix)
+61
View File
@@ -0,0 +1,61 @@
/* globals TetherBase */
const {getBounds, updateClasses, defer} = TetherBase.Utils;
TetherBase.modules.push({
position({top, left}) {
const {height, width} = this.cache('element-bounds', () => {
return getBounds(this.element);
});
const targetPos = this.getTargetBounds();
const bottom = top + height;
const right = left + width;
const abutted = [];
if (top <= targetPos.bottom && bottom >= targetPos.top) {
['left', 'right'].forEach(side => {
const targetPosSide = targetPos[side];
if (targetPosSide === left || targetPosSide === right) {
abutted.push(side);
}
});
}
if (left <= targetPos.right && right >= targetPos.left) {
['top', 'bottom'].forEach(side => {
const targetPosSide = targetPos[side];
if (targetPosSide === top || targetPosSide === bottom) {
abutted.push(side);
}
});
}
const allClasses = [];
const addClasses = [];
const sides = ['left', 'top', 'right', 'bottom'];
allClasses.push(this.getClass('abutted'));
sides.forEach(side => {
allClasses.push(`${ this.getClass('abutted') }-${ side }`);
});
if (abutted.length) {
addClasses.push(this.getClass('abutted'));
}
abutted.forEach(side => {
addClasses.push(`${ this.getClass('abutted') }-${ side }`);
});
defer(() => {
if (!(this.options.addTargetClasses === false)) {
updateClasses(this.target, addClasses, allClasses);
}
updateClasses(this.element, addClasses, allClasses);
});
return true;
}
});
+366
View File
@@ -0,0 +1,366 @@
/* globals TetherBase */
const {
getBounds,
extend,
updateClasses,
defer
} = TetherBase.Utils;
const BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom'];
function getBoundingRect(tether, to) {
if (to === 'scrollParent') {
to = tether.scrollParents[0];
} else if (to === 'window') {
to = [pageXOffset, pageYOffset, innerWidth + pageXOffset, innerHeight + pageYOffset];
}
if (to === document) {
to = to.documentElement;
}
if (typeof to.nodeType !== 'undefined') {
const node = to;
const size = getBounds(to);
const pos = size;
const style = getComputedStyle(to);
to = [pos.left, pos.top, size.width + pos.left, size.height + pos.top];
// Account any parent Frames scroll offset
if (node.ownerDocument !== document) {
let win = node.ownerDocument.defaultView;
to[0] += win.pageXOffset;
to[1] += win.pageYOffset;
to[2] += win.pageXOffset;
to[3] += win.pageYOffset;
}
BOUNDS_FORMAT.forEach((side, i) => {
side = side[0].toUpperCase() + side.substr(1);
if (side === 'Top' || side === 'Left') {
to[i] += parseFloat(style[`border${ side }Width`]);
} else {
to[i] -= parseFloat(style[`border${ side }Width`]);
}
});
}
return to;
}
TetherBase.modules.push({
position({top, left, targetAttachment}) {
if (!this.options.constraints) {
return true;
}
let {height, width} = this.cache('element-bounds', () => {
return getBounds(this.element);
});
if (width === 0 && height === 0 && typeof this.lastSize !== 'undefined') {
// Handle the item getting hidden as a result of our positioning without glitching
// the classes in and out
({width, height} = this.lastSize);
}
const targetSize = this.cache('target-bounds', () => {
return this.getTargetBounds();
});
const {height: targetHeight, width: targetWidth} = targetSize;
const allClasses = [this.getClass('pinned'), this.getClass('out-of-bounds')];
this.options.constraints.forEach(constraint => {
const {outOfBoundsClass, pinnedClass} = constraint;
if (outOfBoundsClass) {
allClasses.push(outOfBoundsClass);
}
if (pinnedClass) {
allClasses.push(pinnedClass);
}
});
allClasses.forEach(cls => {
['left', 'top', 'right', 'bottom'].forEach(side => {
allClasses.push(`${ cls }-${ side }`);
});
});
const addClasses = [];
const tAttachment = extend({}, targetAttachment);
const eAttachment = extend({}, this.attachment);
this.options.constraints.forEach(constraint => {
let {to, attachment, pin} = constraint;
if (typeof attachment === 'undefined') {
attachment = '';
}
let changeAttachX, changeAttachY;
if (attachment.indexOf(' ') >= 0) {
[changeAttachY, changeAttachX] = attachment.split(' ');
} else {
changeAttachX = changeAttachY = attachment;
}
const bounds = getBoundingRect(this, to);
if (changeAttachY === 'target' || changeAttachY === 'both') {
if (top < bounds[1] && tAttachment.top === 'top') {
top += targetHeight;
tAttachment.top = 'bottom';
}
if (top + height > bounds[3] && tAttachment.top === 'bottom') {
top -= targetHeight;
tAttachment.top = 'top';
}
}
if (changeAttachY === 'together') {
if (tAttachment.top === 'top') {
if (eAttachment.top === 'bottom' && top < bounds[1]) {
top += targetHeight;
tAttachment.top = 'bottom';
top += height;
eAttachment.top = 'top';
} else if (eAttachment.top === 'top' && top + height > bounds[3] && top - (height - targetHeight) >= bounds[1]) {
top -= height - targetHeight;
tAttachment.top = 'bottom';
eAttachment.top = 'bottom';
}
}
if (tAttachment.top === 'bottom') {
if (eAttachment.top === 'top' && top + height > bounds[3]) {
top -= targetHeight;
tAttachment.top = 'top';
top -= height;
eAttachment.top = 'bottom';
} else if (eAttachment.top === 'bottom'&& top < bounds[1] && top + (height*2 - targetHeight) <= bounds[3]) {
top += height - targetHeight;
tAttachment.top = 'top';
eAttachment.top = 'top';
}
}
if (tAttachment.top === 'middle') {
if (top + height > bounds[3] && eAttachment.top === 'top') {
top -= height;
eAttachment.top = 'bottom';
} else if (top < bounds[1] && eAttachment.top === 'bottom') {
top += height;
eAttachment.top = 'top';
}
}
}
if (changeAttachX === 'target' || changeAttachX === 'both') {
if (left < bounds[0] && tAttachment.left === 'left') {
left += targetWidth;
tAttachment.left = 'right';
}
if (left + width > bounds[2] && tAttachment.left === 'right') {
left -= targetWidth;
tAttachment.left = 'left';
}
}
if (changeAttachX === 'together') {
if (left < bounds[0] && tAttachment.left === 'left') {
if (eAttachment.left === 'right') {
left += targetWidth;
tAttachment.left = 'right';
left += width;
eAttachment.left = 'left';
} else if (eAttachment.left === 'left') {
left += targetWidth;
tAttachment.left = 'right';
left -= width;
eAttachment.left = 'right';
}
} else if (left + width > bounds[2] && tAttachment.left === 'right') {
if (eAttachment.left === 'left') {
left -= targetWidth;
tAttachment.left = 'left';
left -= width;
eAttachment.left = 'right';
} else if (eAttachment.left === 'right') {
left -= targetWidth;
tAttachment.left = 'left';
left += width;
eAttachment.left = 'left';
}
} else if (tAttachment.left === 'center') {
if (left + width > bounds[2] && eAttachment.left === 'left') {
left -= width;
eAttachment.left = 'right';
} else if (left < bounds[0] && eAttachment.left === 'right') {
left += width;
eAttachment.left = 'left';
}
}
}
if (changeAttachY === 'element' || changeAttachY === 'both') {
if (top < bounds[1] && eAttachment.top === 'bottom') {
top += height;
eAttachment.top = 'top';
}
if (top + height > bounds[3] && eAttachment.top === 'top') {
top -= height;
eAttachment.top = 'bottom';
}
}
if (changeAttachX === 'element' || changeAttachX === 'both') {
if (left < bounds[0]) {
if (eAttachment.left === 'right') {
left += width;
eAttachment.left = 'left';
} else if (eAttachment.left === 'center') {
left += (width / 2);
eAttachment.left = 'left';
}
}
if (left + width > bounds[2]) {
if (eAttachment.left === 'left') {
left -= width;
eAttachment.left = 'right';
} else if (eAttachment.left === 'center') {
left -= (width / 2);
eAttachment.left = 'right';
}
}
}
if (typeof pin === 'string') {
pin = pin.split(',').map(p => p.trim());
} else if (pin === true) {
pin = ['top', 'left', 'right', 'bottom'];
}
pin = pin || [];
const pinned = [];
const oob = [];
if (top < bounds[1]) {
if (pin.indexOf('top') >= 0) {
top = bounds[1];
pinned.push('top');
} else {
oob.push('top');
}
}
if (top + height > bounds[3]) {
if (pin.indexOf('bottom') >= 0) {
top = bounds[3] - height;
pinned.push('bottom');
} else {
oob.push('bottom');
}
}
if (left < bounds[0]) {
if (pin.indexOf('left') >= 0) {
left = bounds[0];
pinned.push('left');
} else {
oob.push('left');
}
}
if (left + width > bounds[2]) {
if (pin.indexOf('right') >= 0) {
left = bounds[2] - width;
pinned.push('right');
} else {
oob.push('right');
}
}
if (pinned.length) {
let pinnedClass;
if (typeof this.options.pinnedClass !== 'undefined') {
pinnedClass = this.options.pinnedClass;
} else {
pinnedClass = this.getClass('pinned');
}
addClasses.push(pinnedClass);
pinned.forEach(side => {
addClasses.push(`${ pinnedClass }-${ side }`);
});
}
if (oob.length) {
let oobClass;
if (typeof this.options.outOfBoundsClass !== 'undefined') {
oobClass = this.options.outOfBoundsClass;
} else {
oobClass = this.getClass('out-of-bounds');
}
addClasses.push(oobClass);
oob.forEach(side => {
addClasses.push(`${ oobClass }-${ side }`);
});
}
if (pinned.indexOf('left') >= 0 || pinned.indexOf('right') >= 0) {
eAttachment.left = tAttachment.left = false;
}
if (pinned.indexOf('top') >= 0 || pinned.indexOf('bottom') >= 0) {
eAttachment.top = tAttachment.top = false;
}
if (tAttachment.top !== targetAttachment.top ||
tAttachment.left !== targetAttachment.left ||
eAttachment.top !== this.attachment.top ||
eAttachment.left !== this.attachment.left) {
this.updateAttachClasses(eAttachment, tAttachment);
this.trigger('update', {
attachment: eAttachment,
targetAttachment: tAttachment,
});
}
});
defer(() => {
if (!(this.options.addTargetClasses === false)) {
updateClasses(this.target, addClasses, allClasses);
}
updateClasses(this.element, addClasses, allClasses);
});
return {top, left};
}
});
+46
View File
@@ -0,0 +1,46 @@
/* globals Tether */
Tether.modules.push({
initialize() {
this.markers = {};
['target', 'element'].forEach(type => {
const el = document.createElement('div');
el.className = this.getClass(`${ type }-marker`);
const dot = document.createElement('div');
dot.className = this.getClass('marker-dot');
el.appendChild(dot);
this[type].appendChild(el);
this.markers[type] = {dot, el};
});
},
position({manualOffset, manualTargetOffset}) {
const offsets = {
element: manualOffset,
target: manualTargetOffset
};
for (let type in offsets) {
const offset = offsets[type];
for (let side in offset) {
let val = offset[side];
const notString = typeof val !== 'string';
if (notString ||
val.indexOf('%') === -1 &&
val.indexOf('px') === -1) {
val += 'px';
}
if (this.markers[type].dot.style[side] !== val) {
this.markers[type].dot.style[side] = val;
}
}
}
return true;
}
});
+32
View File
@@ -0,0 +1,32 @@
/* globals TetherBase */
TetherBase.modules.push({
position({top, left}) {
if (!this.options.shift) {
return;
}
let shift = this.options.shift;
if (typeof this.options.shift === 'function') {
shift = this.options.shift.call(this, {top, left});
}
let shiftTop, shiftLeft;
if (typeof shift === 'string') {
shift = shift.split(' ');
shift[1] = shift[1] || shift[0];
([shiftTop, shiftLeft] = shift);
shiftTop = parseFloat(shiftTop, 10);
shiftLeft = parseFloat(shiftLeft, 10);
} else {
([shiftTop, shiftLeft] = [shift.top, shift.left]);
}
top += shiftTop;
left += shiftLeft;
return {top, left};
}
});
+809
View File
@@ -0,0 +1,809 @@
/* globals TetherBase, performance */
if (typeof TetherBase === 'undefined') {
throw new Error('You must include the utils.js file before tether.js');
}
const {
getScrollParents,
getBounds,
getOffsetParent,
extend,
addClass,
removeClass,
updateClasses,
defer,
flush,
getScrollBarSize,
removeUtilElements
} = TetherBase.Utils;
function within(a, b, diff=1) {
return (a + diff >= b && b >= a - diff);
}
const transformKey = (() => {
if(typeof document === 'undefined') {
return '';
}
const el = document.createElement('div');
const transforms = ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform'];
for (let i = 0; i < transforms.length; ++i) {
const key = transforms[i];
if (el.style[key] !== undefined) {
return key;
}
}
})();
const tethers = [];
const position = () => {
tethers.forEach(tether => {
tether.position(false);
});
flush();
};
function now() {
if (typeof performance !== 'undefined' && typeof performance.now !== 'undefined') {
return performance.now();
}
return +new Date;
}
(() => {
let lastCall = null;
let lastDuration = null;
let pendingTimeout = null;
const tick = () => {
if (typeof lastDuration !== 'undefined' && lastDuration > 16) {
// We voluntarily throttle ourselves if we can't manage 60fps
lastDuration = Math.min(lastDuration - 16, 250);
// Just in case this is the last event, remember to position just once more
pendingTimeout = setTimeout(tick, 250);
return;
}
if (typeof lastCall !== 'undefined' && (now() - lastCall) < 10) {
// Some browsers call events a little too frequently, refuse to run more than is reasonable
return;
}
if (pendingTimeout != null) {
clearTimeout(pendingTimeout);
pendingTimeout = null;
}
lastCall = now();
position();
lastDuration = now() - lastCall;
};
if(typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
['resize', 'scroll', 'touchmove'].forEach(event => {
window.addEventListener(event, tick);
});
}
})();
const MIRROR_LR = {
center: 'center',
left: 'right',
right: 'left'
};
const MIRROR_TB = {
middle: 'middle',
top: 'bottom',
bottom: 'top'
};
const OFFSET_MAP = {
top: 0,
left: 0,
middle: '50%',
center: '50%',
bottom: '100%',
right: '100%'
};
const autoToFixedAttachment = (attachment, relativeToAttachment) => {
let {left, top} = attachment;
if (left === 'auto') {
left = MIRROR_LR[relativeToAttachment.left];
}
if (top === 'auto') {
top = MIRROR_TB[relativeToAttachment.top];
}
return {left, top};
};
const attachmentToOffset = (attachment) => {
let left = attachment.left;
let top = attachment.top;
if (typeof OFFSET_MAP[attachment.left] !== 'undefined') {
left = OFFSET_MAP[attachment.left];
}
if (typeof OFFSET_MAP[attachment.top] !== 'undefined') {
top = OFFSET_MAP[attachment.top];
}
return {left, top};
};
function addOffset(...offsets) {
const out = {top: 0, left: 0};
offsets.forEach(({top, left}) => {
if (typeof top === 'string') {
top = parseFloat(top, 10);
}
if (typeof left === 'string') {
left = parseFloat(left, 10);
}
out.top += top;
out.left += left;
});
return out;
}
function offsetToPx(offset, size) {
if (typeof offset.left === 'string' && offset.left.indexOf('%') !== -1) {
offset.left = parseFloat(offset.left, 10) / 100 * size.width;
}
if (typeof offset.top === 'string' && offset.top.indexOf('%') !== -1) {
offset.top = parseFloat(offset.top, 10) / 100 * size.height;
}
return offset;
}
const parseOffset = (value) => {
const [top, left] = value.split(' ');
return {top, left};
};
const parseAttachment = parseOffset;
class TetherClass extends Evented {
constructor(options) {
super();
this.position = this.position.bind(this);
tethers.push(this);
this.history = [];
this.setOptions(options, false);
TetherBase.modules.forEach(module => {
if (typeof module.initialize !== 'undefined') {
module.initialize.call(this);
}
});
this.position();
}
getClass(key='') {
const {classes} = this.options;
if (typeof classes !== 'undefined' && classes[key]) {
return this.options.classes[key];
} else if (this.options.classPrefix) {
return `${ this.options.classPrefix }-${ key }`;
} else {
return key;
}
}
setOptions(options, pos=true) {
const defaults = {
offset: '0 0',
targetOffset: '0 0',
targetAttachment: 'auto auto',
classPrefix: 'tether'
};
this.options = extend(defaults, options);
let {element, target, targetModifier} = this.options;
this.element = element;
this.target = target;
this.targetModifier = targetModifier;
if (this.target === 'viewport') {
this.target = document.body;
this.targetModifier = 'visible';
} else if (this.target === 'scroll-handle') {
this.target = document.body;
this.targetModifier = 'scroll-handle';
}
['element', 'target'].forEach(key => {
if (typeof this[key] === 'undefined') {
throw new Error('Tether Error: Both element and target must be defined');
}
if (typeof this[key].jquery !== 'undefined') {
this[key] = this[key][0];
} else if (typeof this[key] === 'string') {
this[key] = document.querySelector(this[key]);
}
});
addClass(this.element, this.getClass('element'));
if (!(this.options.addTargetClasses === false)) {
addClass(this.target, this.getClass('target'));
}
if (!this.options.attachment) {
throw new Error('Tether Error: You must provide an attachment');
}
this.targetAttachment = parseAttachment(this.options.targetAttachment);
this.attachment = parseAttachment(this.options.attachment);
this.offset = parseOffset(this.options.offset);
this.targetOffset = parseOffset(this.options.targetOffset);
if (typeof this.scrollParents !== 'undefined') {
this.disable();
}
if (this.targetModifier === 'scroll-handle') {
this.scrollParents = [this.target];
} else {
this.scrollParents = getScrollParents(this.target);
}
if(!(this.options.enabled === false)) {
this.enable(pos);
}
}
getTargetBounds() {
if (typeof this.targetModifier !== 'undefined') {
if (this.targetModifier === 'visible') {
if (this.target === document.body) {
return {top: pageYOffset, left: pageXOffset, height: innerHeight, width: innerWidth};
} else {
const bounds = getBounds(this.target);
const out = {
height: bounds.height,
width: bounds.width,
top: bounds.top,
left: bounds.left
};
out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top));
out.height = Math.min(out.height, bounds.height - ((bounds.top + bounds.height) - (pageYOffset + innerHeight)));
out.height = Math.min(innerHeight, out.height);
out.height -= 2;
out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left));
out.width = Math.min(out.width, bounds.width - ((bounds.left + bounds.width) - (pageXOffset + innerWidth)));
out.width = Math.min(innerWidth, out.width);
out.width -= 2;
if (out.top < pageYOffset) {
out.top = pageYOffset;
}
if (out.left < pageXOffset) {
out.left = pageXOffset;
}
return out;
}
} else if (this.targetModifier === 'scroll-handle') {
let bounds;
let target = this.target;
if (target === document.body) {
target = document.documentElement;
bounds = {
left: pageXOffset,
top: pageYOffset,
height: innerHeight,
width: innerWidth
};
} else {
bounds = getBounds(target);
}
const style = getComputedStyle(target);
const hasBottomScroll = (
target.scrollWidth > target.clientWidth ||
[style.overflow, style.overflowX].indexOf('scroll') >= 0 ||
this.target !== document.body
);
let scrollBottom = 0;
if (hasBottomScroll) {
scrollBottom = 15;
}
const height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom;
const out = {
width: 15,
height: height * 0.975 * (height / target.scrollHeight),
left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15
};
let fitAdj = 0;
if (height < 408 && this.target === document.body) {
fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58;
}
if (this.target !== document.body) {
out.height = Math.max(out.height, 24);
}
const scrollPercentage = this.target.scrollTop / (target.scrollHeight - height);
out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth);
if (this.target === document.body) {
out.height = Math.max(out.height, 24);
}
return out;
}
} else {
return getBounds(this.target);
}
}
clearCache() {
this._cache = {};
}
cache(k, getter) {
// More than one module will often need the same DOM info, so
// we keep a cache which is cleared on each position call
if (typeof this._cache === 'undefined') {
this._cache = {};
}
if (typeof this._cache[k] === 'undefined') {
this._cache[k] = getter.call(this);
}
return this._cache[k];
}
enable(pos=true) {
if (!(this.options.addTargetClasses === false)) {
addClass(this.target, this.getClass('enabled'));
}
addClass(this.element, this.getClass('enabled'));
this.enabled = true;
this.scrollParents.forEach((parent) => {
if (parent !== this.target.ownerDocument) {
parent.addEventListener('scroll', this.position);
}
})
if (pos) {
this.position();
}
}
disable() {
removeClass(this.target, this.getClass('enabled'));
removeClass(this.element, this.getClass('enabled'));
this.enabled = false;
if (typeof this.scrollParents !== 'undefined') {
this.scrollParents.forEach((parent) => {
parent.removeEventListener('scroll', this.position);
})
}
}
destroy() {
this.disable();
tethers.forEach((tether, i) => {
if (tether === this) {
tethers.splice(i, 1);
}
});
// Remove any elements we were using for convenience from the DOM
if (tethers.length === 0) {
removeUtilElements();
}
}
updateAttachClasses(elementAttach, targetAttach) {
elementAttach = elementAttach || this.attachment;
targetAttach = targetAttach || this.targetAttachment;
const sides = ['left', 'top', 'bottom', 'right', 'middle', 'center'];
if (typeof this._addAttachClasses !== 'undefined' && this._addAttachClasses.length) {
// updateAttachClasses can be called more than once in a position call, so
// we need to clean up after ourselves such that when the last defer gets
// ran it doesn't add any extra classes from previous calls.
this._addAttachClasses.splice(0, this._addAttachClasses.length);
}
if (typeof this._addAttachClasses === 'undefined') {
this._addAttachClasses = [];
}
const add = this._addAttachClasses;
if (elementAttach.top) {
add.push(`${ this.getClass('element-attached') }-${ elementAttach.top }`);
}
if (elementAttach.left) {
add.push(`${ this.getClass('element-attached') }-${ elementAttach.left }`);
}
if (targetAttach.top) {
add.push(`${ this.getClass('target-attached') }-${ targetAttach.top }`);
}
if (targetAttach.left) {
add.push(`${ this.getClass('target-attached') }-${ targetAttach.left }`);
}
const all = [];
sides.forEach(side => {
all.push(`${ this.getClass('element-attached') }-${ side }`);
all.push(`${ this.getClass('target-attached') }-${ side }`);
});
defer(() => {
if (!(typeof this._addAttachClasses !== 'undefined')) {
return;
}
updateClasses(this.element, this._addAttachClasses, all);
if (!(this.options.addTargetClasses === false)) {
updateClasses(this.target, this._addAttachClasses, all);
}
delete this._addAttachClasses;
});
}
position(flushChanges=true) {
// flushChanges commits the changes immediately, leave true unless you are positioning multiple
// tethers (in which case call Tether.Utils.flush yourself when you're done)
if (!this.enabled) {
return;
}
this.clearCache();
// Turn 'auto' attachments into the appropriate corner or edge
const targetAttachment = autoToFixedAttachment(this.targetAttachment, this.attachment);
this.updateAttachClasses(this.attachment, targetAttachment);
const elementPos = this.cache('element-bounds', () => {
return getBounds(this.element);
});
let {width, height} = elementPos;
if (width === 0 && height === 0 && typeof this.lastSize !== 'undefined') {
// We cache the height and width to make it possible to position elements that are
// getting hidden.
({width, height} = this.lastSize);
} else {
this.lastSize = {width, height};
}
const targetPos = this.cache('target-bounds', () => {
return this.getTargetBounds();
});
const targetSize = targetPos;
// Get an actual px offset from the attachment
let offset = offsetToPx(attachmentToOffset(this.attachment), {width, height});
let targetOffset = offsetToPx(attachmentToOffset(targetAttachment), targetSize);
const manualOffset = offsetToPx(this.offset, {width, height});
const manualTargetOffset = offsetToPx(this.targetOffset, targetSize);
// Add the manually provided offset
offset = addOffset(offset, manualOffset);
targetOffset = addOffset(targetOffset, manualTargetOffset);
// It's now our goal to make (element position + offset) == (target position + target offset)
let left = targetPos.left + targetOffset.left - offset.left;
let top = targetPos.top + targetOffset.top - offset.top;
for (let i = 0; i < TetherBase.modules.length; ++i) {
const module = TetherBase.modules[i];
const ret = module.position.call(this, {
left,
top,
targetAttachment,
targetPos,
elementPos,
offset,
targetOffset,
manualOffset,
manualTargetOffset,
scrollbarSize,
attachment: this.attachment
});
if (ret === false) {
return false;
} else if (typeof ret === 'undefined' || typeof ret !== 'object') {
continue;
} else {
({top, left} = ret);
}
}
// We describe the position three different ways to give the optimizer
// a chance to decide the best possible way to position the element
// with the fewest repaints.
const next = {
// It's position relative to the page (absolute positioning when
// the element is a child of the body)
page: {
top: top,
left: left
},
// It's position relative to the viewport (fixed positioning)
viewport: {
top: top - pageYOffset,
bottom: pageYOffset - top - height + innerHeight,
left: left - pageXOffset,
right: pageXOffset - left - width + innerWidth
}
};
var doc = this.target.ownerDocument;
var win = doc.defaultView;
let scrollbarSize;
if (win.innerHeight > doc.documentElement.clientHeight) {
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
next.viewport.bottom -= scrollbarSize.height;
}
if (win.innerWidth > doc.documentElement.clientWidth) {
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
next.viewport.right -= scrollbarSize.width;
}
if (['', 'static'].indexOf(doc.body.style.position) === -1 ||
['', 'static'].indexOf(doc.body.parentElement.style.position) === -1) {
// Absolute positioning in the body will be relative to the page, not the 'initial containing block'
next.page.bottom = doc.body.scrollHeight - top - height;
next.page.right = doc.body.scrollWidth - left - width;
}
if (typeof this.options.optimizations !== 'undefined' &&
this.options.optimizations.moveElement !== false &&
!(typeof this.targetModifier !== 'undefined')) {
const offsetParent = this.cache('target-offsetparent', () => getOffsetParent(this.target));
const offsetPosition = this.cache('target-offsetparent-bounds', () => getBounds(offsetParent));
const offsetParentStyle = getComputedStyle(offsetParent);
const offsetParentSize = offsetPosition;
const offsetBorder = {};
['Top', 'Left', 'Bottom', 'Right'].forEach(side => {
offsetBorder[side.toLowerCase()] = parseFloat(offsetParentStyle[`border${ side }Width`]);
});
offsetPosition.right = doc.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right;
offsetPosition.bottom = doc.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom;
if (next.page.top >= (offsetPosition.top + offsetBorder.top) && next.page.bottom >= offsetPosition.bottom) {
if (next.page.left >= (offsetPosition.left + offsetBorder.left) && next.page.right >= offsetPosition.right) {
// We're within the visible part of the target's scroll parent
const scrollTop = offsetParent.scrollTop;
const scrollLeft = offsetParent.scrollLeft;
// It's position relative to the target's offset parent (absolute positioning when
// the element is moved to be a child of the target's offset parent).
next.offset = {
top: next.page.top - offsetPosition.top + scrollTop - offsetBorder.top,
left: next.page.left - offsetPosition.left + scrollLeft - offsetBorder.left
};
}
}
}
// We could also travel up the DOM and try each containing context, rather than only
// looking at the body, but we're gonna get diminishing returns.
this.move(next);
this.history.unshift(next);
if (this.history.length > 3) {
this.history.pop();
}
if (flushChanges) {
flush();
}
return true;
}
// THE ISSUE
move(pos) {
if (!(typeof this.element.parentNode !== 'undefined')) {
return;
}
const same = {};
for (let type in pos) {
same[type] = {};
for (let key in pos[type]) {
let found = false;
for (let i = 0; i < this.history.length; ++i) {
const point = this.history[i];
if (typeof point[type] !== 'undefined' &&
!within(point[type][key], pos[type][key])) {
found = true;
break;
}
}
if (!found) {
same[type][key] = true;
}
}
}
let css = {top: '', left: '', right: '', bottom: ''};
const transcribe = (_same, _pos) => {
const hasOptimizations = typeof this.options.optimizations !== 'undefined';
const gpu = hasOptimizations ? this.options.optimizations.gpu : null;
if (gpu !== false) {
let yPos, xPos;
if (_same.top) {
css.top = 0;
yPos = _pos.top;
} else {
css.bottom = 0;
yPos = -_pos.bottom;
}
if (_same.left) {
css.left = 0;
xPos = _pos.left;
} else {
css.right = 0;
xPos = -_pos.right;
}
if (window.matchMedia) {
// HubSpot/tether#207
const retina = window.matchMedia('only screen and (min-resolution: 1.3dppx)').matches ||
window.matchMedia('only screen and (-webkit-min-device-pixel-ratio: 1.3)').matches;
if (!retina) {
xPos = Math.round(xPos);
yPos = Math.round(yPos);
}
}
css[transformKey] = `translateX(${ xPos }px) translateY(${ yPos }px)`;
if (transformKey !== 'msTransform') {
// The Z transform will keep this in the GPU (faster, and prevents artifacts),
// but IE9 doesn't support 3d transforms and will choke.
css[transformKey] += " translateZ(0)";
}
} else {
if (_same.top) {
css.top = `${ _pos.top }px`;
} else {
css.bottom = `${ _pos.bottom }px`;
}
if (_same.left) {
css.left = `${ _pos.left }px`;
} else {
css.right = `${ _pos.right }px`;
}
}
};
let moved = false;
if ((same.page.top || same.page.bottom) && (same.page.left || same.page.right)) {
css.position = 'absolute';
transcribe(same.page, pos.page);
} else if ((same.viewport.top || same.viewport.bottom) && (same.viewport.left || same.viewport.right)) {
css.position = 'fixed';
transcribe(same.viewport, pos.viewport);
} else if (typeof same.offset !== 'undefined' && same.offset.top && same.offset.left) {
css.position = 'absolute';
const offsetParent = this.cache('target-offsetparent', () => getOffsetParent(this.target));
if (getOffsetParent(this.element) !== offsetParent) {
defer(() => {
this.element.parentNode.removeChild(this.element);
offsetParent.appendChild(this.element);
});
}
transcribe(same.offset, pos.offset);
moved = true;
} else {
css.position = 'absolute';
transcribe({top: true, left: true}, pos.page);
}
if (!moved) {
if (this.options.bodyElement) {
this.options.bodyElement.appendChild(this.element);
} else {
let offsetParentIsBody = true;
let currentNode = this.element.parentNode;
while (currentNode && currentNode.nodeType === 1 && currentNode.tagName !== 'BODY') {
if (getComputedStyle(currentNode).position !== 'static') {
offsetParentIsBody = false;
break;
}
currentNode = currentNode.parentNode;
}
if (!offsetParentIsBody) {
this.element.parentNode.removeChild(this.element);
this.element.ownerDocument.body.appendChild(this.element);
}
}
}
// Any css change will trigger a repaint, so let's avoid one if nothing changed
const writeCSS = {};
let write = false;
for (let key in css) {
let val = css[key];
let elVal = this.element.style[key];
if (elVal !== val) {
write = true;
writeCSS[key] = val;
}
}
if (write) {
defer(() => {
extend(this.element.style, writeCSS);
this.trigger('repositioned');
});
}
}
}
TetherClass.modules = [];
TetherBase.position = position;
let Tether = extend(TetherClass, TetherBase);
+370
View File
@@ -0,0 +1,370 @@
let TetherBase;
if (typeof TetherBase === 'undefined') {
TetherBase = {modules: []};
}
let zeroElement = null;
// Same as native getBoundingClientRect, except it takes into account parent <frame> offsets
// if the element lies within a nested document (<frame> or <iframe>-like).
function getActualBoundingClientRect(node) {
let boundingRect = node.getBoundingClientRect();
// The original object returned by getBoundingClientRect is immutable, so we clone it
// We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
let rect = {};
for (var k in boundingRect) {
rect[k] = boundingRect[k];
}
if (node.ownerDocument !== document) {
let frameElement = node.ownerDocument.defaultView.frameElement;
if (frameElement) {
let frameRect = getActualBoundingClientRect(frameElement);
rect.top += frameRect.top;
rect.bottom += frameRect.top;
rect.left += frameRect.left;
rect.right += frameRect.left;
}
}
return rect;
}
function getScrollParents(el) {
// In firefox if the el is inside an iframe with display: none; window.getComputedStyle() will return null;
// https://bugzilla.mozilla.org/show_bug.cgi?id=548397
const computedStyle = getComputedStyle(el) || {};
const position = computedStyle.position;
let parents = [];
if (position === 'fixed') {
return [el];
}
let parent = el;
while ((parent = parent.parentNode) && parent && parent.nodeType === 1) {
let style;
try {
style = getComputedStyle(parent);
} catch (err) {}
if (typeof style === 'undefined' || style === null) {
parents.push(parent);
return parents;
}
const {overflow, overflowX, overflowY} = style;
if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
if (position !== 'absolute' || ['relative', 'absolute', 'fixed'].indexOf(style.position) >= 0) {
parents.push(parent)
}
}
}
parents.push(el.ownerDocument.body);
// If the node is within a frame, account for the parent window scroll
if (el.ownerDocument !== document) {
parents.push(el.ownerDocument.defaultView);
}
return parents;
}
const uniqueId = (() => {
let id = 0;
return () => ++id;
})();
const zeroPosCache = {};
const getOrigin = () => {
// getBoundingClientRect is unfortunately too accurate. It introduces a pixel or two of
// jitter as the user scrolls that messes with our ability to detect if two positions
// are equivilant or not. We place an element at the top left of the page that will
// get the same jitter, so we can cancel the two out.
let node = zeroElement;
if (!node || !document.body.contains(node)) {
node = document.createElement('div');
node.setAttribute('data-tether-id', uniqueId());
extend(node.style, {
top: 0,
left: 0,
position: 'absolute'
});
document.body.appendChild(node);
zeroElement = node;
}
const id = node.getAttribute('data-tether-id');
if (typeof zeroPosCache[id] === 'undefined') {
zeroPosCache[id] = getActualBoundingClientRect(node);
// Clear the cache when this position call is done
defer(() => {
delete zeroPosCache[id];
});
}
return zeroPosCache[id];
};
function removeUtilElements() {
if (zeroElement) {
document.body.removeChild(zeroElement);
}
zeroElement = null;
};
function getBounds(el) {
let doc;
if (el === document) {
doc = document;
el = document.documentElement;
} else {
doc = el.ownerDocument;
}
const docEl = doc.documentElement;
const box = getActualBoundingClientRect(el);
const origin = getOrigin();
box.top -= origin.top;
box.left -= origin.left;
if (typeof box.width === 'undefined') {
box.width = document.body.scrollWidth - box.left - box.right;
}
if (typeof box.height === 'undefined') {
box.height = document.body.scrollHeight - box.top - box.bottom;
}
box.top = box.top - docEl.clientTop;
box.left = box.left - docEl.clientLeft;
box.right = doc.body.clientWidth - box.width - box.left;
box.bottom = doc.body.clientHeight - box.height - box.top;
return box;
}
function getOffsetParent(el) {
return el.offsetParent || document.documentElement;
}
let _scrollBarSize = null;
function getScrollBarSize() {
if (_scrollBarSize) {
return _scrollBarSize;
}
const inner = document.createElement('div');
inner.style.width = '100%';
inner.style.height = '200px';
const outer = document.createElement('div');
extend(outer.style, {
position: 'absolute',
top: 0,
left: 0,
pointerEvents: 'none',
visibility: 'hidden',
width: '200px',
height: '150px',
overflow: 'hidden'
});
outer.appendChild(inner);
document.body.appendChild(outer);
const widthContained = inner.offsetWidth;
outer.style.overflow = 'scroll';
let widthScroll = inner.offsetWidth;
if (widthContained === widthScroll) {
widthScroll = outer.clientWidth;
}
document.body.removeChild(outer);
const width = widthContained - widthScroll;
_scrollBarSize = {width, height: width};
return _scrollBarSize;
}
function extend(out={}) {
const args = [];
Array.prototype.push.apply(args, arguments);
args.slice(1).forEach(obj => {
if (obj) {
for (let key in obj) {
if ({}.hasOwnProperty.call(obj, key)) {
out[key] = obj[key];
}
}
}
});
return out;
}
function removeClass(el, name) {
if (typeof el.classList !== 'undefined') {
name.split(' ').forEach(cls => {
if (cls.trim()) {
el.classList.remove(cls);
}
});
} else {
const regex = new RegExp(`(^| )${ name.split(' ').join('|') }( |$)`, 'gi');
const className = getClassName(el).replace(regex, ' ');
setClassName(el, className);
}
}
function addClass(el, name) {
if (typeof el.classList !== 'undefined') {
name.split(' ').forEach(cls => {
if (cls.trim()) {
el.classList.add(cls);
}
});
} else {
removeClass(el, name);
const cls = getClassName(el) + ` ${name}`;
setClassName(el, cls);
}
}
function hasClass(el, name) {
if (typeof el.classList !== 'undefined') {
return el.classList.contains(name);
}
const className = getClassName(el);
return new RegExp(`(^| )${ name }( |$)`, 'gi').test(className);
}
function getClassName(el) {
// Can't use just SVGAnimatedString here since nodes within a Frame in IE have
// completely separately SVGAnimatedString base classes
if (el.className instanceof el.ownerDocument.defaultView.SVGAnimatedString) {
return el.className.baseVal;
}
return el.className;
}
function setClassName(el, className) {
el.setAttribute('class', className);
}
function updateClasses(el, add, all) {
// Of the set of 'all' classes, we need the 'add' classes, and only the
// 'add' classes to be set.
all.forEach(cls => {
if (add.indexOf(cls) === -1 && hasClass(el, cls)) {
removeClass(el, cls);
}
});
add.forEach(cls => {
if (!hasClass(el, cls)) {
addClass(el, cls);
}
});
}
const deferred = [];
const defer = (fn) => {
deferred.push(fn);
};
const flush = () => {
let fn;
while(fn = deferred.pop()) {
fn();
}
};
class Evented {
on(event, handler, ctx, once=false) {
if (typeof this.bindings === 'undefined') {
this.bindings = {};
}
if (typeof this.bindings[event] === 'undefined') {
this.bindings[event] = [];
}
this.bindings[event].push({handler, ctx, once});
}
once(event, handler, ctx) {
this.on(event, handler, ctx, true);
}
off(event, handler) {
if (typeof this.bindings === 'undefined' ||
typeof this.bindings[event] === 'undefined') {
return;
}
if (typeof handler === 'undefined') {
delete this.bindings[event];
} else {
let i = 0;
while (i < this.bindings[event].length) {
if (this.bindings[event][i].handler === handler) {
this.bindings[event].splice(i, 1);
} else {
++i;
}
}
}
}
trigger(event, ...args) {
if (typeof this.bindings !== 'undefined' && this.bindings[event]) {
let i = 0;
while (i < this.bindings[event].length) {
const {handler, ctx, once} = this.bindings[event][i];
let context = ctx;
if (typeof context === 'undefined') {
context = this;
}
handler.apply(context, args);
if (once) {
this.bindings[event].splice(i, 1);
} else {
++i;
}
}
}
}
}
TetherBase.Utils = {
getActualBoundingClientRect,
getScrollParents,
getBounds,
getOffsetParent,
extend,
addClass,
removeClass,
hasClass,
updateClasses,
defer,
flush,
uniqueId,
Evented,
getScrollBarSize,
removeUtilElements
};