Full Code of shipshapecode/tether for AI

master eca87d51d040 cached
84 files
268.1 KB
71.1k tokens
68 symbols
1 requests
Download .txt
Showing preview only (288K chars total). Download the full file or copy to clipboard to get everything.
Repository: shipshapecode/tether
Branch: master
Commit: eca87d51d040
Files: 84
Total size: 268.1 KB

Directory structure:
gitextract_i2r94h7w/

├── .browserslistrc
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc.js
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── main.yml
│       ├── plan-release.yml
│       └── publish.yml
├── .gitignore
├── .hsdoc
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── .release-plan.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── HISTORY.md
├── LICENSE
├── README.md
├── RELEASE.md
├── __mocks__/
│   └── styleMock.js
├── babel.config.js
├── cypress.config.cjs
├── examples/
│   ├── common/
│   │   └── css/
│   │       └── style.css
│   ├── content-visible/
│   │   └── index.html
│   ├── dolls/
│   │   ├── dolls.css
│   │   ├── dolls.js
│   │   └── index.html
│   ├── element-scroll/
│   │   └── index.html
│   ├── enable-disable/
│   │   └── index.html
│   ├── index.html
│   ├── out-of-bounds/
│   │   └── index.html
│   ├── pin/
│   │   └── index.html
│   ├── resources/
│   │   └── css/
│   │       └── base.css
│   ├── scroll/
│   │   └── index.html
│   ├── simple/
│   │   └── index.html
│   ├── testbed/
│   │   └── index.html
│   └── viewport/
│       ├── colors.css
│       └── index.html
├── jest.config.js
├── package.json
├── rollup.config.js
├── src/
│   ├── css/
│   │   ├── helpers/
│   │   │   ├── _tether-theme-arrows.scss
│   │   │   ├── _tether-theme-basic.scss
│   │   │   └── _tether.scss
│   │   ├── mixins/
│   │   │   ├── _inline-block.scss
│   │   │   └── _pie-clearfix.scss
│   │   ├── tether-theme-arrows-dark.scss
│   │   ├── tether-theme-arrows.scss
│   │   ├── tether-theme-basic.scss
│   │   └── tether.scss
│   └── js/
│       ├── abutment.js
│       ├── constraint.js
│       ├── evented.js
│       ├── shift.js
│       ├── tether.js
│       └── utils/
│           ├── bounds.js
│           ├── classes.js
│           ├── deferred.js
│           ├── general.js
│           ├── offset.js
│           ├── parents.js
│           └── type-check.js
└── test/
    ├── .eslintrc.js
    ├── cypress/
    │   ├── integration/
    │   │   ├── content-visible.cy.js
    │   │   ├── enable-disable.cy.js
    │   │   ├── out-of-bounds.cy.js
    │   │   ├── pin.cy.js
    │   │   ├── scroll.cy.js
    │   │   ├── simple.cy.js
    │   │   └── testbed.cy.js
    │   ├── plugins/
    │   │   └── index.js
    │   └── support/
    │       ├── commands.js
    │       └── index.js
    └── unit/
        ├── constraint.spec.js
        ├── evented.spec.js
        ├── setupTests.js
        ├── shift.spec.js
        ├── tether.spec.js
        └── utils/
            ├── bounds.spec.js
            ├── classes.spec.js
            ├── deferred.spec.js
            ├── offset.spec.js
            └── parents.spec.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .browserslistrc
================================================
# Browsers that we support

last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
last 2 Edge versions
ie >= 10


================================================
FILE: .codeclimate.yml
================================================
version: "2"
checks:
  argument-count:
    config:
      threshold: 10
  method-count:
    config:
      threshold: 25
  method-lines:
    config:
      threshold: 30
exclude_patterns:
  - "demo/"
  - "dist/"
  - "docs/"
  - "examples/"
  - "jsdoc-template/"
  - "**/node_modules/"
  - "**/test/"
  - "**/tests/"
  - "**/vendor/"
  - "babel.config.js"


================================================
FILE: .eslintignore
================================================
/coverage/
/dist/
/docs/
/examples/


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module'
  },
  extends: [
    'eslint:recommended'
  ],
  env: {
    browser: true,
    es6: true
  },
  rules: {
    'complexity': ['warn', 6],
    'max-lines': ['warn', { max: 250, skipBlankLines: true, skipComments: true }],
    'no-console': 'off',
    'no-unused-vars': 'off',
    'prefer-const': 'off'
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        'babel.config.js',
        'jest.config.js',
        'rollup.config.js',
        '__mocks__/styleMock.js'
      ],
      parserOptions: {
        sourceType: 'module',
        ecmaVersion: 2020
      },
      env: {
        node: true
      }
    }
  ]
};


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: weekly
    time: "10:00"
  open-pull-requests-limit: 10
  labels:
  - dependencies
  versioning-strategy: increase


================================================
FILE: .github/workflows/main.yml
================================================

name: CI Build

on:
  pull_request: {}
  push:
    branches:
      - master
    tags:
      - v*

jobs:
  test:
    name: Tests
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: Install Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - name: Install dependencies
        run: pnpm i
      - name: Cache Cypress binary
        uses: actions/cache@v4
        with:
          path: ~/.cache/Cypress
          key: cypress-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
          restore-keys: |
            cypress-${{ runner.os }}-
      - name: Install Cypress binary
        run: pnpm exec cypress install
      - run: pnpm test
      
  automerge:
    needs: [test]
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: write
    steps:
      - uses: fastify/github-action-merge-dependabot@v3.2.0
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}


================================================
FILE: .github/workflows/plan-release.yml
================================================
name: Plan Release
on:
  workflow_dispatch:
  push:
    branches:
      - main
      - master
  pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
    types:
      - labeled
      - unlabeled

concurrency:
  group: plan-release # only the latest one of these should ever be running
  cancel-in-progress: true

jobs:
  should-run-release-plan-prepare:
    name: Should we run release-plan prepare?
    runs-on: ubuntu-latest
    outputs:
      should-prepare: ${{ steps.should-prepare.outputs.should-prepare }}
    steps:
      - uses: release-plan/actions/should-prepare-release@v1
        with:
          ref: 'master'
        id: should-prepare

  create-prepare-release-pr:
    name: Create Prepare Release PR
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: should-run-release-plan-prepare
    permissions:
      contents: write
      issues: read
      pull-requests: write
    if: needs.should-run-release-plan-prepare.outputs.should-prepare == 'true'    
    steps:
      - uses: release-plan/actions/prepare@v1
        name: Run release-plan prepare
        with:
          ref: 'master'
        env:
          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}
        id: explanation

      - uses: peter-evans/create-pull-request@v7
        name: Create Prepare Release PR
        with:
          commit-message: "Prepare Release ${{ steps.explanation.outputs.new-version}} using 'release-plan'"
          labels: "internal"
          sign-commits: true
          branch: release-preview
          title: Prepare Release ${{ steps.explanation.outputs.new-version }}
          body: |
            This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍

            -----------------------------------------

            ${{ steps.explanation.outputs.text }}


================================================
FILE: .github/workflows/publish.yml
================================================
# For every push to the primary branch with .release-plan.json modified,
# runs release-plan.

name: Publish Stable

on:
  workflow_dispatch:
  push:
    branches:
      - main
      - master
    paths:
      - '.release-plan.json'

concurrency:
  group: publish-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

jobs:
  publish:
    name: "NPM Publish"
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      attestations: write

    steps:
      - uses: actions/checkout@v5
      - uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v6
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: npm install -g npm@latest # ensure that the globally installed npm is new enough to support OIDC
      - name: Publish to NPM
        run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish
        env:
          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# Editors
/.idea/
/.vscode/

/.log/
/.nyc_output/
/coverage/
/cypress/
/docs/
/dist/
/node_modules/
/test/unit/dist
/.DS_Store
/.sass-cache
/npm-debug.log*
/stats.html
/yarn-error.log


================================================
FILE: .hsdoc
================================================
name: "Tether"
description: "Marrying DOM elements for life"
domain: "tetherjs.dev"
source: "src/**/*.js"
examples: "**/*.md"
assets: "{dist/js/*.js,dist/css/*.css,docs/css/*.css,docs/js/*,js,docs/welcome/*,examples/*}"


================================================
FILE: .npmignore
================================================
.idea/
.vscode/

coverage/
cypress/
docs/
examples/
esdoc/
jsdoc-template/
test/
tests/

.codeclimate.yml
.eslintignore
.eslintrc.js
.gitignore
.hsdoc
.stylelintrc.js
.travis.yml
babel.config.js
cypress.json
index.html
pnpm-lock.yaml
rollup.config.js
yarn.lock
yarn-error.log

CONTRIBUTING.md
HISTORY.md


================================================
FILE: .npmrc
================================================
scripts-prepend-node-path=true


================================================
FILE: .prettierrc.js
================================================
'use strict';

module.exports = {
  singleQuote: true,
  trailingComma: 'none'
};

================================================
FILE: .release-plan.json
================================================
{
  "solution": {
    "tether": {
      "impact": "patch",
      "oldVersion": "3.0.1",
      "newVersion": "3.0.2",
      "tagName": "latest",
      "constraints": [
        {
          "impact": "patch",
          "reason": "Appears in changelog section :bug: Bug Fix"
        }
      ],
      "pkgJSONPath": "./package.json"
    }
  },
  "description": "## Release (2025-12-07)\n\n* tether 3.0.2 (patch)\n\n#### :bug: Bug Fix\n* `tether`\n  * [#1707](https://github.com/shipshapecode/tether/pull/1707) Guard against invalid removeChild ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n\n#### Committers: 1\n- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n"
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## Release (2025-12-07)

* tether 3.0.2 (patch)

#### :bug: Bug Fix
* `tether`
  * [#1707](https://github.com/shipshapecode/tether/pull/1707) Guard against invalid removeChild ([@RobbieTheWagner](https://github.com/RobbieTheWagner))

#### Committers: 1
- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))

## Release (2025-12-05)

* tether 3.0.1 (patch)

#### :bug: Bug Fix
* `tether`
  * [#1705](https://github.com/shipshapecode/tether/pull/1705) Add prepare to ensure dist is published ([@RobbieTheWagner](https://github.com/RobbieTheWagner))

#### Committers: 1
- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))

## Release (2025-12-05)

* tether 3.0.0 (major)

#### :boom: Breaking Change
* `tether`
  * [#1114](https://github.com/shipshapecode/tether/pull/1114) Drop support for node < 16 ([@monshan](https://github.com/monshan))

#### :rocket: Enhancement
* `tether`
  * [#1075](https://github.com/shipshapecode/tether/pull/1075) Remove the markers when tether is destroyed ([@pieter-v](https://github.com/pieter-v))

#### :bug: Bug Fix
* `tether`
  * [#835](https://github.com/shipshapecode/tether/pull/835) Fix "document is not defined" error ([@diegohaz](https://github.com/diegohaz))

#### :memo: Documentation
* `tether`
  * [#1052](https://github.com/shipshapecode/tether/pull/1052) docs: update CDN url ([@drl990114](https://github.com/drl990114))

#### :house: Internal
* `tether`
  * [#1703](https://github.com/shipshapecode/tether/pull/1703) Add Release plan ([@RobbieTheWagner](https://github.com/RobbieTheWagner))
  * [#1702](https://github.com/shipshapecode/tether/pull/1702) Switch to pnpm ([@RobbieTheWagner](https://github.com/RobbieTheWagner))

#### Committers: 5
- Haz ([@diegohaz](https://github.com/diegohaz))
- Marika Shanahan ([@monshan](https://github.com/monshan))
- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))
- [@pieter-v](https://github.com/pieter-v)
- drl990114 ([@drl990114](https://github.com/drl990114))

Deprecated as of 10.7.0. highlight(lang, code, ...args) has been deprecated.
Deprecated as of 10.7.0. Please use highlight(code, options) instead.
https://github.com/highlightjs/highlight.js/issues/2277

## v2.0.0 (2021-03-26)

#### :bug: Bug Fix
* [#713](https://github.com/shipshapecode/tether/pull/713) Ensure parent still exists when removing event listeners ([@drewjenkins](https://github.com/drewjenkins))
* [#692](https://github.com/shipshapecode/tether/pull/692) Guard against undefined markers type ([@rwwagner90](https://github.com/rwwagner90))

#### :memo: Documentation
* [#668](https://github.com/shipshapecode/tether/pull/668) Remove Bootstrap from list ([@MartijnCuppens](https://github.com/MartijnCuppens))
* [#600](https://github.com/shipshapecode/tether/pull/600) Small Typo Fix ([@SebYLim](https://github.com/SebYLim))

#### Committers: 5
- Andrew Jenkins ([@drewjenkins](https://github.com/drewjenkins))
- Martijn Cuppens ([@MartijnCuppens](https://github.com/MartijnCuppens))
- Robert Wagner ([@rwwagner90](https://github.com/rwwagner90))
- Sebastian Lim ([@SebYLim](https://github.com/SebYLim))
- [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)

## [v2.0.0-beta.5](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.5) (2019-12-05)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v2.0.0-beta.4...v2.0.0-beta.5)

**Implemented enhancements:**

- Allow zeroElement parent to be configurable. [\#374](https://github.com/shipshapecode/tether/pull/374) ([deanmarano](https://github.com/deanmarano))

**Merged pull requests:**

- Bump rollup from 1.27.5 to 1.27.8 [\#403](https://github.com/shipshapecode/tether/pull/403) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-visualizer from 3.2.2 to 3.3.0 [\#402](https://github.com/shipshapecode/tether/pull/402) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.7.2 to 9.7.3 [\#400](https://github.com/shipshapecode/tether/pull/400) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cypress from 3.6.1 to 3.7.0 [\#399](https://github.com/shipshapecode/tether/pull/399) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint from 6.7.1 to 6.7.2 [\#397](https://github.com/shipshapecode/tether/pull/397) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-browsersync from 1.0.0 to 1.1.0 [\#396](https://github.com/shipshapecode/tether/pull/396) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 23.0.4 to 23.1.1 [\#395](https://github.com/shipshapecode/tether/pull/395) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint from 6.6.0 to 6.7.1 [\#394](https://github.com/shipshapecode/tether/pull/394) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.7.1 to 9.7.2 [\#393](https://github.com/shipshapecode/tether/pull/393) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.7.1 to 7.7.4 [\#392](https://github.com/shipshapecode/tether/pull/392) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.7.2 to 7.7.4 [\#391](https://github.com/shipshapecode/tether/pull/391) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump postcss from 7.0.21 to 7.0.23 [\#390](https://github.com/shipshapecode/tether/pull/390) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.27.1 to 1.27.5 [\#389](https://github.com/shipshapecode/tether/pull/389) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-visualizer from 3.1.1 to 3.2.2 [\#388](https://github.com/shipshapecode/tether/pull/388) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 23.0.3 to 23.0.4 [\#387](https://github.com/shipshapecode/tether/pull/387) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @testing-library/jest-dom from 4.2.3 to 4.2.4 [\#386](https://github.com/shipshapecode/tether/pull/386) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-visualizer from 2.7.2 to 3.1.1 [\#385](https://github.com/shipshapecode/tether/pull/385) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.26.5 to 1.27.1 [\#384](https://github.com/shipshapecode/tether/pull/384) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.3 to 7.7.1 [\#383](https://github.com/shipshapecode/tether/pull/383) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-visualizer from 2.6.0 to 2.7.2 [\#382](https://github.com/shipshapecode/tether/pull/382) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.26.0 to 1.26.5 [\#381](https://github.com/shipshapecode/tether/pull/381) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cypress from 3.5.0 to 3.6.1 [\#380](https://github.com/shipshapecode/tether/pull/380) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.4 to 7.7.2 [\#379](https://github.com/shipshapecode/tether/pull/379) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @testing-library/jest-dom from 4.2.0 to 4.2.3 [\#378](https://github.com/shipshapecode/tether/pull/378) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.7.0 to 9.7.1 [\#377](https://github.com/shipshapecode/tether/pull/377) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 23.0.0 to 23.0.3 [\#376](https://github.com/shipshapecode/tether/pull/376) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- test: Add more coverage to the module [\#375](https://github.com/shipshapecode/tether/pull/375) ([chuckcarpenter](https://github.com/chuckcarpenter))

## [v2.0.0-beta.4](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.4) (2019-10-29)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v2.0.0-beta.3...v2.0.0-beta.4)

**Implemented enhancements:**

- Simplify Evented code [\#339](https://github.com/shipshapecode/tether/pull/339) ([rwwagner90](https://github.com/rwwagner90))

**Closed issues:**

- Tether is harder to update due to a missing changelog [\#40](https://github.com/shipshapecode/tether/issues/40)

**Merged pull requests:**

- Bump eslint from 6.5.1 to 6.6.0 [\#372](https://github.com/shipshapecode/tether/pull/372) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump postcss from 7.0.20 to 7.0.21 [\#371](https://github.com/shipshapecode/tether/pull/371) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 22.20.0 to 23.0.0 [\#370](https://github.com/shipshapecode/tether/pull/370) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.25.2 to 1.26.0 [\#369](https://github.com/shipshapecode/tether/pull/369) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @testing-library/jest-dom from 4.1.2 to 4.2.0 [\#368](https://github.com/shipshapecode/tether/pull/368) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Split constraints into smaller functions [\#367](https://github.com/shipshapecode/tether/pull/367) ([rwwagner90](https://github.com/rwwagner90))
- Add dependabot config [\#366](https://github.com/shipshapecode/tether/pull/366) ([rwwagner90](https://github.com/rwwagner90))
- Bump postcss from 7.0.18 to 7.0.20 [\#365](https://github.com/shipshapecode/tether/pull/365) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.6.5 to 9.7.0 [\#364](https://github.com/shipshapecode/tether/pull/364) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cypress from 3.4.1 to 3.5.0 [\#363](https://github.com/shipshapecode/tether/pull/363) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.25.1 to 1.25.2 [\#362](https://github.com/shipshapecode/tether/pull/362) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 22.19.0 to 22.20.0 [\#361](https://github.com/shipshapecode/tether/pull/361) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.24.0 to 1.25.1 [\#360](https://github.com/shipshapecode/tether/pull/360) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update getClass to util and add tests [\#359](https://github.com/shipshapecode/tether/pull/359) ([chuckcarpenter](https://github.com/chuckcarpenter))
- Bump start-server-and-test from 1.10.5 to 1.10.6 [\#357](https://github.com/shipshapecode/tether/pull/357) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup-plugin-filesize from 6.2.0 to 6.2.1 [\#356](https://github.com/shipshapecode/tether/pull/356) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.23.1 to 1.24.0 [\#355](https://github.com/shipshapecode/tether/pull/355) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.6.4 to 9.6.5 [\#354](https://github.com/shipshapecode/tether/pull/354) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-jest from 22.17.0 to 22.19.0 [\#353](https://github.com/shipshapecode/tether/pull/353) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.3 to 7.6.4 [\#352](https://github.com/shipshapecode/tether/pull/352) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump start-server-and-test from 1.10.4 to 1.10.5 [\#350](https://github.com/shipshapecode/tether/pull/350) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.2 to 7.6.3 [\#349](https://github.com/shipshapecode/tether/pull/349) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.2 to 7.6.3 [\#348](https://github.com/shipshapecode/tether/pull/348) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @testing-library/jest-dom from 4.1.1 to 4.1.2 [\#347](https://github.com/shipshapecode/tether/pull/347) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @testing-library/jest-dom from 4.1.0 to 4.1.1 [\#345](https://github.com/shipshapecode/tether/pull/345) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.23.0 to 1.23.1 [\#344](https://github.com/shipshapecode/tether/pull/344) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump autoprefixer from 9.6.1 to 9.6.4 [\#343](https://github.com/shipshapecode/tether/pull/343) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.22.0 to 1.23.0 [\#342](https://github.com/shipshapecode/tether/pull/342) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump start-server-and-test from 1.10.3 to 1.10.4 [\#341](https://github.com/shipshapecode/tether/pull/341) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump start-server-and-test from 1.10.2 to 1.10.3 [\#340](https://github.com/shipshapecode/tether/pull/340) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- chore: Add basic landing page for reloading and contribution [\#338](https://github.com/shipshapecode/tether/pull/338) ([chuckcarpenter](https://github.com/chuckcarpenter))
- Bump eslint from 6.5.0 to 6.5.1 [\#337](https://github.com/shipshapecode/tether/pull/337) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint from 6.4.0 to 6.5.0 [\#336](https://github.com/shipshapecode/tether/pull/336) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump rollup from 1.21.4 to 1.22.0 [\#335](https://github.com/shipshapecode/tether/pull/335) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Move some bounds utils [\#334](https://github.com/shipshapecode/tether/pull/334) ([rwwagner90](https://github.com/rwwagner90))

## [v2.0.0-beta.3](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.3) (2019-09-30)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v2.0.0-beta.2...v2.0.0-beta.3)

**Implemented enhancements:**

- Option to disable `position: fixed` [\#152](https://github.com/shipshapecode/tether/issues/152)
- Use type-check utils instead of typeof [\#333](https://github.com/shipshapecode/tether/pull/333) ([rwwagner90](https://github.com/rwwagner90))
- Move TetherBase and use imports [\#328](https://github.com/shipshapecode/tether/pull/328) ([rwwagner90](https://github.com/rwwagner90))
- Split out some utils [\#325](https://github.com/shipshapecode/tether/pull/325) ([rwwagner90](https://github.com/rwwagner90))
- More offset utils and tests [\#319](https://github.com/shipshapecode/tether/pull/319) ([rwwagner90](https://github.com/rwwagner90))
- Move offset to utils, add tests, test getClass [\#318](https://github.com/shipshapecode/tether/pull/318) ([rwwagner90](https://github.com/rwwagner90))
- Refactor rollup config, add tests for pin and out-of-bounds [\#317](https://github.com/shipshapecode/tether/pull/317) ([rwwagner90](https://github.com/rwwagner90))
- Move deferred utils to their own file [\#315](https://github.com/shipshapecode/tether/pull/315) ([rwwagner90](https://github.com/rwwagner90))

**Fixed bugs:**

- Uglify breaks library: "Super expression must either be null or a function, not undefined" [\#298](https://github.com/shipshapecode/tether/issues/298)
- production build with angular cli \(uglify\) results in `undefined` error [\#295](https://github.com/shipshapecode/tether/issues/295)
- Does not compile with parcel-bundler [\#284](https://github.com/shipshapecode/tether/issues/284)
- Tether not initialize window.Tether when loaded by ReqireJS [\#257](https://github.com/shipshapecode/tether/issues/257)
- Can't disable classes [\#253](https://github.com/shipshapecode/tether/issues/253)
- Duplicate Identifiers within Tether.js Library [\#206](https://github.com/shipshapecode/tether/issues/206)
- Remove classes when set to false [\#329](https://github.com/shipshapecode/tether/pull/329) ([rwwagner90](https://github.com/rwwagner90))

**Closed issues:**

- Action required: Greenkeeper could not be activated 🚨 [\#304](https://github.com/shipshapecode/tether/issues/304)
- Import of Evented from TetherBase.Utils instead of global scope [\#261](https://github.com/shipshapecode/tether/issues/261)
- SVGAnimatedString is not defined [\#201](https://github.com/shipshapecode/tether/issues/201)
- Option to not append to the body [\#189](https://github.com/shipshapecode/tether/issues/189)
- UglifyJS warnings [\#183](https://github.com/shipshapecode/tether/issues/183)
- Clean up on destroy [\#36](https://github.com/shipshapecode/tether/issues/36)

**Merged pull requests:**

- Document events [\#331](https://github.com/shipshapecode/tether/pull/331) ([rwwagner90](https://github.com/rwwagner90))
- Add test for fixed anchoring on scroll [\#330](https://github.com/shipshapecode/tether/pull/330) ([chuckcarpenter](https://github.com/chuckcarpenter))
- Remove facebook example [\#327](https://github.com/shipshapecode/tether/pull/327) ([rwwagner90](https://github.com/rwwagner90))
- chore: Remove example of 3rd party lib [\#326](https://github.com/shipshapecode/tether/pull/326) ([chuckcarpenter](https://github.com/chuckcarpenter))
- Bump eslint-plugin-ship-shape from 0.6.0 to 0.7.1 [\#324](https://github.com/shipshapecode/tether/pull/324) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- test: Remove tooltip example from outside lib [\#323](https://github.com/shipshapecode/tether/pull/323) ([chuckcarpenter](https://github.com/chuckcarpenter))
- Bump sinon from 7.4.2 to 7.5.0 [\#322](https://github.com/shipshapecode/tether/pull/322) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.0 to 7.6.2 [\#321](https://github.com/shipshapecode/tether/pull/321) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.0 to 7.6.2 [\#320](https://github.com/shipshapecode/tether/pull/320) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Remove classes on destroy [\#316](https://github.com/shipshapecode/tether/pull/316) ([rwwagner90](https://github.com/rwwagner90))
- Bump rollup from 1.21.3 to 1.21.4 [\#314](https://github.com/shipshapecode/tether/pull/314) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))

## [v2.0.0-beta.2](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.2) (2019-09-18)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v2.0.0-beta.1...v2.0.0-beta.2)

## [v2.0.0-beta.1](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.1) (2019-09-18)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v2.0.0-beta.0...v2.0.0-beta.1)

## [v2.0.0-beta.0](https://github.com/shipshapecode/tether/tree/v2.0.0-beta.0) (2019-09-18)

[Full Changelog](https://github.com/shipshapecode/tether/compare/v1.4.7...v2.0.0-beta.0)

**Breaking changes:**

- Remove dist from git [\#311](https://github.com/shipshapecode/tether/pull/311) ([rwwagner90](https://github.com/rwwagner90))
- Move class utils to a utils file, drop IE9 support [\#310](https://github.com/shipshapecode/tether/pull/310) ([rwwagner90](https://github.com/rwwagner90))

**Implemented enhancements:**

- Return `this` in Evented class for easy chaining [\#309](https://github.com/shipshapecode/tether/pull/309) ([rwwagner90](https://github.com/rwwagner90))
- Add `allowPositionFixed` optimization option [\#308](https://github.com/shipshapecode/tether/pull/308) ([rwwagner90](https://github.com/rwwagner90))

**Closed issues:**

- Transferring ownership [\#303](https://github.com/shipshapecode/tether/issues/303)
- No test cases to run the package [\#293](https://github.com/shipshapecode/tether/issues/293)
- Not Compatible with TypeScript compiler [\#263](https://github.com/shipshapecode/tether/issues/263)
- dist/js/tether.min.js is outdated [\#256](https://github.com/shipshapecode/tether/issues/256)
- no version information in min.js [\#239](https://github.com/shipshapecode/tether/issues/239)

**Merged pull requests:**

- Add tests for enable/disable [\#306](https://github.com/shipshapecode/tether/pull/306) ([rwwagner90](https://github.com/rwwagner90))
- Add basic tests, sass -\> scss, gulp -\> rollup, etc [\#305](https://github.com/shipshapecode/tether/pull/305) ([rwwagner90](https://github.com/rwwagner90))
- Fix code example in README.md [\#216](https://github.com/shipshapecode/tether/pull/216) ([Stanton](https://github.com/Stanton))
- Add reactstrap to examples of projects using tether [\#211](https://github.com/shipshapecode/tether/pull/211) ([eddywashere](https://github.com/eddywashere))

## 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


\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide

You will need:

- [pnpm](https://pnpm.io/)

Windows users will need additional setup to enable build capabilities in NPM.
From an administrative command window:

```sh
    pnpm global add windows-build-tools
```

## Getting started

1. Fork the project
2. Clone your forked project by running `git clone git@github.com:{
   YOUR_USERNAME }/tether.git`
3. Run `pnpm` to install node modules
4. Test that you can build the source by running `pnpm build` and ensure the `dist` directory appears.

## Writing code!

We use `rollup` to facilitate things like transpilation, minification, etc. so
you can 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 `pnpm build`.

2. Some examples are served out of the `examples` directory. Running `pnpm start` will open the list in your browser and initiate a live-reloading session as you make changes.


## Opening Pull Requests

1. Please 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).

2. Open a Pull Request on GitHub with a description of your changes.


## Testing

All PRs, that change code functionality, are required to have accompanying tests.

### Acceptance Tests

Acceptance tests are run using [`cypress`](https://github.com/cypress-io/cypress). A number of different testing configurations can be found in [`package.json`](/package.json), but you can simply run `pnpm test:ci:watch` to build your latest changes and begin running the tests inside a Chrome browser instance.

⚠️ The acceptance tests are set up to run on `localhost` port `9002`. If you'd like to change this port, make sure to change the `baseUrl` option inside of [`cypress.json`](/cypress.json), and change any references to port `9002` in [`package.json`](/package.json) accordingly.



================================================
FILE: HISTORY.md
================================================
## 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


================================================
FILE: LICENSE
================================================
Copyright (c) 2014-2019 HubSpot, Inc.
Copyright (c) 2019-2022 Ship Shape Consulting LLC

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.



================================================
FILE: README.md
================================================
# Tether

<div>
  <a href="https://shipshape.io">
    <img align="left" src="http://i.imgur.com/DWHQjA5.png" alt="Ship Shape" width="50" height="50"/>
  </a>
 
  **[Tether is maintained by Ship Shape. Contact us for web app consulting, development, and training for your project](https://shipshape.io/services/app-development/)**.
</div>  

[![npm version](https://badge.fury.io/js/tether.svg)](http://badge.fury.io/js/tether)
![Download count all time](https://img.shields.io/npm/dt/tether.svg)
[![npm](https://img.shields.io/npm/dm/tether.svg)]()
![npm bundle size](https://img.shields.io/bundlephobia/minzip/tether.svg)
[![CI Build](https://github.com/shipshapecode/tether/actions/workflows/main.yml/badge.svg)](https://github.com/shipshapecode/tether/actions/workflows/main.yml)

## 🐙 Project status 🐙 

We at Ship Shape have recently taken over Tether's maintenance and hope to modernize and revitalize it. Stay tuned for updates!

## Install

__npm__
```sh
npm install tether
```

For the latest beta:

```sh
npm install tether@next
```

__download__

Or download from the [releases](https://github.com/shipshapecode/tether/releases).

## Introduction

[Tether](http://tetherjs.dev/) 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 we’re 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), and [dropdown menus](http://github.hubspot.com/drop/docs/welcome). Tether is flexible and can be used to [solve](http://tetherjs.dev/examples/out-of-bounds/) [all](http://tetherjs.dev/examples/content-visible) [kinds](http://tetherjs.dev/examples/element-scroll) [of](http://tetherjs.dev/examples/enable-disable) interesting [problems](http://tetherjs.dev/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://tetherjs.dev/) 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.

Tether’s goal to is to help you position your elements side-by-side when needed.

Let’s say you’ve started working on your dream project&mdash;a fancy web app that’s 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, you’d 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 aren’t 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 library’s options. Tether’s extensive list of options can be found in the [Tether documentation](http://tetherjs.dev/).

    ```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! It’ll 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://tetherjs.dev/examples/list_of_examples/)!

## Usage
You only need to include `tether.min.js` in your page:
```
<script src="path/to/dist/js/tether.min.js"></script>
```
Or use a CDN:
```
<script src="https://cdn.jsdelivr.net/npm/tether@2.0.0-beta.5/dist/js/tether.min.js"></script>
```

The css files are not required to get tether running.

For more details jump straight in to the detailed [Usage](http://tetherjs.dev/#usage) page.

[![Tether Docs](http://i.imgur.com/YCx8cLr.png)](http://tetherjs.dev/#usage)

[Demo & API Documentation](http://tetherjs.dev/)

## 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; 2019-2022 Ship Shape Consulting LLC - [MIT License](LICENSE)
Copyright &copy; 2014-2018 HubSpot - [MIT License](LICENSE)


================================================
FILE: RELEASE.md
================================================
# Release Process

Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged.

## Preparation

Since the majority of the actual release process is automated, the remaining tasks before releasing are:

- correctly labeling **all** pull requests that have been merged since the last release
- updating pull request titles so they make sense to our users

Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall
guiding principle here is that changelogs are for humans, not machines.

When reviewing merged PR's the labels to be used are:

- breaking - Used when the PR is considered a breaking change.
- enhancement - Used when the PR adds a new feature or enhancement.
- bug - Used when the PR fixes a bug included in a previous release.
- documentation - Used when the PR adds or updates documentation.
- internal - Internal changes or things that don't fit in any other category.

**Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal`

## Release

Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/shipshapecode/tether/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR


================================================
FILE: __mocks__/styleMock.js
================================================
module.exports = {};

================================================
FILE: babel.config.js
================================================
module.exports = function(api) {
  api.cache(true);

  return {
    env: {
      development: {
        presets: [
          [
            '@babel/preset-env',
            {
              loose: true
            }
          ]
        ]
      },
      test: {
        presets: [
          [
            '@babel/preset-env'
          ]
        ],
        plugins: [
          'babel-plugin-rewire',
          'transform-es2015-modules-commonjs'
        ]
      }
    }
  };
};


================================================
FILE: cypress.config.cjs
================================================
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  fixturesFolder: 'test/cypress/fixtures',
  video: false,
  e2e: {
    // We've imported your old cypress plugins here.
    // You may want to clean this up later by importing these.
    setupNodeEvents(on, config) {
      return require('./test/cypress/plugins/index.js')(on, config)
    },
    baseUrl: 'http://localhost:9002',
    specPattern: 'test/cypress/integration/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'test/cypress/support/index.js',
  },
})


================================================
FILE: examples/common/css/style.css
================================================
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;
}



================================================
FILE: examples/content-visible/index.html
================================================
<!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="../../dist/js/tether.js"></script>
    <script>
      new Tether({
        element: '.element',
        target: '.content-box',
        attachment: 'middle center',
        targetAttachment: 'middle center',
        targetModifier: 'visible'
      });
    </script>
  </body>
</html>


================================================
FILE: examples/dolls/dolls.css
================================================
.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;
}


================================================
FILE: examples/dolls/dolls.js
================================================
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();
}


================================================
FILE: examples/dolls/index.html
================================================
<link rel="stylesheet" href="./dolls.css" />
<script src="../../dist/js/tether.js"></script>
<script src="./dolls.js"></script>
<body>
  <div class="scroll">
  </div>
</body>


================================================
FILE: examples/element-scroll/index.html
================================================
<!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="../../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>


================================================
FILE: examples/enable-disable/index.html
================================================
<!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="../../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>


================================================
FILE: examples/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Examples</title>
</head>
<body>
  <h3 id="examples">Examples</h3>
  <p>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.</p>
  <h4 id="beginner">Beginner</h4>
  <ul>
    <li><a href="./simple">simple</a>: A simple example to get you started</li>
    <li><a href="./out-of-bounds">out-of-bounds</a>: How to hide the element when it would
      otherwise be offscreen</li>
    <li><a href="./pin">pin</a>: How to pin the element so it never goes offscreen</li>
    <li><a href="./enable-disable">enable-disable</a>: How to enable and disable the Tether
      in JavaScript</li>
  </ul>
  <h4 id="advanced">Advanced</h4>
  <ul>
    <li><a href="./content-visible">content-visible</a>: Demonstrates using the <code>'visible'</code>
      <code>targetModifier</code> to align an element with the visible portion of another.</li>
    <li><a href="./dolls">dolls</a>: A performance test to show several dozen elements,
      each tethered to the previous. Try dragging the top left tether.</li>
    <li><a href="./element-scroll">element-scroll</a>: Demonstrates using the <code>'scroll-handle'</code>
      <code>targetModifier</code> to align an element with the scrollbar of an element.</li>
    <li><a href="./scroll">scroll</a>: Demonstrates using the <code>'scroll-handle'</code>
      <code>targetModifier</code>
      to align an element with the body's scroll handle.</li>
    <li><a href="./viewport">viewport</a>: Demonstrates aligning an element with the
      viewport by using the <code>'visible'</code> <code>targetModifier</code> when tethered to the body.</li>
  </ul>
</body>
</html>

================================================
FILE: examples/out-of-bounds/index.html
================================================
<!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="../../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>


================================================
FILE: examples/pin/index.html
================================================
<!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="../../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>


================================================
FILE: examples/resources/css/base.css
================================================
body {
    font-family: "Helvetica Neue", sans-serif;
    color: #444;
    margin: 0px;
}

a {
    cursor: pointer;
    color: blue;
}

================================================
FILE: examples/scroll/index.html
================================================
<!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="../../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>


================================================
FILE: examples/simple/index.html
================================================
<!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="../../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>


================================================
FILE: examples/testbed/index.html
================================================
<!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="../../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>


================================================
FILE: examples/viewport/colors.css
================================================
@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; }


================================================
FILE: examples/viewport/index.html
================================================
<!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="../../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>


================================================
FILE: jest.config.js
================================================
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
  // Automatically clear mock calls and instances between every test
  clearMocks: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  collectCoverageFrom: ['src/js/**/*.js'],

  // The directory where Jest should output its coverage files
  coverageDirectory: 'coverage',

  // An array of file extensions your modules use
  moduleFileExtensions: ['js'],

  moduleNameMapper: {
    '.+\\.(css|styl|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.js'
  },

  // The path to a module that runs some code to configure or set up the testing framework before each test
  setupFilesAfterEnv: ['<rootDir>/test/unit/setupTests.js'],

  testEnvironment: 'jsdom',

  testPathIgnorePatterns: ['/node_modules/', '/test/cypress/'],

  // A map from regular expressions to paths to transformers
  transform: {
    '^.+\\.js$': 'babel-jest'
  },

  // Transform sinon ESM module (handle pnpm structure)
  transformIgnorePatterns: [
    'node_modules/(?!(.pnpm|sinon))'
  ]
};


================================================
FILE: package.json
================================================
{
  "name": "tether",
  "version": "3.0.2",
  "description": "A client-side library to make absolutely positioned elements attach to elements in the page efficiently.",
  "repository": {
    "type": "git",
    "url": "https://github.com/shipshapecode/tether.git"
  },
  "license": "MIT",
  "maintainers": [
    "Nicholas Hwang <nick.joosung.hwang@gmail.com>",
    "Trevor Burnham <trevorburnham@gmail.com>"
  ],
  "main": "dist/js/tether.js",
  "module": "dist/js/tether.esm.js",
  "scripts": {
    "build": "pnpm clean && rollup -c",
    "changelog": "github_changelog_generator -u shipshapecode -p tether --since-tag v1.4.7",
    "clean": "rimraf dist",
    "cy:open": "./node_modules/.bin/cypress open",
    "cy:run": "./node_modules/.bin/cypress run",
    "lint:js": "eslint . --ext js",
    "prepare": "pnpm build",
    "start": "pnpm watch",
    "start-test-server": "http-server -p 9002",
    "test": "pnpm lint:js && pnpm test:ci",
    "test:ci": "pnpm test:unit:ci && pnpm test:cy:ci",
    "test:cy:ci": "pnpm build && start-server-and-test start-test-server http://127.0.0.1:9002 cy:run",
    "test:cy:watch": "pnpm build && start-server-and-test start-test-server http://127.0.0.1:9002 cy:open",
    "test:unit:ci": "jest --coverage",
    "test:unit:watch": "jest --watch",
    "watch": "pnpm clean && rollup -c --environment DEVELOPMENT --watch"
  },
  "devDependencies": {
    "@babel/core": "^7.28.6",
    "@babel/preset-env": "^7.29.0",
    "@testing-library/jest-dom": "^5.17.0",
    "autoprefixer": "^10.4.24",
    "babel-jest": "^29.7.0",
    "babel-plugin-rewire": "^1.2.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "chai": "^6.2.2",
    "cssnano": "^7.1.2",
    "cypress": "15.9.0",
    "eslint": "^8.57.1",
    "eslint-plugin-jest": "^29.12.1",
    "http-server": "^14.1.1",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^30.2.0",
    "jest-expect-message": "^1.1.3",
    "jsdom": "^28.0.0",
    "mutationobserver-shim": "^0.3.7",
    "postcss": "^8.5.6",
    "release-plan": "^0.17.4",
    "rimraf": "^6.1.2",
    "rollup": "^2.79.2",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-browsersync": "^1.3.3",
    "rollup-plugin-eslint": "^7.0.0",
    "rollup-plugin-filesize": "^10.0.0",
    "rollup-plugin-license": "^3.6.0",
    "rollup-plugin-sass": "^1.15.3",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-visualizer": "^6.0.5",
    "sinon": "^21.0.1",
    "start-server-and-test": "^2.1.3"
  },
  "engines": {
    "node": ">= 20",
    "pnpm": ">= 10"
  },
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "pnpm": {
    "onlyBuiltDependencies": [
      "@parcel/watcher",
      "core-js",
      "cypress"
    ]
  },
  "authors": [
    "Zack Bloom <zackbloom@gmail.com>",
    "Adam Schwartz <adam.flynn.schwartz@gmail.com>"
  ]
}


================================================
FILE: rollup.config.js
================================================
import autoprefixer from 'autoprefixer';
import babel from 'rollup-plugin-babel';
import browsersync from 'rollup-plugin-browsersync';
import cssnano from 'cssnano';
import { eslint } from 'rollup-plugin-eslint';
import filesize from 'rollup-plugin-filesize';
import license from 'rollup-plugin-license';
import postcss from 'postcss';
import sass from 'rollup-plugin-sass';
import { terser } from 'rollup-plugin-terser';
import visualizer from 'rollup-plugin-visualizer';
import fs from 'fs';

const pkg = require('./package.json');
const banner = ['/*!', pkg.name, pkg.version, '*/\n'].join(' ');

const env = process.env.DEVELOPMENT ? 'development' : 'production';

function getSassOptions(minify = false) {
  const postcssPlugins = [
    autoprefixer({
      grid: false
    })
  ];

  if (minify) {
    postcssPlugins.push(cssnano());
  }
  return {
    output(styles, styleNodes) {
      fs.mkdirSync('dist/css', { recursive: true }, (err) => {
        if (err) {
          throw err;
        }
      });

      styleNodes.forEach(({ id, content }) => {
        const scssName = id.substring(id.lastIndexOf('/') + 1, id.length);
        const [name] = scssName.split('.');
        fs.writeFileSync(`dist/css/${name}.${minify ? 'min.css' : 'css'}`, content);
      });
    },
    processor: (css) => postcss(postcssPlugins)
      .process(css, {
        from: undefined
      })
      .then((result) => result.css)
  };
}

const plugins = [
  eslint({
    include: '**/*.js'
  }),
  babel(),
  sass(getSassOptions(false)),
  license({
    banner
  }),
  filesize(),
  visualizer()
];

// If we are running with --environment DEVELOPMENT, serve via browsersync for local development
if (process.env.DEVELOPMENT) {
  plugins.push(
    browsersync({
      host: 'localhost',
      watch: true,
      port: 3000,
      notify: false,
      open: true,
      server: {
        baseDir: 'examples',
        routes: {
          '/dist/js/tether.js': 'dist/js/tether.js',
          '/dist/css/tether-theme-arrows-dark.css':
            'dist/css/tether-theme-arrows-dark.css'
        }
      }
    })
  );
}

const rollupBuilds = [
  {
    input: './src/js/tether.js',
    output: [
      {
        file: pkg.main,
        format: 'umd',
        name: 'Tether',
        sourcemap: true
      },
      {
        file: pkg.module,
        format: 'esm',
        sourcemap: true
      }
    ],
    plugins
  }
];

rollupBuilds.push({
  input: './src/js/tether.js',
  output: [
    {
      file: 'dist/js/tether.min.js',
      format: 'umd',
      name: 'Tether',
      sourcemap: true
    },
    {
      file: 'dist/js/tether.esm.min.js',
      format: 'esm',
      sourcemap: true
    }
  ],
  plugins: [
    babel(),
    sass(getSassOptions(true)),
    terser(),
    license({
      banner
    }),
    filesize(),
    visualizer()
  ]
});

export default rollupBuilds;


================================================
FILE: src/css/helpers/_tether-theme-arrows.scss
================================================
@mixin 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, 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;
      }
    }
  }
}


================================================
FILE: src/css/helpers/_tether-theme-basic.scss
================================================
@mixin 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, 0.2);
      font-family: inherit;
      background: $backgroundColor;
      color: $color;
      padding: 1em;
      font-size: 1.1em;
      line-height: 1.5em;
    }
  }
}


================================================
FILE: src/css/helpers/_tether.scss
================================================
@mixin tether($themePrefix: "tether") {
  .#{$themePrefix}-element, .#{$themePrefix}-element * {
    &, &:after, &:before {
      box-sizing: border-box;
    }
  }

  .#{$themePrefix}-element {
    position: absolute;
    display: none;

    &.#{$themePrefix}-open {
      display: block;
    }
  }
}


================================================
FILE: src/css/mixins/_inline-block.scss
================================================
@mixin inline-block {
  display: inline-block;
  vertical-align: middle;
  *vertical-align: auto;
  *zoom: 1;
  *display: inline;
}


================================================
FILE: src/css/mixins/_pie-clearfix.scss
================================================
@mixin pie-clearfix {
  *zoom: 1;

  &:after {
    content: "";
    display: table;
    clear: both;
  }
}


================================================
FILE: src/css/tether-theme-arrows-dark.scss
================================================
@import "helpers/tether";
@import "helpers/tether-theme-arrows";

$themePrefix: "tether";
$themeName: "arrows-dark";
$arrowSize: 16px;
$backgroundColor: #000;
$color: #fff;
$useDropShadow: false;

@include tether($themePrefix: $themePrefix);
@include tether-theme-arrows($themePrefix: $themePrefix, $themeName: $themeName, $arrowSize: $arrowSize, $backgroundColor: $backgroundColor, $color: $color, $useDropShadow: $useDropShadow);


================================================
FILE: src/css/tether-theme-arrows.scss
================================================
@import "helpers/tether";
@import "helpers/tether-theme-arrows";

$themePrefix: "tether";
$themeName: "arrows";
$arrowSize: 16px;
$backgroundColor: #fff;
$color: inherit;
$useDropShadow: true;

@include tether($themePrefix: $themePrefix);
@include tether-theme-arrows($themePrefix: $themePrefix, $themeName: $themeName, $arrowSize: $arrowSize, $backgroundColor: $backgroundColor, $color: $color, $useDropShadow: $useDropShadow);


================================================
FILE: src/css/tether-theme-basic.scss
================================================
@import "helpers/tether";
@import "helpers/tether-theme-basic";

$themePrefix: "tether";
$themeName: "basic";
$backgroundColor: #fff;
$color: inherit;

@include tether($themePrefix: $themePrefix);
@include tether-theme-basic($themePrefix: $themePrefix, $themeName: $themeName, $backgroundColor: $backgroundColor, $color: $color);


================================================
FILE: src/css/tether.scss
================================================
@import "helpers/tether";

$themePrefix: "tether";

@include tether($themePrefix: $themePrefix);


================================================
FILE: src/js/abutment.js
================================================
import { getClass, updateClasses } from './utils/classes';
import { defer } from './utils/deferred';
import { getBounds } from './utils/bounds';

export default {
  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 sides = ['left', 'top', 'right', 'bottom'];
    const { classes, classPrefix } = this.options;
    this.all.push(getClass('abutted', classes, classPrefix));
    sides.forEach((side) => {
      this.all.push(`${getClass('abutted', classes, classPrefix)}-${side}`);
    });

    if (abutted.length) {
      this.add.push(getClass('abutted', classes, classPrefix));
    }

    abutted.forEach((side) => {
      this.add.push(`${getClass('abutted', classes, classPrefix)}-${side}`);
    });

    defer(() => {
      if (!(this.options.addTargetClasses === false)) {
        updateClasses(this.target, this.add, this.all);
      }
      updateClasses(this.element, this.add, this.all);
    });

    return true;
  }
};


================================================
FILE: src/js/constraint.js
================================================
import { getClass, updateClasses } from './utils/classes';
import { defer } from './utils/deferred';
import { extend } from './utils/general';
import { getBounds } from './utils/bounds';
import { isString, isUndefined } from './utils/type-check';

const BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom'];

/**
 * Returns an array of bounds of the format [left, top, right, bottom]
 * @param tether
 * @param to
 * @return {*[]|HTMLElement|ActiveX.IXMLDOMElement}
 */
function getBoundingRect(body, tether, to) {
  // arg to is required
  if (!to) {
    return null;
  }
  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 (!isUndefined(to.nodeType)) {
    const node = to;
    const size = getBounds(body, 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;
}

/**
 * Add out of bounds classes to the list of classes we add to tether
 * @param {string[]} oob An array of directions that are out of bounds
 * @param {string[]} addClasses The array of classes to add to Tether
 * @param {string[]} classes The array of class types for Tether
 * @param {string} classPrefix The prefix to add to the front of the class
 * @param {string} outOfBoundsClass The class to apply when out of bounds
 * @private
 */
function _addOutOfBoundsClass(oob, addClasses, classes, classPrefix, outOfBoundsClass) {
  if (oob.length) {
    let oobClass;
    if (!isUndefined(outOfBoundsClass)) {
      oobClass = outOfBoundsClass;
    } else {
      oobClass = getClass('out-of-bounds', classes, classPrefix);
    }

    addClasses.push(oobClass);
    oob.forEach((side) => {
      addClasses.push(`${oobClass}-${side}`);
    });
  }
}

/**
 * Calculates if out of bounds or pinned in the X direction.
 *
 * @param {number} left
 * @param {number[]} bounds Array of bounds of the format [left, top, right, bottom]
 * @param {number} width
 * @param pin
 * @param pinned
 * @param {string[]} oob
 * @return {number}
 * @private
 */
function _calculateOOBAndPinnedLeft(left, bounds, width, pin, pinned, oob) {
  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');
    }
  }

  return left;
}

/**
 * Calculates if out of bounds or pinned in the Y direction.
 *
 * @param {number} top
 * @param {number[]} bounds Array of bounds of the format [left, top, right, bottom]
 * @param {number} height
 * @param pin
 * @param {string[]} pinned
 * @param {string[]} oob
 * @return {number}
 * @private
 */
function _calculateOOBAndPinnedTop(top, bounds, height, pin, pinned, 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');
    }
  }

  return top;
}

/**
 * Flip X "together"
 * @param {object} tAttachment The target attachment
 * @param {object} eAttachment The element attachment
 * @param {number[]} bounds Array of bounds of the format [left, top, right, bottom]
 * @param {number} width
 * @param targetWidth
 * @param {number} left
 * @private
 */
function _flipXTogether(tAttachment, eAttachment, bounds, width, targetWidth, left) {
  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';
    }
  }

  return left;
}

/**
 * Flip Y "together"
 * @param {object} tAttachment The target attachment
 * @param {object} eAttachment The element attachment
 * @param {number[]} bounds Array of bounds of the format [left, top, right, bottom]
 * @param {number} height
 * @param targetHeight
 * @param {number} top
 * @private
 */
function _flipYTogether(tAttachment, eAttachment, bounds, height, targetHeight, top) {
  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';
    }
  }

  return top;
}

/**
 * Get all the initial classes
 * @param classes
 * @param {string} classPrefix
 * @param constraints
 * @return {[*, *]}
 * @private
 */
function _getAllClasses(classes, classPrefix, constraints) {
  const allClasses = [getClass('pinned', classes, classPrefix), getClass('out-of-bounds', classes, classPrefix)];

  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}`);
    });
  });

  return allClasses;
}

export default {
  position({ top, left, targetAttachment }) {
    if (!this.options.constraints) {
      return true;
    }

    let { height, width } = this.cache('element-bounds', () => {
      return getBounds(this.bodyElement, this.element);
    });

    if (width === 0 && height === 0 && !isUndefined(this.lastSize)) {
      // 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 { classes, classPrefix } = this.options;

    const allClasses = _getAllClasses(classes, classPrefix, this.options.constraints);
    const addClasses = [];

    const tAttachment = extend({}, targetAttachment);
    const eAttachment = extend({}, this.attachment);

    this.options.constraints.forEach((constraint) => {
      let { to, attachment, pin } = constraint;

      if (isUndefined(attachment)) {
        attachment = '';
      }

      let changeAttachX, changeAttachY;
      if (attachment.indexOf(' ') >= 0) {
        [changeAttachY, changeAttachX] = attachment.split(' ');
      } else {
        changeAttachX = changeAttachY = attachment;
      }

      const bounds = getBoundingRect(this.bodyElement, 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') {
        top = _flipYTogether(tAttachment, eAttachment, bounds, height, targetHeight, 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') {
        left = _flipXTogether(tAttachment, eAttachment, bounds, width, targetWidth, 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 (isString(pin)) {
        pin = pin.split(',').map((p) => p.trim());
      } else if (pin === true) {
        pin = ['top', 'left', 'right', 'bottom'];
      }

      pin = pin || [];

      const pinned = [];
      const oob = [];

      left = _calculateOOBAndPinnedLeft(left, bounds, width, pin, pinned, oob);
      top = _calculateOOBAndPinnedTop(top, bounds, height, pin, pinned, oob);

      if (pinned.length) {
        let pinnedClass;
        if (!isUndefined(this.options.pinnedClass)) {
          pinnedClass = this.options.pinnedClass;
        } else {
          pinnedClass = getClass('pinned', classes, classPrefix);
        }

        addClasses.push(pinnedClass);
        pinned.forEach((side) => {
          addClasses.push(`${pinnedClass}-${side}`);
        });
      }

      _addOutOfBoundsClass(oob, addClasses, classes, classPrefix, this.options.outOfBoundsClass);

      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 };
  }
};


================================================
FILE: src/js/evented.js
================================================
import { isUndefined } from './utils/type-check';

export class Evented {
  on(event, handler, ctx, once = false) {
    if (isUndefined(this.bindings)) {
      this.bindings = {};
    }
    if (isUndefined(this.bindings[event])) {
      this.bindings[event] = [];
    }
    this.bindings[event].push({ handler, ctx, once });

    return this;
  }

  once(event, handler, ctx) {
    return this.on(event, handler, ctx, true);
  }

  off(event, handler) {
    if (isUndefined(this.bindings) ||
      isUndefined(this.bindings[event])) {
      return this;
    }

    if (isUndefined(handler)) {
      delete this.bindings[event];
    } else {
      this.bindings[event].forEach((binding, index) => {
        if (binding.handler === handler) {
          this.bindings[event].splice(index, 1);
        }
      });
    }

    return this;
  }

  trigger(event, ...args) {
    if (!isUndefined(this.bindings) && this.bindings[event]) {
      this.bindings[event].forEach((binding, index) => {
        const { ctx, handler, once } = binding;

        const context = ctx || this;

        handler.apply(context, args);

        if (once) {
          this.bindings[event].splice(index, 1);
        }
      });
    }

    return this;
  }
}


================================================
FILE: src/js/shift.js
================================================
import { isFunction, isString } from './utils/type-check';

export default {
  position({ top, left }) {
    if (!this.options.shift) {
      return;
    }

    let { shift } = this.options;
    if (isFunction(shift)) {
      shift = shift.call(this, { top, left });
    }

    let shiftTop, shiftLeft;
    if (isString(shift)) {
      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 };
  }
};


================================================
FILE: src/js/tether.js
================================================
import '../css/tether.scss';
import '../css/tether-theme-arrows.scss';
import '../css/tether-theme-arrows-dark.scss';
import '../css/tether-theme-basic.scss';
import Abutment from './abutment';
import Constraint from './constraint';
import Shift from './shift';
import { Evented } from './evented';
import {
  addClass,
  getClass,
  removeClass,
  updateClasses
} from './utils/classes';
import { defer, flush } from './utils/deferred';
import { extend, getScrollBarSize } from './utils/general';
import {
  addOffset,
  attachmentToOffset,
  autoToFixedAttachment,
  offsetToPx,
  parseTopLeft
} from './utils/offset';
import {
  getBounds,
  getScrollHandleBounds,
  getVisibleBounds,
  removeUtilElements
} from './utils/bounds';
import { getOffsetParent, getScrollParents } from './utils/parents';
import { isNumber, isObject, isString, isUndefined } from './utils/type-check';

const TetherBase = { modules: [Constraint, Abutment, Shift] };

function isFullscreenElement(e) {
  let d = e.ownerDocument;
  let fe =
    d.fullscreenElement ||
    d.webkitFullscreenElement ||
    d.mozFullScreenElement ||
    d.msFullscreenElement;
  return fe === e;
}

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() {
  return performance.now();
}

(() => {
  let lastCall = null;
  let lastDuration = null;
  let pendingTimeout = null;

  const tick = () => {
    if (!isUndefined(lastDuration) && 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 (!isUndefined(lastCall) && 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' && !isUndefined(window.addEventListener)) {
    ['resize', 'scroll', 'touchmove'].forEach((event) => {
      window.addEventListener(event, tick);
    });
  }
})();

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 (!isUndefined(module.initialize)) {
        module.initialize.call(this);
      }
    });

    this.position();
  }

  setOptions(options, pos = true) {
    const defaults = {
      offset: '0 0',
      targetOffset: '0 0',
      targetAttachment: 'auto auto',
      classPrefix: 'tether',
      bodyElement: document.body
    };

    this.options = extend(defaults, options);

    let { element, target, targetModifier, bodyElement } = this.options;
    this.element = element;
    this.target = target;
    this.targetModifier = targetModifier;

    if (typeof bodyElement === 'string') {
      bodyElement = document.querySelector(bodyElement);
    }
    this.bodyElement = bodyElement;

    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 (isUndefined(this[key])) {
        throw new Error(
          'Tether Error: Both element and target must be defined'
        );
      }

      if (!isUndefined(this[key].jquery)) {
        this[key] = this[key][0];
      } else if (isString(this[key])) {
        this[key] = document.querySelector(this[key]);
      }
    });

    this._addClasses();

    if (!this.options.attachment) {
      throw new Error('Tether Error: You must provide an attachment');
    }

    this.targetAttachment = parseTopLeft(this.options.targetAttachment);
    this.attachment = parseTopLeft(this.options.attachment);
    this.offset = parseTopLeft(this.options.offset);
    this.targetOffset = parseTopLeft(this.options.targetOffset);

    if (!isUndefined(this.scrollParents)) {
      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 (!isUndefined(this.targetModifier)) {
      if (this.targetModifier === 'visible') {
        return getVisibleBounds(this.bodyElement, this.target);
      } else if (this.targetModifier === 'scroll-handle') {
        return getScrollHandleBounds(this.bodyElement, this.target);
      }
    } else {
      return getBounds(this.bodyElement, 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 (isUndefined(this._cache)) {
      this._cache = {};
    }

    if (isUndefined(this._cache[k])) {
      this._cache[k] = getter.call(this);
    }

    return this._cache[k];
  }

  enable(pos = true) {
    const { classes, classPrefix } = this.options;
    if (!(this.options.addTargetClasses === false)) {
      addClass(this.target, getClass('enabled', classes, classPrefix));
    }
    addClass(this.element, getClass('enabled', classes, classPrefix));
    this.enabled = true;

    this.scrollParents.forEach((parent) => {
      if (parent !== this.target.ownerDocument) {
        parent.addEventListener('scroll', this.position);
      }
    });

    if (pos) {
      this.position();
    }
  }

  disable() {
    const { classes, classPrefix } = this.options;
    removeClass(this.target, getClass('enabled', classes, classPrefix));
    removeClass(this.element, getClass('enabled', classes, classPrefix));
    this.enabled = false;

    if (!isUndefined(this.scrollParents)) {
      this.scrollParents.forEach((parent) => {
        if (parent && parent.removeEventListener) {
          parent.removeEventListener('scroll', this.position);
        }
      });
    }
  }

  destroy() {
    this.disable();

    this._removeClasses();

    TetherBase.modules.forEach((module) => {
      if (!isUndefined(module.destroy)) {
        module.destroy.call(this);
      }
    });

    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(this.bodyElement);
    }
  }

  updateAttachClasses(elementAttach, targetAttach) {
    elementAttach = elementAttach || this.attachment;
    targetAttach = targetAttach || this.targetAttachment;
    const sides = ['left', 'top', 'bottom', 'right', 'middle', 'center'];
    const { classes, classPrefix } = this.options;

    if (!isUndefined(this._addAttachClasses) && 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 (isUndefined(this._addAttachClasses)) {
      this._addAttachClasses = [];
    }
    this.add = this._addAttachClasses;

    if (elementAttach.top) {
      this.add.push(
        `${getClass('element-attached', classes, classPrefix)}-${
          elementAttach.top
        }`
      );
    }
    if (elementAttach.left) {
      this.add.push(
        `${getClass('element-attached', classes, classPrefix)}-${
          elementAttach.left
        }`
      );
    }
    if (targetAttach.top) {
      this.add.push(
        `${getClass('target-attached', classes, classPrefix)}-${
          targetAttach.top
        }`
      );
    }
    if (targetAttach.left) {
      this.add.push(
        `${getClass('target-attached', classes, classPrefix)}-${
          targetAttach.left
        }`
      );
    }

    this.all = [];
    sides.forEach((side) => {
      this.all.push(
        `${getClass('element-attached', classes, classPrefix)}-${side}`
      );
      this.all.push(
        `${getClass('target-attached', classes, classPrefix)}-${side}`
      );
    });

    defer(() => {
      if (isUndefined(this._addAttachClasses)) {
        return;
      }

      updateClasses(this.element, this._addAttachClasses, this.all);
      if (!(this.options.addTargetClasses === false)) {
        updateClasses(this.target, this._addAttachClasses, this.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.bodyElement, this.element);
    });

    let { width, height } = elementPos;

    if (width === 0 && height === 0 && !isUndefined(this.lastSize)) {
      // 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 (isUndefined(ret) || !isObject(ret)) {
        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,
        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
      }
    };

    let doc = this.target.ownerDocument;
    let 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 (
      !isUndefined(this.options.optimizations) &&
      this.options.optimizations.moveElement !== false &&
      isUndefined(this.targetModifier)
    ) {
      const offsetParent = this.cache('target-offsetparent', () =>
        getOffsetParent(this.target)
      );
      const offsetPosition = this.cache('target-offsetparent-bounds', () =>
        getBounds(this.bodyElement, offsetParent)
      );
  
Download .txt
gitextract_i2r94h7w/

├── .browserslistrc
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc.js
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── main.yml
│       ├── plan-release.yml
│       └── publish.yml
├── .gitignore
├── .hsdoc
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── .release-plan.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── HISTORY.md
├── LICENSE
├── README.md
├── RELEASE.md
├── __mocks__/
│   └── styleMock.js
├── babel.config.js
├── cypress.config.cjs
├── examples/
│   ├── common/
│   │   └── css/
│   │       └── style.css
│   ├── content-visible/
│   │   └── index.html
│   ├── dolls/
│   │   ├── dolls.css
│   │   ├── dolls.js
│   │   └── index.html
│   ├── element-scroll/
│   │   └── index.html
│   ├── enable-disable/
│   │   └── index.html
│   ├── index.html
│   ├── out-of-bounds/
│   │   └── index.html
│   ├── pin/
│   │   └── index.html
│   ├── resources/
│   │   └── css/
│   │       └── base.css
│   ├── scroll/
│   │   └── index.html
│   ├── simple/
│   │   └── index.html
│   ├── testbed/
│   │   └── index.html
│   └── viewport/
│       ├── colors.css
│       └── index.html
├── jest.config.js
├── package.json
├── rollup.config.js
├── src/
│   ├── css/
│   │   ├── helpers/
│   │   │   ├── _tether-theme-arrows.scss
│   │   │   ├── _tether-theme-basic.scss
│   │   │   └── _tether.scss
│   │   ├── mixins/
│   │   │   ├── _inline-block.scss
│   │   │   └── _pie-clearfix.scss
│   │   ├── tether-theme-arrows-dark.scss
│   │   ├── tether-theme-arrows.scss
│   │   ├── tether-theme-basic.scss
│   │   └── tether.scss
│   └── js/
│       ├── abutment.js
│       ├── constraint.js
│       ├── evented.js
│       ├── shift.js
│       ├── tether.js
│       └── utils/
│           ├── bounds.js
│           ├── classes.js
│           ├── deferred.js
│           ├── general.js
│           ├── offset.js
│           ├── parents.js
│           └── type-check.js
└── test/
    ├── .eslintrc.js
    ├── cypress/
    │   ├── integration/
    │   │   ├── content-visible.cy.js
    │   │   ├── enable-disable.cy.js
    │   │   ├── out-of-bounds.cy.js
    │   │   ├── pin.cy.js
    │   │   ├── scroll.cy.js
    │   │   ├── simple.cy.js
    │   │   └── testbed.cy.js
    │   ├── plugins/
    │   │   └── index.js
    │   └── support/
    │       ├── commands.js
    │       └── index.js
    └── unit/
        ├── constraint.spec.js
        ├── evented.spec.js
        ├── setupTests.js
        ├── shift.spec.js
        ├── tether.spec.js
        └── utils/
            ├── bounds.spec.js
            ├── classes.spec.js
            ├── deferred.spec.js
            ├── offset.spec.js
            └── parents.spec.js
Download .txt
SYMBOL INDEX (68 symbols across 15 files)

FILE: cypress.config.cjs
  method setupNodeEvents (line 9) | setupNodeEvents(on, config) {

FILE: examples/dolls/dolls.js
  function initAnim (line 56) | function initAnim(el){

FILE: rollup.config.js
  function getSassOptions (line 19) | function getSassOptions(minify = false) {

FILE: src/js/abutment.js
  method position (line 6) | position({ top, left }) {

FILE: src/js/constraint.js
  constant BOUNDS_FORMAT (line 7) | const BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom'];
  function getBoundingRect (line 15) | function getBoundingRect(body, tether, to) {
  function _addOutOfBoundsClass (line 69) | function _addOutOfBoundsClass(oob, addClasses, classes, classPrefix, out...
  function _calculateOOBAndPinnedLeft (line 97) | function _calculateOOBAndPinnedLeft(left, bounds, width, pin, pinned, oo...
  function _calculateOOBAndPinnedTop (line 131) | function _calculateOOBAndPinnedTop(top, bounds, height, pin, pinned, oob) {
  function _flipXTogether (line 163) | function _flipXTogether(tAttachment, eAttachment, bounds, width, targetW...
  function _flipYTogether (line 220) | function _flipYTogether(tAttachment, eAttachment, bounds, height, target...
  function _getAllClasses (line 276) | function _getAllClasses(classes, classPrefix, constraints) {
  method position (line 299) | position({ top, left, targetAttachment }) {

FILE: src/js/evented.js
  class Evented (line 3) | class Evented {
    method on (line 4) | on(event, handler, ctx, once = false) {
    method once (line 16) | once(event, handler, ctx) {
    method off (line 20) | off(event, handler) {
    method trigger (line 39) | trigger(event, ...args) {

FILE: src/js/shift.js
  method position (line 4) | position({ top, left }) {

FILE: src/js/tether.js
  function isFullscreenElement (line 35) | function isFullscreenElement(e) {
  function within (line 45) | function within(a, b, diff = 1) {
  function now (line 79) | function now() {
  class TetherClass (line 120) | class TetherClass extends Evented {
    method constructor (line 121) | constructor(options) {
    method setOptions (line 140) | setOptions(options, pos = true) {
    method getTargetBounds (line 209) | getTargetBounds() {
    method clearCache (line 221) | clearCache() {
    method cache (line 225) | cache(k, getter) {
    method enable (line 239) | enable(pos = true) {
    method disable (line 258) | disable() {
    method destroy (line 273) | destroy() {
    method updateAttachClasses (line 296) | updateAttachClasses(elementAttach, targetAttach) {
    method position (line 367) | position(flushChanges = true) {
    method move (line 570) | move(pos) {
    method _addClasses (line 749) | _addClasses() {
    method _removeClasses (line 757) | _removeClasses() {
  method initialize (line 778) | initialize() {
  method destroy (line 796) | destroy() {
  method position (line 811) | position({ manualOffset, manualTargetOffset }) {

FILE: src/js/utils/bounds.js
  function getBounds (line 8) | function getBounds(body, el) {
  function getScrollHandleBounds (line 46) | function getScrollHandleBounds(body, target) {
  function getVisibleBounds (line 111) | function getVisibleBounds(body, target) {
  function removeUtilElements (line 145) | function removeUtilElements(body) {
  function _getActualBoundingClientRect (line 157) | function _getActualBoundingClientRect(node) {
  function _getOrigin (line 185) | function _getOrigin(body) {

FILE: src/js/utils/classes.js
  function addClass (line 3) | function addClass(el, name) {
  function getClass (line 17) | function getClass(key = '', classes, classPrefix) {
  function removeClass (line 30) | function removeClass(el, name) {
  function updateClasses (line 38) | function updateClasses(el, add, all) {

FILE: src/js/utils/deferred.js
  function defer (line 3) | function defer(fn) {
  function flush (line 7) | function flush() {

FILE: src/js/utils/general.js
  function extend (line 3) | function extend(out = {}) {
  function getScrollBarSize (line 21) | function getScrollBarSize() {

FILE: src/js/utils/offset.js
  constant MIRROR_LR (line 3) | const MIRROR_LR = {
  constant MIRROR_TB (line 9) | const MIRROR_TB = {
  constant OFFSET_MAP (line 15) | const OFFSET_MAP = {
  function addOffset (line 24) | function addOffset(...offsets) {
  function attachmentToOffset (line 42) | function attachmentToOffset(attachment) {
  function autoToFixedAttachment (line 56) | function autoToFixedAttachment(attachment, relativeToAttachment) {
  function offsetToPx (line 70) | function offsetToPx(offset, size) {
  function parseTopLeft (line 81) | function parseTopLeft(value) {

FILE: src/js/utils/parents.js
  function getScrollParents (line 3) | function getScrollParents(el) {
  function getOffsetParent (line 46) | function getOffsetParent(el) {

FILE: src/js/utils/type-check.js
  function isFunction (line 5) | function isFunction(value) {
  function isNumber (line 13) | function isNumber(value) {
  function isObject (line 21) | function isObject(value) {
  function isString (line 29) | function isString(value) {
  function isUndefined (line 37) | function isUndefined(value) {
Condensed preview — 84 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (287K chars).
[
  {
    "path": ".browserslistrc",
    "chars": 128,
    "preview": "# Browsers that we support\n\nlast 2 Chrome versions\nlast 2 Firefox versions\nlast 2 Safari versions\nlast 2 Edge versions\ni"
  },
  {
    "path": ".codeclimate.yml",
    "chars": 352,
    "preview": "version: \"2\"\nchecks:\n  argument-count:\n    config:\n      threshold: 10\n  method-count:\n    config:\n      threshold: 25\n "
  },
  {
    "path": ".eslintignore",
    "chars": 36,
    "preview": "/coverage/\n/dist/\n/docs/\n/examples/\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 747,
    "preview": "module.exports = {\n  root: true,\n  parserOptions: {\n    ecmaVersion: 2020,\n    sourceType: 'module'\n  },\n  extends: [\n  "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 203,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: weekly\n    time: \"10:00\"\n  open-"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 1071,
    "preview": "\nname: CI Build\n\non:\n  pull_request: {}\n  push:\n    branches:\n      - master\n    tags:\n      - v*\n\njobs:\n  test:\n    nam"
  },
  {
    "path": ".github/workflows/plan-release.yml",
    "chars": 2019,
    "preview": "name: Plan Release\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - master\n  pull_request_target: # T"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 1065,
    "preview": "# For every push to the primary branch with .release-plan.json modified,\n# runs release-plan.\n\nname: Publish Stable\n\non:"
  },
  {
    "path": ".gitignore",
    "chars": 184,
    "preview": "# Editors\n/.idea/\n/.vscode/\n\n/.log/\n/.nyc_output/\n/coverage/\n/cypress/\n/docs/\n/dist/\n/node_modules/\n/test/unit/dist\n/.DS"
  },
  {
    "path": ".hsdoc",
    "chars": 220,
    "preview": "name: \"Tether\"\ndescription: \"Marrying DOM elements for life\"\ndomain: \"tetherjs.dev\"\nsource: \"src/**/*.js\"\nexamples: \"**/"
  },
  {
    "path": ".npmignore",
    "chars": 304,
    "preview": ".idea/\n.vscode/\n\ncoverage/\ncypress/\ndocs/\nexamples/\nesdoc/\njsdoc-template/\ntest/\ntests/\n\n.codeclimate.yml\n.eslintignore\n"
  },
  {
    "path": ".npmrc",
    "chars": 31,
    "preview": "scripts-prepend-node-path=true\n"
  },
  {
    "path": ".prettierrc.js",
    "chars": 81,
    "preview": "'use strict';\n\nmodule.exports = {\n  singleQuote: true,\n  trailingComma: 'none'\n};"
  },
  {
    "path": ".release-plan.json",
    "chars": 697,
    "preview": "{\n  \"solution\": {\n    \"tether\": {\n      \"impact\": \"patch\",\n      \"oldVersion\": \"3.0.1\",\n      \"newVersion\": \"3.0.2\",\n   "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 21967,
    "preview": "# Changelog\n\n## Release (2025-12-07)\n\n* tether 3.0.2 (patch)\n\n#### :bug: Bug Fix\n* `tether`\n  * [#1707](https://github.c"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2107,
    "preview": "# Contributing Guide\n\nYou will need:\n\n- [pnpm](https://pnpm.io/)\n\nWindows users will need additional setup to enable bui"
  },
  {
    "path": "HISTORY.md",
    "chars": 283,
    "preview": "## v1.3.0\n- Tether instances now fire an 'update' event when attachments change due to constraints (#119)\n\n## v1.0.1\n- U"
  },
  {
    "path": "LICENSE",
    "chars": 1113,
    "preview": "Copyright (c) 2014-2019 HubSpot, Inc.\nCopyright (c) 2019-2022 Ship Shape Consulting LLC\n\nPermission is hereby granted, f"
  },
  {
    "path": "README.md",
    "chars": 6634,
    "preview": "# Tether\n\n<div>\n  <a href=\"https://shipshape.io\">\n    <img align=\"left\" src=\"http://i.imgur.com/DWHQjA5.png\" alt=\"Ship S"
  },
  {
    "path": "RELEASE.md",
    "chars": 1608,
    "preview": "# Release Process\n\nReleases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/re"
  },
  {
    "path": "__mocks__/styleMock.js",
    "chars": 20,
    "preview": "module.exports = {};"
  },
  {
    "path": "babel.config.js",
    "chars": 475,
    "preview": "module.exports = function(api) {\n  api.cache(true);\n\n  return {\n    env: {\n      development: {\n        presets: [\n     "
  },
  {
    "path": "cypress.config.cjs",
    "chars": 535,
    "preview": "const { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n  fixturesFolder: 'test/cypress/fixtures',\n"
  },
  {
    "path": "examples/common/css/style.css",
    "chars": 675,
    "preview": "body {\n    min-height: 3000px;\n}\n.element {\n    width: 200px;\n    height: 200px;\n    background-color: #fe8;\n    positio"
  },
  {
    "path": "examples/content-visible/index.html",
    "chars": 1415,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <link rel=\"stylesheet\" href=\"../resources/css/base.css\" />\n    </head>\n    <bo"
  },
  {
    "path": "examples/dolls/dolls.css",
    "chars": 275,
    "preview": ".tether-element, .tether-target {\n  width: 200px;\n  height: 50px;\n  background-color: #4cc;\n  position: absolute;\n}\nbody"
  },
  {
    "path": "examples/dolls/dolls.js",
    "chars": 1688,
    "preview": "var tethers = [];\n\ndocument.addEventListener('DOMContentLoaded', function(){\n  dragging = null;\n\n  document.body.addEven"
  },
  {
    "path": "examples/dolls/index.html",
    "chars": 175,
    "preview": "<link rel=\"stylesheet\" href=\"./dolls.css\" />\n<script src=\"../../dist/js/tether.js\"></script>\n<script src=\"./dolls.js\"></"
  },
  {
    "path": "examples/element-scroll/index.html",
    "chars": 29996,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n      <link rel=\"stylesheet\" href=\"../resources/css/base.css\" />\n    </head>\n    <body"
  },
  {
    "path": "examples/enable-disable/index.html",
    "chars": 1226,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"chr"
  },
  {
    "path": "examples/index.html",
    "chars": 1913,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "examples/out-of-bounds/index.html",
    "chars": 1249,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"chr"
  },
  {
    "path": "examples/pin/index.html",
    "chars": 1015,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"chr"
  },
  {
    "path": "examples/resources/css/base.css",
    "chars": 134,
    "preview": "body {\n    font-family: \"Helvetica Neue\", sans-serif;\n    color: #444;\n    margin: 0px;\n}\n\na {\n    cursor: pointer;\n    "
  },
  {
    "path": "examples/scroll/index.html",
    "chars": 58972,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <link rel=\"stylesheet\" href=\"../resources/css/base.css\" />\n        <link rel=\""
  },
  {
    "path": "examples/simple/index.html",
    "chars": 970,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"chr"
  },
  {
    "path": "examples/testbed/index.html",
    "chars": 1069,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"chr"
  },
  {
    "path": "examples/viewport/colors.css",
    "chars": 2044,
    "preview": "@charset \"UTF-8\";\n/****\n\n   colors.css v1.0 For a friendlier looking web\n   MIT License • http://clrs.cc • http://github"
  },
  {
    "path": "examples/viewport/index.html",
    "chars": 1757,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"../resources/css/base.css\" />\n    <link rel=\"stylesheet"
  },
  {
    "path": "jest.config.js",
    "chars": 1173,
    "preview": "// For a detailed explanation regarding each configuration property, visit:\n// https://jestjs.io/docs/en/configuration.h"
  },
  {
    "path": "package.json",
    "chars": 2832,
    "preview": "{\n  \"name\": \"tether\",\n  \"version\": \"3.0.2\",\n  \"description\": \"A client-side library to make absolutely positioned elemen"
  },
  {
    "path": "rollup.config.js",
    "chars": 2865,
    "preview": "import autoprefixer from 'autoprefixer';\nimport babel from 'rollup-plugin-babel';\nimport browsersync from 'rollup-plugin"
  },
  {
    "path": "src/css/helpers/_tether-theme-arrows.scss",
    "chars": 6596,
    "preview": "@mixin tether-theme-arrows($themePrefix: \"tether\", $themeName: \"arrows\", $arrowSize: 16px, $arrowPointerEvents: null, $b"
  },
  {
    "path": "src/css/helpers/_tether-theme-basic.scss",
    "chars": 495,
    "preview": "@mixin tether-theme-basic($themePrefix: \"tether\", $themeName: \"basic\", $backgroundColor: #fff, $color: inherit) {\n  .#{$"
  },
  {
    "path": "src/css/helpers/_tether.scss",
    "chars": 301,
    "preview": "@mixin tether($themePrefix: \"tether\") {\n  .#{$themePrefix}-element, .#{$themePrefix}-element * {\n    &, &:after, &:befor"
  },
  {
    "path": "src/css/mixins/_inline-block.scss",
    "chars": 132,
    "preview": "@mixin inline-block {\n  display: inline-block;\n  vertical-align: middle;\n  *vertical-align: auto;\n  *zoom: 1;\n  *display"
  },
  {
    "path": "src/css/mixins/_pie-clearfix.scss",
    "chars": 107,
    "preview": "@mixin pie-clearfix {\n  *zoom: 1;\n\n  &:after {\n    content: \"\";\n    display: table;\n    clear: both;\n  }\n}\n"
  },
  {
    "path": "src/css/tether-theme-arrows-dark.scss",
    "chars": 432,
    "preview": "@import \"helpers/tether\";\n@import \"helpers/tether-theme-arrows\";\n\n$themePrefix: \"tether\";\n$themeName: \"arrows-dark\";\n$ar"
  },
  {
    "path": "src/css/tether-theme-arrows.scss",
    "chars": 429,
    "preview": "@import \"helpers/tether\";\n@import \"helpers/tether-theme-arrows\";\n\n$themePrefix: \"tether\";\n$themeName: \"arrows\";\n$arrowSi"
  },
  {
    "path": "src/css/tether-theme-basic.scss",
    "chars": 330,
    "preview": "@import \"helpers/tether\";\n@import \"helpers/tether-theme-basic\";\n\n$themePrefix: \"tether\";\n$themeName: \"basic\";\n$backgroun"
  },
  {
    "path": "src/css/tether.scss",
    "chars": 97,
    "preview": "@import \"helpers/tether\";\n\n$themePrefix: \"tether\";\n\n@include tether($themePrefix: $themePrefix);\n"
  },
  {
    "path": "src/js/abutment.js",
    "chars": 1716,
    "preview": "import { getClass, updateClasses } from './utils/classes';\nimport { defer } from './utils/deferred';\nimport { getBounds "
  },
  {
    "path": "src/js/constraint.js",
    "chars": 13040,
    "preview": "import { getClass, updateClasses } from './utils/classes';\nimport { defer } from './utils/deferred';\nimport { extend } f"
  },
  {
    "path": "src/js/evented.js",
    "chars": 1232,
    "preview": "import { isUndefined } from './utils/type-check';\n\nexport class Evented {\n  on(event, handler, ctx, once = false) {\n    "
  },
  {
    "path": "src/js/shift.js",
    "chars": 685,
    "preview": "import { isFunction, isString } from './utils/type-check';\n\nexport default {\n  position({ top, left }) {\n    if (!this.o"
  },
  {
    "path": "src/js/tether.js",
    "chars": 22903,
    "preview": "import '../css/tether.scss';\nimport '../css/tether-theme-arrows.scss';\nimport '../css/tether-theme-arrows-dark.scss';\nim"
  },
  {
    "path": "src/js/utils/bounds.js",
    "chars": 6038,
    "preview": "import { defer } from './deferred';\nimport { extend, uniqueId } from './general';\nimport { isUndefined } from './type-ch"
  },
  {
    "path": "src/js/utils/classes.js",
    "chars": 1210,
    "preview": "import { isUndefined } from './type-check';\n\nexport function addClass(el, name) {\n  name.split(' ').forEach((cls) => {\n "
  },
  {
    "path": "src/js/utils/deferred.js",
    "chars": 188,
    "preview": "const deferred = [];\n\nexport function defer(fn) {\n  deferred.push(fn);\n}\n\nexport function flush() {\n  let fn;\n  // eslin"
  },
  {
    "path": "src/js/utils/general.js",
    "chars": 1283,
    "preview": "let _scrollBarSize = null;\n\nexport function extend(out = {}) {\n  const args = [];\n\n  Array.prototype.push.apply(args, ar"
  },
  {
    "path": "src/js/utils/offset.js",
    "chars": 1648,
    "preview": "import { isString, isUndefined } from './type-check';\n\nconst MIRROR_LR = {\n  center: 'center',\n  left: 'right',\n  right:"
  },
  {
    "path": "src/js/utils/parents.js",
    "chars": 1335,
    "preview": "import { isUndefined } from './type-check';\n\nexport function getScrollParents(el) {\n  // In firefox if the el is inside "
  },
  {
    "path": "src/js/utils/type-check.js",
    "chars": 953,
    "preview": "/**\n * Checks if `value` is classified as a `Function` object.\n * @param {*} value The param to check if it is a functio"
  },
  {
    "path": "test/.eslintrc.js",
    "chars": 596,
    "preview": "module.exports = {\n  root: true,\n  parserOptions: {\n    ecmaVersion: 2019,\n    sourceType: 'module'\n  },\n  plugins: [\n  "
  },
  {
    "path": "test/cypress/integration/content-visible.cy.js",
    "chars": 715,
    "preview": "describe('content-visible', () => {\n  beforeEach(() => {\n    cy.visit('/examples/content-visible/');\n    cy.scrollTo(0, "
  },
  {
    "path": "test/cypress/integration/enable-disable.cy.js",
    "chars": 1349,
    "preview": "describe('enable-disable', () => {\n  beforeEach(() => {\n    cy.visit('/examples/enable-disable/');\n    cy.get('.containe"
  },
  {
    "path": "test/cypress/integration/out-of-bounds.cy.js",
    "chars": 589,
    "preview": "describe('out of bounds', () => {\n  beforeEach(() => {\n    cy.visit('/examples/out-of-bounds/');\n  });\n\n  describe('teth"
  },
  {
    "path": "test/cypress/integration/pin.cy.js",
    "chars": 1288,
    "preview": "describe('pin', () => {\n  beforeEach(() => {\n    cy.visit('/examples/pin/');\n  });\n\n  describe('tether element pinned', "
  },
  {
    "path": "test/cypress/integration/scroll.cy.js",
    "chars": 1033,
    "preview": "describe('Scroll', () => {\n  beforeEach(() => {\n    cy.visit('/examples/scroll/', {});\n  });\n\n  describe('tether stays n"
  },
  {
    "path": "test/cypress/integration/simple.cy.js",
    "chars": 772,
    "preview": "describe('simple', () => {\n  // let Tether;\n\n  beforeEach(() => {\n    // Tether = null;\n\n    cy.visit('/examples/simple/"
  },
  {
    "path": "test/cypress/integration/testbed.cy.js",
    "chars": 593,
    "preview": "describe('testbed', () => {\n  beforeEach(() => {\n    cy.visit('/examples/testbed/');\n  });\n\n  describe('flip works', () "
  },
  {
    "path": "test/cypress/plugins/index.js",
    "chars": 546,
    "preview": "// ***********************************************************\n// This example plugins/index.js can be used to load plug"
  },
  {
    "path": "test/cypress/support/commands.js",
    "chars": 841,
    "preview": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom"
  },
  {
    "path": "test/cypress/support/index.js",
    "chars": 671,
    "preview": "// ***********************************************************\n// This example support/index.js is processed and\n// load"
  },
  {
    "path": "test/unit/constraint.spec.js",
    "chars": 17021,
    "preview": "import Constraint from '../../src/js/constraint.js';\n\ndescribe('Constraint', () => {\n  describe('getBoundingRect()', () "
  },
  {
    "path": "test/unit/evented.spec.js",
    "chars": 2312,
    "preview": "import { Evented } from '../../src/js/evented.js';\nimport { spy } from 'sinon';\n\ndescribe('Evented', () => {\n  let testE"
  },
  {
    "path": "test/unit/setupTests.js",
    "chars": 223,
    "preview": "import 'regenerator-runtime/runtime';\nimport 'jest-expect-message';\nimport '@testing-library/jest-dom/extend-expect';\nim"
  },
  {
    "path": "test/unit/shift.spec.js",
    "chars": 2439,
    "preview": "import shift from '../../src/js/shift';\n\ndescribe('Shift', () => {\n  describe('position()', () => {\n    it('returns unde"
  },
  {
    "path": "test/unit/tether.spec.js",
    "chars": 9100,
    "preview": "import Tether from '../../src/js/tether.js';\n\ndescribe('Tether', () => {\n  let element, target;\n\n  beforeEach(() => {\n  "
  },
  {
    "path": "test/unit/utils/bounds.spec.js",
    "chars": 7106,
    "preview": "import { getBounds, getScrollHandleBounds, getVisibleBounds, removeUtilElements } from '../../../src/js/utils/bounds';\n\n"
  },
  {
    "path": "test/unit/utils/classes.spec.js",
    "chars": 2336,
    "preview": "import { addClass, getClass, removeClass } from '../../../src/js/utils/classes';\n\ndescribe('Utils - classes', () => {\n  "
  },
  {
    "path": "test/unit/utils/deferred.spec.js",
    "chars": 516,
    "preview": "import { defer, flush } from '../../../src/js/utils/deferred';\nimport { stub } from 'sinon';\n\ndescribe('Utils - deferred"
  },
  {
    "path": "test/unit/utils/offset.spec.js",
    "chars": 2657,
    "preview": "import { addOffset, attachmentToOffset, autoToFixedAttachment, offsetToPx, parseTopLeft } from '../../../src/js/utils/of"
  },
  {
    "path": "test/unit/utils/parents.spec.js",
    "chars": 7397,
    "preview": "import { getScrollParents, getOffsetParent } from '../../../src/js/utils/parents';\n\ndescribe('Utils - parents', () => {\n"
  }
]

About this extraction

This page contains the full source code of the shipshapecode/tether GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 84 files (268.1 KB), approximately 71.1k tokens, and a symbol index with 68 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!