├── .gitignore
├── README.md
├── index.js
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # poppy
2 | A tiny, fast, configurable popover in 1.6kb. 🍻
3 |
4 | # Usage
5 | ```javascript
6 | import Poppy from 'poppy'
7 |
8 | const target = document.getElementById('target')
9 |
10 | const pop = new Poppy({
11 | target: target,
12 | popover: `
13 |
14 |
I'm a popover!
15 |
16 | `,
17 | position: 'left', // from tackjs
18 | transitionSpeed: 200, // for css transitions
19 | onChange: ({ pinned }) => {...} // boolean
20 | })
21 |
22 | target.addEventListener('mouseenter', pop.pin)
23 | target.addEventListener('mouseleave', pop.unpin)
24 | ```
25 |
26 | Required CSS:
27 | ```css
28 | .poppy {
29 | position: absolute;
30 | z-index: 9999;
31 | top: 0; left: 0;
32 | }
33 | ```
34 |
35 | # The Name
36 | Huge thanks to [swathysubhash](https://github.com/swathysubhash) for letting me
37 | use the name! This library used to be called [micro-popover](https://github.com/estrattonbailey/micro-popover).
38 |
39 | ## License
40 | MIT License (c) 2018 Eric Bailey
41 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import tack from 'tackjs'
2 | import { tarry, queue } from 'tarry.js'
3 |
4 | export default class Popover {
5 | constructor ({
6 | target,
7 | popover = null,
8 | position = 'bottom',
9 | transitionSpeed = 0,
10 | onChange = null
11 | }) {
12 | this.target = target
13 | this.popover = this.createPopover(popover)
14 | this.position = position
15 | this.transitionSpeed = transitionSpeed
16 | this.onChange = onChange
17 |
18 | this.state = {
19 | pinned: false,
20 | busy: false,
21 | requestClose: false
22 | }
23 |
24 | this.pin = this.pin.bind(this)
25 | this.unpin = this.unpin.bind(this)
26 | this.block = this.block.bind(this)
27 | this.unblock = this.unblock.bind(this)
28 | this.isExternalClick = this.isExternalClick.bind(this)
29 | this.handleKeyup = this.handleKeyup.bind(this)
30 |
31 | this.focusNode = null
32 | }
33 |
34 | setState (state, cb) {
35 | this.state = Object.assign(
36 | this.state,
37 | state
38 | )
39 |
40 | cb && tarry(cb, 0)()
41 | }
42 |
43 | block () {
44 | this.setState({
45 | busy: true
46 | })
47 | }
48 |
49 | unblock () {
50 | this.setState({
51 | busy: false
52 | }, () => {
53 | this.state.requestClose && this.unpin()
54 | })
55 | }
56 |
57 | toggle () {
58 | this.state.pinned ? this.unpin() : this.pin()
59 | }
60 |
61 | pin () {
62 | if (this.state.busy || this.state.pinned) return
63 |
64 | this.setState({
65 | busy: true
66 | })
67 |
68 | this.focusNode = document.activeElement
69 |
70 | const render = tarry(() => document.body.appendChild(this.popover))
71 | const pin = tarry(() => tack(this.popover, this.target, this.position))
72 | const show = tarry(() => {
73 | this.popover.classList.add('is-visible')
74 | this.popover.setAttribute('tabindex', '0')
75 | this.popover.setAttribute('aria-hidden', 'false')
76 | })
77 | const focus = tarry(() => this.popover.focus())
78 | const done = tarry(() => this.setState({
79 | busy: false,
80 | pinned: true
81 | }))
82 |
83 | queue(render, pin, show(0), focus(0), done)()
84 |
85 | this.popover.addEventListener('mouseenter', this.block)
86 | this.popover.addEventListener('mouseleave', this.unblock)
87 | window.addEventListener('click', this.isExternalClick)
88 | window.addEventListener('touchstart', this.isExternalClick)
89 | window.addEventListener('keyup', this.handleKeyup)
90 | window.addEventListener('resize', this.unpin)
91 |
92 | this.onChange && this.onChange({
93 | pinned: true
94 | })
95 | }
96 |
97 | unpin (force) {
98 | this.setState({
99 | requestClose: true
100 | })
101 |
102 | if (!force && (this.state.busy || !this.state.pinned)) return
103 |
104 | tarry(() => {
105 | this.setState({
106 | busy: true
107 | })
108 |
109 | this.popover.removeEventListener('mouseenter', this.block)
110 | this.popover.removeEventListener('mouseleave', this.unblock)
111 | window.removeEventListener('click', this.isExternalClick)
112 | window.removeEventListener('touchstart', this.isExternalClick)
113 | window.removeEventListener('keyup', this.handleKeyup)
114 | window.removeEventListener('resize', this.unpin)
115 |
116 | const hide = tarry(() => this.popover.classList.add('is-hiding'))
117 | const remove = tarry(() => document.body.removeChild(this.popover))
118 | const blur = tarry(() => this.focusNode.focus())
119 | const done = tarry(() => {
120 | this.popover.classList.remove('is-hiding')
121 | this.popover.classList.remove('is-visible')
122 | this.setState({
123 | busy: false,
124 | pinned: false,
125 | requestClose: false
126 | })
127 | })
128 |
129 | queue(hide, remove(this.transitionSpeed), blur, done)()
130 |
131 | this.onChange && this.onChange({
132 | pinned: false
133 | })
134 | }, 0)()
135 | }
136 |
137 | handleKeyup (e) {
138 | e.keyCode === 27 && this.unpin()
139 | }
140 |
141 | isExternalClick (e) {
142 | if (
143 | (e.target !== this.popover && !this.popover.contains(e.target)) &&
144 | (e.target !== this.target && !this.target.contains(e.target))
145 | ) {
146 | this.unpin()
147 | }
148 | }
149 |
150 | createPopover (pop) {
151 | const popover = document.createElement('div')
152 | popover.className = 'poppy'
153 |
154 | popover.role = 'dialog'
155 | popover.setAttribute('aria-label', 'Share Dialog')
156 | popover.setAttribute('aria-hidden', 'true')
157 |
158 | typeof pop === 'string' ? popover.innerHTML = pop : popover.appendChild(pop)
159 |
160 | return popover
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "poppy",
3 | "version": "1.0.3",
4 | "description": "A tiny, fast, configurable popover.",
5 | "source": "index.js",
6 | "module": "dist/poppy.es.js",
7 | "jsnext:main": "dist/poppy.es.js",
8 | "main": "dist/poppy.js",
9 | "umd:main": "dist/poppy.umd.js",
10 | "files": [
11 | "dist"
12 | ],
13 | "scripts": {
14 | "build": "microbundle build --external none",
15 | "watch": "microbundle watch",
16 | "publish": "npm publish"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+ssh://git@github.com/estrattonbailey/poppy.git"
21 | },
22 | "author": "estrattonbailey",
23 | "keywords": [
24 | "tooltip",
25 | "popover",
26 | "popovers"
27 | ],
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/estrattonbailey/poppy/issues"
31 | },
32 | "homepage": "https://github.com/estrattonbailey/poppy#readme",
33 | "devDependencies": {
34 | "microbundle": "^0.11.0"
35 | },
36 | "dependencies": {
37 | "tackjs": "^1.1.2",
38 | "tarry.js": "^0.3.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------