├── .gitignore
├── CHANGELOG.md
├── README.md
├── example
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── build
│ ├── .gitignore
│ ├── build
│ │ └── images
│ │ │ └── .keep
│ ├── images
│ │ ├── .keep
│ │ └── GitHub-Mark-64px.png
│ ├── index.html
│ ├── javascripts
│ │ └── vendor
│ │ │ └── floatl.umd.js
│ ├── source
│ │ └── images
│ │ │ └── .keep
│ └── stylesheets
│ │ ├── floatl.css
│ │ ├── site.css
│ │ └── vendor
│ │ └── normalize.css
├── config.rb
└── source
│ ├── images
│ ├── .keep
│ └── GitHub-Mark-64px.png
│ ├── index.html.erb
│ ├── javascripts
│ └── vendor
│ │ └── floatl.umd.js
│ ├── layouts
│ └── layout.erb
│ └── stylesheets
│ ├── floatl.css.scss
│ ├── site.css.scss
│ └── vendor
│ └── normalize.css
├── karma.conf.js
├── package.json
├── rollup.config.js
├── spec
├── main.spec.ts
├── specHelpers.ts
└── utils.spec.ts
├── src
├── main.ts
└── utils.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sass-cache
3 | .rpt2_cache
4 |
5 | /node_modules
6 | /built
7 | /coverage
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file going forward.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [2.0.1] - 2018-02-14
11 | ### Fixed
12 | - Add `npm prepare` command to build the files
13 |
14 | ## [2.0.0] - 2018-02-14
15 | ### Added
16 | - `Floatl#destroy` to remove event listeners
17 | - SauceLabs setup
18 | - Example site
19 |
20 | ### Changed
21 | - Change changelog format to [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
22 | - Rewritten in TypeScript
23 | - Properly setup CodeClimate
24 | - Use Semaphore for CI
25 | - Refactor tests to remove jquery and jasmine-jquery devdependencies
26 | - Moved some documentation from README to GitHub wiki
27 |
28 | ### Removed
29 | - Bower support
30 | - Support for passing in jQuery elements
31 | - Included CSS styling
32 |
33 | ## [1.0.5] - 2017-02-27
34 | ### Fixed
35 | - Forgot to release built files (again!)
36 |
37 | ## [1.0.4] - 2017-02-27
38 | ### Changed
39 | - Handle state change on input event (thanks @zlatevbg!)
40 |
41 | ## [1.0.3] - 2016-10-19
42 | ### Changed
43 | - Stop initialisation if required either input or label is not found (thanks @leptest!)
44 |
45 | ## [1.0.2] - 2016-10-05
46 | ### Fixed
47 | - Actually release built files, whoops!
48 |
49 | ## [1.0.1] - 2016-10-05
50 | ### Changed
51 | - Properly assign regex variable in `removeClass` function, fix #16 (thanks @martinothamar!)
52 |
53 | ## [1.0.0] - 2016-07-13
54 | ### Added
55 | - Start using changelog
56 | - Include globally built floatl.global.js
57 |
58 | ### Changed
59 | - Change default Webpack output to commonjs2 module
60 | - Remove Gulp in favour of NPM scripts
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ☁️ Floatl
2 |
3 | A pragmatic implementation of the [Float Label Pattern](https://dribbble.com/shots/1254439--GIF-Mobile-Form-Interaction) for the web.
4 |
5 | [](https://www.npmjs.com/package/floatl)
6 | [](https://semaphoreci.com/richardvenneman/floatl)
7 | [](https://codeclimate.com/github/richardvenneman/floatl/maintainability)
8 | [](https://codeclimate.com/github/richardvenneman/floatl/test_coverage)
9 |
10 | 
11 |
12 | Try it for yourself: https://richardvenneman.github.io/floatl/
13 |
14 | ## Features
15 |
16 | 🤙 Supports textfields and textareas
17 |
18 | 🏝 Dependency-free and maintained
19 |
20 | ⚡️ Small size, < 1Kb gzipped
21 |
22 |
23 | 🤣 Oldie browser support
24 |
25 |
26 |
27 |
28 | _* Supports IE8 & IE9 as well, but I couldn't get them to run on SauceLabs_ 😰
29 |
30 |
31 | ## Installation
32 |
33 | Floatl is built primarily for module bundlers such as [Browserify](http://browserify.org) and [webpack](http://webpack.github.io).
34 | As such it is distributed via [NPM](https://www.npmjs.com/package/floatl).
35 |
36 | ```bash
37 | yarn add floatl # or npm i -S floatl
38 | ```
39 |
40 | _An UMD build is available from the [GitHub releases page](https://github.com/richardvenneman/floatl/releases) if you're not using a module bundler. This version adds `Floatl` to the global namespace._
41 |
42 | ### Styling
43 |
44 | Add the required styling to your app. Please refer to the [GitHub wiki page on styling](https://github.com/richardvenneman/floatl/wiki/Styling) for more information.
45 |
46 | ## Usage
47 |
48 | *NOTE: Check the [GitHub wiki page](https://github.com/richardvenneman/floatl/wiki) for instructions on how to use with React, AngularJS and Stimulus.*
49 |
50 | Markup your `label` and `input` (or `textarea`) with the floatl classes and wrap them in an element with the `floatl` class:
51 |
52 | ```html
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | Instantiate Floatl by passing in the wrapping DOM element:
60 |
61 | ```javascript
62 | import Floatl from "floatl";
63 |
64 | var element = document.getElementById("my-floatl-element");
65 | new Floatl(element);
66 | ```
67 |
68 | ### Destroying instances
69 |
70 | If you keep a reference to your Floatl instance, it's easy to remove all of the event handlers by calling the `destroy` instance method:
71 |
72 | ```javascript
73 | var myFloatl = new Floatl(element);
74 | myFloatl.destroy()
75 | ```
76 |
77 | ## Placeholder polyfilling
78 |
79 | While the JavaScript supports IE8+, Floatl aims to be good at Floating Labels and only that. The Floating Labels Pattern works best with placeholders and it is therefor recommended to install legacy browser placeholder support should you need it, for example [Placekeeper](https://github.com/kristerkari/placekeeper) or [Placeholders.js](https://github.com/jamesallardice/Placeholders.js).
80 |
81 | ## Motivations
82 |
83 | There are several libraries available that implement the Float Label Pattern, most notably [floatlabels.js](https://github.com/clubdesign/floatlabels.js) and [FloatLabel.js](https://github.com/m10l/FloatLabel.js). However, these libraries did not quite fulfill the requisites I had in mind (see features above) and I did not find any Bower compatible libraries when I started this project. Furthermore I like to use a well-maintained library. Since we're using this library in production at [CitySpotters](https://www.cityspotters.com) I'm keeping this library up to date.
84 |
85 | ## Contributing
86 |
87 | Everyone is encouraged to help improve this project. Here are a few ways you can help:
88 |
89 | * Report bugs
90 | * Fix bugs and submit pull requests
91 | * Write, clarify, or fix documentation
92 | * Suggest or add new features
93 | * Write missing tests
94 | * Improve the TypeScript implementation
95 |
96 | ## Development & testing
97 |
98 | This project uses [Jasmine](http://jasmine.github.io) with the [Karma Test Runner](http://karma-runner.github.io/).
99 |
100 | * Install dependencies with `yarn install`
101 | * Run the test suite with: `yarn test` (or `yarn run tdd` for Test Driven Development)
102 |
103 | ## License
104 |
105 | This library is released under the [MIT License](http://www.opensource.org/licenses/MIT).
106 |
--------------------------------------------------------------------------------
/example/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'middleman', '~> 4.2'
4 | gem 'middleman-autoprefixer', '~> 2.7'
5 | gem 'middleman-livereload', '~> 3.4'
6 | gem 'middleman-gh-pages', '~> 0.3'
7 |
8 | gem 'tzinfo-data', platforms: [:mswin, :mingw, :jruby]
9 | gem 'wdm', '~> 0.1', platforms: [:mswin, :mingw]
10 |
--------------------------------------------------------------------------------
/example/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | activesupport (5.0.6)
5 | concurrent-ruby (~> 1.0, >= 1.0.2)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | tzinfo (~> 1.1)
9 | addressable (2.5.2)
10 | public_suffix (>= 2.0.2, < 4.0)
11 | autoprefixer-rails (7.2.6)
12 | execjs
13 | backports (3.11.1)
14 | coffee-script (2.4.1)
15 | coffee-script-source
16 | execjs
17 | coffee-script-source (1.12.2)
18 | compass-import-once (1.0.5)
19 | sass (>= 3.2, < 3.5)
20 | concurrent-ruby (1.0.5)
21 | contracts (0.13.0)
22 | dotenv (2.2.1)
23 | em-websocket (0.5.1)
24 | eventmachine (>= 0.12.9)
25 | http_parser.rb (~> 0.6.0)
26 | erubis (2.7.0)
27 | eventmachine (1.2.5)
28 | execjs (2.7.0)
29 | fast_blank (1.0.0)
30 | fastimage (2.1.1)
31 | ffi (1.9.21)
32 | haml (5.0.4)
33 | temple (>= 0.8.0)
34 | tilt
35 | hamster (3.0.0)
36 | concurrent-ruby (~> 1.0)
37 | hashie (3.5.7)
38 | http_parser.rb (0.6.0)
39 | i18n (0.7.0)
40 | kramdown (1.16.2)
41 | listen (3.0.8)
42 | rb-fsevent (~> 0.9, >= 0.9.4)
43 | rb-inotify (~> 0.9, >= 0.9.7)
44 | memoist (0.16.0)
45 | middleman (4.2.1)
46 | coffee-script (~> 2.2)
47 | compass-import-once (= 1.0.5)
48 | haml (>= 4.0.5)
49 | kramdown (~> 1.2)
50 | middleman-cli (= 4.2.1)
51 | middleman-core (= 4.2.1)
52 | sass (>= 3.4.0, < 4.0)
53 | middleman-autoprefixer (2.8.0)
54 | autoprefixer-rails (>= 7.0.1, < 8.0.0)
55 | middleman-core (>= 3.3.3)
56 | middleman-cli (4.2.1)
57 | thor (>= 0.17.0, < 2.0)
58 | middleman-core (4.2.1)
59 | activesupport (>= 4.2, < 5.1)
60 | addressable (~> 2.3)
61 | backports (~> 3.6)
62 | bundler (~> 1.1)
63 | contracts (~> 0.13.0)
64 | dotenv
65 | erubis
66 | execjs (~> 2.0)
67 | fast_blank
68 | fastimage (~> 2.0)
69 | hamster (~> 3.0)
70 | hashie (~> 3.4)
71 | i18n (~> 0.7.0)
72 | listen (~> 3.0.0)
73 | memoist (~> 0.14)
74 | padrino-helpers (~> 0.13.0)
75 | parallel
76 | rack (>= 1.4.5, < 3)
77 | sass (>= 3.4)
78 | servolux
79 | tilt (~> 2.0)
80 | uglifier (~> 3.0)
81 | middleman-gh-pages (0.3.1)
82 | rake (> 0.9.3)
83 | middleman-livereload (3.4.6)
84 | em-websocket (~> 0.5.1)
85 | middleman-core (>= 3.3)
86 | rack-livereload (~> 0.3.15)
87 | minitest (5.11.3)
88 | padrino-helpers (0.13.3.4)
89 | i18n (~> 0.6, >= 0.6.7)
90 | padrino-support (= 0.13.3.4)
91 | tilt (>= 1.4.1, < 3)
92 | padrino-support (0.13.3.4)
93 | activesupport (>= 3.1)
94 | parallel (1.12.1)
95 | public_suffix (3.0.2)
96 | rack (2.0.4)
97 | rack-livereload (0.3.16)
98 | rack
99 | rake (12.3.0)
100 | rb-fsevent (0.10.2)
101 | rb-inotify (0.9.10)
102 | ffi (>= 0.5.0, < 2)
103 | sass (3.4.25)
104 | servolux (0.13.0)
105 | temple (0.8.0)
106 | thor (0.20.0)
107 | thread_safe (0.3.6)
108 | tilt (2.0.8)
109 | tzinfo (1.2.5)
110 | thread_safe (~> 0.1)
111 | uglifier (3.2.0)
112 | execjs (>= 0.3.0, < 3)
113 |
114 | PLATFORMS
115 | ruby
116 |
117 | DEPENDENCIES
118 | middleman (~> 4.2)
119 | middleman-autoprefixer (~> 2.7)
120 | middleman-gh-pages (~> 0.3)
121 | middleman-livereload (~> 3.4)
122 | tzinfo-data
123 | wdm (~> 0.1)
124 |
125 | BUNDLED WITH
126 | 1.16.0
127 |
--------------------------------------------------------------------------------
/example/Rakefile:
--------------------------------------------------------------------------------
1 | require 'middleman-gh-pages'
2 |
3 | ENV['PROJECT_ROOT'] = '/Users/richard/Projects/floatl/example'
4 | ENV['COMMIT_MESSAGE_SUFFIX'] = '[skip ci]'
5 |
--------------------------------------------------------------------------------
/example/build/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | .sass-cache/
4 |
5 |
--------------------------------------------------------------------------------
/example/build/build/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/build/build/images/.keep
--------------------------------------------------------------------------------
/example/build/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/build/images/.keep
--------------------------------------------------------------------------------
/example/build/images/GitHub-Mark-64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/build/images/GitHub-Mark-64px.png
--------------------------------------------------------------------------------
/example/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 | Floatl
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/example/build/javascripts/vendor/floatl.umd.js:
--------------------------------------------------------------------------------
1 | /* floatl version 1.0.5 */
2 | (function (global, factory) {
3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4 | typeof define === 'function' && define.amd ? define(factory) :
5 | (global.Floatl = factory());
6 | }(this, (function () { 'use strict';
7 |
8 | function addClass(element, className) {
9 | if (element.classList) {
10 | element.classList.add(className);
11 | }
12 | else {
13 | element.className += " " + className;
14 | }
15 | }
16 | function removeClass(element, className) {
17 | if (element.classList) {
18 | element.classList.remove(className);
19 | }
20 | else {
21 | var re = new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi");
22 | element.className = element.className.replace(re, " ");
23 | }
24 | }
25 | function addEventListener(element, event, cb) {
26 | if (element.addEventListener) {
27 | element.addEventListener(event, cb);
28 | }
29 | else {
30 | element.attachEvent("on" + event, function () {
31 | cb.call(element);
32 | });
33 | }
34 | }
35 | function removeEventListener(element, event, cb) {
36 | if (element.removeEventListener) {
37 | element.removeEventListener(event, cb);
38 | }
39 | else {
40 | element.detachEvent("on" + event, cb);
41 | }
42 | }
43 |
44 | var Floatl = /** @class */ (function () {
45 | function Floatl(element) {
46 | var _this = this;
47 | this.handleChange = function () {
48 | if (_this.input.value === "") {
49 | removeClass(_this.element, Floatl.ACTIVE_CLASS);
50 | }
51 | else {
52 | addClass(_this.element, Floatl.ACTIVE_CLASS);
53 | }
54 | };
55 | this.addFocusedClass = function () {
56 | addClass(_this.element, Floatl.FOCUSED_CLASS);
57 | };
58 | this.removeFocusedClass = function () {
59 | removeClass(_this.element, Floatl.FOCUSED_CLASS);
60 | };
61 | this.element = element;
62 | this.label = element.querySelectorAll(".floatl__label")[0];
63 | this.input = element.querySelectorAll(".floatl__input")[0];
64 | // Return early if not both the label and input are present
65 | if (!this.label || !this.input) {
66 | return;
67 | }
68 | if (this.input.nodeName === "TEXTAREA") {
69 | addClass(this.element, Floatl.MULTILINE_CLASS);
70 | }
71 | // Handle initial value
72 | this.handleChange();
73 | // Bind event listeners
74 | addEventListener(this.input, "focus", this.addFocusedClass);
75 | addEventListener(this.input, "blur", this.removeFocusedClass);
76 | for (var _i = 0, _a = ["keyup", "blur", "change", "input"]; _i < _a.length; _i++) {
77 | var event_1 = _a[_i];
78 | addEventListener(this.input, event_1, this.handleChange);
79 | }
80 | }
81 | Floatl.prototype.destroy = function () {
82 | removeEventListener(this.input, "focus", this.addFocusedClass);
83 | removeEventListener(this.input, "blur", this.removeFocusedClass);
84 | for (var _i = 0, _a = ["keyup", "blur", "change", "input"]; _i < _a.length; _i++) {
85 | var event_2 = _a[_i];
86 | removeEventListener(this.input, event_2, this.handleChange);
87 | }
88 | };
89 | Floatl.FOCUSED_CLASS = "floatl--focused";
90 | Floatl.ACTIVE_CLASS = "floatl--active";
91 | Floatl.MULTILINE_CLASS = "floatl--multiline";
92 | return Floatl;
93 | }());
94 |
95 | return Floatl;
96 |
97 | })));
98 |
--------------------------------------------------------------------------------
/example/build/source/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/build/source/images/.keep
--------------------------------------------------------------------------------
/example/build/stylesheets/floatl.css:
--------------------------------------------------------------------------------
1 | .floatl {
2 | position: relative; }
3 |
4 | .floatl--focused .floatl__label {
5 | color: #2a8dea; }
6 |
7 | .floatl--active .floatl__label {
8 | visibility: visible;
9 | opacity: 1;
10 | top: 1px; }
11 | .floatl--active .floatl__input {
12 | padding: 21px 11px 7px; }
13 | .floatl--active.floatl--multiline .floatl__label {
14 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.95)), color-stop(80%, rgba(255, 255, 255, 0.95)), to(rgba(255, 255, 255, 0)));
15 | background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.95) 80%, rgba(255, 255, 255, 0) 100%);
16 | background-color: white; }
17 |
18 | .floatl__label {
19 | -webkit-transition: all 200ms ease;
20 | transition: all 200ms ease;
21 | position: absolute;
22 | visibility: hidden;
23 | opacity: 0;
24 | top: 3px;
25 | left: 9px;
26 | display: inline-block;
27 | padding: 6px 3px 3px;
28 | font-weight: bold;
29 | font-size: 11px;
30 | line-height: 1em;
31 | color: #666666; }
32 |
33 | .floatl__input {
34 | -webkit-transition: all 200ms ease;
35 | transition: all 200ms ease;
36 | padding: 14px 11px;
37 | background-color: #fafafa;
38 | font-size: 16px;
39 | border: 1px solid #e6e6e6;
40 | border-radius: 5px;
41 | -webkit-appearance: none;
42 | -moz-appearance: none;
43 | appearance: none;
44 | outline: none; }
45 | .floatl__input::-moz-selection {
46 | background-color: #2a8dea;
47 | color: white; }
48 | .floatl__input::selection {
49 | background-color: #2a8dea;
50 | color: white; }
51 | .floatl__input:focus {
52 | border-color: #2a8dea; }
53 |
54 | input.floatl__input {
55 | height: 47px; }
56 |
--------------------------------------------------------------------------------
/example/build/stylesheets/site.css:
--------------------------------------------------------------------------------
1 | @import url(./vendor/normalize.css);
2 | .floatl {
3 | position: relative; }
4 |
5 | .floatl--focused .floatl__label {
6 | color: #2a8dea; }
7 |
8 | .floatl--active .floatl__label {
9 | visibility: visible;
10 | opacity: 1;
11 | top: 1px; }
12 | .floatl--active .floatl__input {
13 | padding: 21px 11px 7px; }
14 | .floatl--active.floatl--multiline .floatl__label {
15 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.95)), color-stop(80%, rgba(255, 255, 255, 0.95)), to(rgba(255, 255, 255, 0)));
16 | background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.95) 80%, rgba(255, 255, 255, 0) 100%);
17 | background-color: white; }
18 |
19 | .floatl__label {
20 | -webkit-transition: all 200ms ease;
21 | transition: all 200ms ease;
22 | position: absolute;
23 | visibility: hidden;
24 | opacity: 0;
25 | top: 3px;
26 | left: 9px;
27 | display: inline-block;
28 | padding: 6px 3px 3px;
29 | font-weight: bold;
30 | font-size: 11px;
31 | line-height: 1em;
32 | color: #666666; }
33 |
34 | .floatl__input {
35 | -webkit-transition: all 200ms ease;
36 | transition: all 200ms ease;
37 | padding: 14px 11px;
38 | background-color: #fafafa;
39 | font-size: 16px;
40 | border: 1px solid #e6e6e6;
41 | border-radius: 5px;
42 | -webkit-appearance: none;
43 | -moz-appearance: none;
44 | appearance: none;
45 | outline: none; }
46 | .floatl__input::-moz-selection {
47 | background-color: #2a8dea;
48 | color: white; }
49 | .floatl__input::selection {
50 | background-color: #2a8dea;
51 | color: white; }
52 | .floatl__input:focus {
53 | border-color: #2a8dea; }
54 |
55 | input.floatl__input {
56 | height: 47px; }
57 |
58 | html {
59 | -webkit-box-sizing: border-box;
60 | box-sizing: border-box;
61 | text-rendering: optimizeLegibility;
62 | -webkit-font-smoothing: antialiased;
63 | -moz-osx-font-smoothing: grayscale; }
64 |
65 | *,
66 | *::before,
67 | *::after {
68 | -webkit-box-sizing: inherit;
69 | box-sizing: inherit; }
70 |
71 | body {
72 | -webkit-animation: gradient 20s ease infinite;
73 | animation: gradient 20s ease infinite;
74 | color: #333;
75 | background: linear-gradient(76deg, #2c3e50, #4ca1af);
76 | background-size: 400% 400%;
77 | font-family: -apple-system, BlinkMacSystemFont, "Avenir Next", "Avenir", "Segoe UI", "Lucida Grande", "Helvetica Neue", "Helvetica", "Fira Sans", "Roboto", "Noto", "Droid Sans", "Cantarell", "Oxygen", "Ubuntu", "Franklin Gothic Medium", "Century Gothic", "Liberation Sans", sans-serif;
78 | padding: 18vh 1rem; }
79 |
80 | @-webkit-keyframes gradient {
81 | 0% {
82 | background-position: 0% 50%; }
83 | 50% {
84 | background-position: 100% 50%; }
85 | 100% {
86 | background-position: 0% 50%; } }
87 |
88 | @keyframes gradient {
89 | 0% {
90 | background-position: 0% 50%; }
91 | 50% {
92 | background-position: 100% 50%; }
93 | 100% {
94 | background-position: 0% 50%; } }
95 | .header {
96 | color: white;
97 | text-align: center;
98 | font-weight: 300; }
99 |
100 | .container {
101 | will-change: transform;
102 | margin: 0 auto;
103 | padding: 3em;
104 | background-color: white;
105 | max-width: 360px;
106 | border-radius: 5px; }
107 |
108 | input, textarea {
109 | width: 100%; }
110 |
111 | textarea {
112 | height: 100px; }
113 |
114 | .floatl + .floatl {
115 | margin-top: 1em; }
116 |
117 | footer {
118 | padding-top: 2em;
119 | margin: 2em auto 0;
120 | width: 128px;
121 | text-align: center;
122 | border-top: 1px dashed #e6e6e6; }
123 |
124 | .github-link {
125 | -webkit-transition: opacity 300ms ease;
126 | transition: opacity 300ms ease;
127 | opacity: 0.9;
128 | display: inline-block;
129 | text-decoration: none; }
130 | .github-link:hover {
131 | opacity: 0.6; }
132 |
--------------------------------------------------------------------------------
/example/build/stylesheets/vendor/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Correct the font size and margin on `h1` elements within `section` and
29 | * `article` contexts in Chrome, Firefox, and Safari.
30 | */
31 |
32 | h1 {
33 | font-size: 2em;
34 | margin: 0.67em 0;
35 | }
36 |
37 | /* Grouping content
38 | ========================================================================== */
39 |
40 | /**
41 | * 1. Add the correct box sizing in Firefox.
42 | * 2. Show the overflow in Edge and IE.
43 | */
44 |
45 | hr {
46 | -webkit-box-sizing: content-box;
47 | box-sizing: content-box; /* 1 */
48 | height: 0; /* 1 */
49 | overflow: visible; /* 2 */
50 | }
51 |
52 | /**
53 | * 1. Correct the inheritance and scaling of font size in all browsers.
54 | * 2. Correct the odd `em` font sizing in all browsers.
55 | */
56 |
57 | pre {
58 | font-family: monospace, monospace; /* 1 */
59 | font-size: 1em; /* 2 */
60 | }
61 |
62 | /* Text-level semantics
63 | ========================================================================== */
64 |
65 | /**
66 | * Remove the gray background on active links in IE 10.
67 | */
68 |
69 | a {
70 | background-color: transparent;
71 | }
72 |
73 | /**
74 | * 1. Remove the bottom border in Chrome 57-
75 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
76 | */
77 |
78 | abbr[title] {
79 | border-bottom: none; /* 1 */
80 | text-decoration: underline; /* 2 */
81 | -webkit-text-decoration: underline dotted;
82 | text-decoration: underline dotted; /* 2 */
83 | }
84 |
85 | /**
86 | * Add the correct font weight in Chrome, Edge, and Safari.
87 | */
88 |
89 | b,
90 | strong {
91 | font-weight: bolder;
92 | }
93 |
94 | /**
95 | * 1. Correct the inheritance and scaling of font size in all browsers.
96 | * 2. Correct the odd `em` font sizing in all browsers.
97 | */
98 |
99 | code,
100 | kbd,
101 | samp {
102 | font-family: monospace, monospace; /* 1 */
103 | font-size: 1em; /* 2 */
104 | }
105 |
106 | /**
107 | * Add the correct font size in all browsers.
108 | */
109 |
110 | small {
111 | font-size: 80%;
112 | }
113 |
114 | /**
115 | * Prevent `sub` and `sup` elements from affecting the line height in
116 | * all browsers.
117 | */
118 |
119 | sub,
120 | sup {
121 | font-size: 75%;
122 | line-height: 0;
123 | position: relative;
124 | vertical-align: baseline;
125 | }
126 |
127 | sub {
128 | bottom: -0.25em;
129 | }
130 |
131 | sup {
132 | top: -0.5em;
133 | }
134 |
135 | /* Embedded content
136 | ========================================================================== */
137 |
138 | /**
139 | * Remove the border on images inside links in IE 10.
140 | */
141 |
142 | img {
143 | border-style: none;
144 | }
145 |
146 | /* Forms
147 | ========================================================================== */
148 |
149 | /**
150 | * 1. Change the font styles in all browsers.
151 | * 2. Remove the margin in Firefox and Safari.
152 | */
153 |
154 | button,
155 | input,
156 | optgroup,
157 | select,
158 | textarea {
159 | font-family: inherit; /* 1 */
160 | font-size: 100%; /* 1 */
161 | line-height: 1.15; /* 1 */
162 | margin: 0; /* 2 */
163 | }
164 |
165 | /**
166 | * Show the overflow in IE.
167 | * 1. Show the overflow in Edge.
168 | */
169 |
170 | button,
171 | input { /* 1 */
172 | overflow: visible;
173 | }
174 |
175 | /**
176 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
177 | * 1. Remove the inheritance of text transform in Firefox.
178 | */
179 |
180 | button,
181 | select { /* 1 */
182 | text-transform: none;
183 | }
184 |
185 | /**
186 | * Correct the inability to style clickable types in iOS and Safari.
187 | */
188 |
189 | button,
190 | [type="button"],
191 | [type="reset"],
192 | [type="submit"] {
193 | -webkit-appearance: button;
194 | }
195 |
196 | /**
197 | * Remove the inner border and padding in Firefox.
198 | */
199 |
200 | button::-moz-focus-inner,
201 | [type="button"]::-moz-focus-inner,
202 | [type="reset"]::-moz-focus-inner,
203 | [type="submit"]::-moz-focus-inner {
204 | border-style: none;
205 | padding: 0;
206 | }
207 |
208 | /**
209 | * Restore the focus styles unset by the previous rule.
210 | */
211 |
212 | button:-moz-focusring,
213 | [type="button"]:-moz-focusring,
214 | [type="reset"]:-moz-focusring,
215 | [type="submit"]:-moz-focusring {
216 | outline: 1px dotted ButtonText;
217 | }
218 |
219 | /**
220 | * Correct the padding in Firefox.
221 | */
222 |
223 | fieldset {
224 | padding: 0.35em 0.75em 0.625em;
225 | }
226 |
227 | /**
228 | * 1. Correct the text wrapping in Edge and IE.
229 | * 2. Correct the color inheritance from `fieldset` elements in IE.
230 | * 3. Remove the padding so developers are not caught out when they zero out
231 | * `fieldset` elements in all browsers.
232 | */
233 |
234 | legend {
235 | -webkit-box-sizing: border-box;
236 | box-sizing: border-box; /* 1 */
237 | color: inherit; /* 2 */
238 | display: table; /* 1 */
239 | max-width: 100%; /* 1 */
240 | padding: 0; /* 3 */
241 | white-space: normal; /* 1 */
242 | }
243 |
244 | /**
245 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
246 | */
247 |
248 | progress {
249 | vertical-align: baseline;
250 | }
251 |
252 | /**
253 | * Remove the default vertical scrollbar in IE 10+.
254 | */
255 |
256 | textarea {
257 | overflow: auto;
258 | }
259 |
260 | /**
261 | * 1. Add the correct box sizing in IE 10.
262 | * 2. Remove the padding in IE 10.
263 | */
264 |
265 | [type="checkbox"],
266 | [type="radio"] {
267 | -webkit-box-sizing: border-box;
268 | box-sizing: border-box; /* 1 */
269 | padding: 0; /* 2 */
270 | }
271 |
272 | /**
273 | * Correct the cursor style of increment and decrement buttons in Chrome.
274 | */
275 |
276 | [type="number"]::-webkit-inner-spin-button,
277 | [type="number"]::-webkit-outer-spin-button {
278 | height: auto;
279 | }
280 |
281 | /**
282 | * 1. Correct the odd appearance in Chrome and Safari.
283 | * 2. Correct the outline style in Safari.
284 | */
285 |
286 | [type="search"] {
287 | -webkit-appearance: textfield; /* 1 */
288 | outline-offset: -2px; /* 2 */
289 | }
290 |
291 | /**
292 | * Remove the inner padding in Chrome and Safari on macOS.
293 | */
294 |
295 | [type="search"]::-webkit-search-decoration {
296 | -webkit-appearance: none;
297 | }
298 |
299 | /**
300 | * 1. Correct the inability to style clickable types in iOS and Safari.
301 | * 2. Change font properties to `inherit` in Safari.
302 | */
303 |
304 | ::-webkit-file-upload-button {
305 | -webkit-appearance: button; /* 1 */
306 | font: inherit; /* 2 */
307 | }
308 |
309 | /* Interactive
310 | ========================================================================== */
311 |
312 | /*
313 | * Add the correct display in Edge, IE 10+, and Firefox.
314 | */
315 |
316 | details {
317 | display: block;
318 | }
319 |
320 | /*
321 | * Add the correct display in all browsers.
322 | */
323 |
324 | summary {
325 | display: list-item;
326 | }
327 |
328 | /* Misc
329 | ========================================================================== */
330 |
331 | /**
332 | * Add the correct display in IE 10+.
333 | */
334 |
335 | template {
336 | display: none;
337 | }
338 |
339 | /**
340 | * Add the correct display in IE 10.
341 | */
342 |
343 | [hidden] {
344 | display: none;
345 | }
346 |
--------------------------------------------------------------------------------
/example/config.rb:
--------------------------------------------------------------------------------
1 | # Activate and configure extensions
2 | # https://middlemanapp.com/advanced/configuration/#configuring-extensions
3 |
4 | activate :autoprefixer do |prefix|
5 | prefix.browsers = "last 2 versions"
6 | end
7 |
8 | activate :livereload
9 |
10 | activate :relative_assets
11 | set :relative_links, true
12 |
13 | set :sass, output_style: :expanded
14 |
15 | # Layouts
16 | # https://middlemanapp.com/basics/layouts/
17 |
18 | # Per-page layout changes
19 | page '/*.xml', layout: false
20 | page '/*.json', layout: false
21 | page '/*.txt', layout: false
22 |
23 | # With alternative layout
24 | # page '/path/to/file.html', layout: 'other_layout'
25 |
26 | # Proxy pages
27 | # https://middlemanapp.com/advanced/dynamic-pages/
28 |
29 | # proxy(
30 | # '/this-page-has-no-template.html',
31 | # '/template-file.html',
32 | # locals: {
33 | # which_fake_page: 'Rendering a fake page with a local variable'
34 | # },
35 | # )
36 |
37 | # Helpers
38 | # Methods defined in the helpers block are available in templates
39 | # https://middlemanapp.com/basics/helper-methods/
40 |
41 | # helpers do
42 | # def some_helper
43 | # 'Helping'
44 | # end
45 | # end
46 |
47 | # Build-specific configuration
48 | # https://middlemanapp.com/advanced/configuration/#environment-specific-settings
49 |
50 | # configure :build do
51 | # activate :minify_css
52 | # activate :minify_javascript
53 | # end
54 |
--------------------------------------------------------------------------------
/example/source/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/source/images/.keep
--------------------------------------------------------------------------------
/example/source/images/GitHub-Mark-64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richardvenneman/floatl/0e3159ddafb23f698f9ad7b720e77871f2d88c6a/example/source/images/GitHub-Mark-64px.png
--------------------------------------------------------------------------------
/example/source/index.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: Floatl
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
37 |
--------------------------------------------------------------------------------
/example/source/javascripts/vendor/floatl.umd.js:
--------------------------------------------------------------------------------
1 | /* floatl version 1.0.5 */
2 | (function (global, factory) {
3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4 | typeof define === 'function' && define.amd ? define(factory) :
5 | (global.Floatl = factory());
6 | }(this, (function () { 'use strict';
7 |
8 | function addClass(element, className) {
9 | if (element.classList) {
10 | element.classList.add(className);
11 | }
12 | else {
13 | element.className += " " + className;
14 | }
15 | }
16 | function removeClass(element, className) {
17 | if (element.classList) {
18 | element.classList.remove(className);
19 | }
20 | else {
21 | var re = new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi");
22 | element.className = element.className.replace(re, " ");
23 | }
24 | }
25 | function addEventListener(element, event, cb) {
26 | if (element.addEventListener) {
27 | element.addEventListener(event, cb);
28 | }
29 | else {
30 | element.attachEvent("on" + event, function () {
31 | cb.call(element);
32 | });
33 | }
34 | }
35 | function removeEventListener(element, event, cb) {
36 | if (element.removeEventListener) {
37 | element.removeEventListener(event, cb);
38 | }
39 | else {
40 | element.detachEvent("on" + event, cb);
41 | }
42 | }
43 |
44 | var Floatl = /** @class */ (function () {
45 | function Floatl(element) {
46 | var _this = this;
47 | this.handleChange = function () {
48 | if (_this.input.value === "") {
49 | removeClass(_this.element, Floatl.ACTIVE_CLASS);
50 | }
51 | else {
52 | addClass(_this.element, Floatl.ACTIVE_CLASS);
53 | }
54 | };
55 | this.addFocusedClass = function () {
56 | addClass(_this.element, Floatl.FOCUSED_CLASS);
57 | };
58 | this.removeFocusedClass = function () {
59 | removeClass(_this.element, Floatl.FOCUSED_CLASS);
60 | };
61 | this.element = element;
62 | this.label = element.querySelectorAll(".floatl__label")[0];
63 | this.input = element.querySelectorAll(".floatl__input")[0];
64 | // Return early if not both the label and input are present
65 | if (!this.label || !this.input) {
66 | return;
67 | }
68 | if (this.input.nodeName === "TEXTAREA") {
69 | addClass(this.element, Floatl.MULTILINE_CLASS);
70 | }
71 | // Handle initial value
72 | this.handleChange();
73 | // Bind event listeners
74 | addEventListener(this.input, "focus", this.addFocusedClass);
75 | addEventListener(this.input, "blur", this.removeFocusedClass);
76 | for (var _i = 0, _a = ["keyup", "blur", "change", "input"]; _i < _a.length; _i++) {
77 | var event_1 = _a[_i];
78 | addEventListener(this.input, event_1, this.handleChange);
79 | }
80 | }
81 | Floatl.prototype.destroy = function () {
82 | removeEventListener(this.input, "focus", this.addFocusedClass);
83 | removeEventListener(this.input, "blur", this.removeFocusedClass);
84 | for (var _i = 0, _a = ["keyup", "blur", "change", "input"]; _i < _a.length; _i++) {
85 | var event_2 = _a[_i];
86 | removeEventListener(this.input, event_2, this.handleChange);
87 | }
88 | };
89 | Floatl.FOCUSED_CLASS = "floatl--focused";
90 | Floatl.ACTIVE_CLASS = "floatl--active";
91 | Floatl.MULTILINE_CLASS = "floatl--multiline";
92 | return Floatl;
93 | }());
94 |
95 | return Floatl;
96 |
97 | })));
98 |
--------------------------------------------------------------------------------
/example/source/layouts/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 | <%= current_page.data.title || "Middleman" %>
10 | <%= stylesheet_link_tag "site" %>
11 | <%= javascript_include_tag "vendor/floatl.umd.js" %>
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/source/stylesheets/floatl.css.scss:
--------------------------------------------------------------------------------
1 | $font-size: 16px;
2 | $vpadding: 7px;
3 | $hpadding: 11px;
4 |
5 | $label-font-size: 11px;
6 |
7 | $duration: 200ms;
8 | $border-color: hsl(0, 0%, 90%);
9 | $text-color: hsl(0, 0%, 40%);
10 | $background-color: white;
11 | $active-color: hsl(209, 82%, 54%);
12 |
13 | .floatl {
14 | position: relative;
15 | }
16 |
17 | .floatl--focused .floatl__label {
18 | color: $active-color;
19 | }
20 |
21 | .floatl--active {
22 | .floatl__label {
23 | visibility: visible;
24 | opacity: 1;
25 | top: 1px;
26 | }
27 |
28 | .floatl__input {
29 | padding: ($vpadding * 3) $hpadding $vpadding;
30 | }
31 |
32 | &.floatl--multiline .floatl__label {
33 | background: linear-gradient(
34 | to bottom,
35 | rgba(white, 0.95) 0%,
36 | rgba(white, 0.95) 80%,
37 | rgba(white, 0) 100%
38 | );
39 | background-color: $background-color;
40 | }
41 | }
42 |
43 | .floatl__label {
44 | transition: all $duration ease;
45 | position: absolute;
46 | visibility: hidden;
47 | opacity: 0;
48 | top: 3px;
49 | left: $hpadding - 3px + 1px;
50 | display: inline-block;
51 | padding: 6px 3px 3px;
52 | font-weight: bold;
53 | font-size: $label-font-size;
54 | line-height: 1em;
55 | color: $text-color;
56 | }
57 |
58 | .floatl__input {
59 | transition: all $duration ease;
60 | padding: ($vpadding * 2) $hpadding;
61 | background-color: hsl(0, 0%, 98%);
62 | font-size: $font-size;
63 | border: 1px solid $border-color;
64 | border-radius: 5px;
65 | appearance: none;
66 | outline: none;
67 |
68 | &::selection {
69 | background-color: $active-color;
70 | color: white;
71 | }
72 |
73 | &:focus {
74 | border-color: $active-color;
75 | }
76 | }
77 |
78 | input.floatl__input {
79 | height: 47px;
80 | }
81 |
--------------------------------------------------------------------------------
/example/source/stylesheets/site.css.scss:
--------------------------------------------------------------------------------
1 | @import './vendor/normalize.css';
2 | @import 'floatl.css.scss';
3 |
4 | html {
5 | box-sizing: border-box;
6 | text-rendering: optimizeLegibility;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | *,
12 | *::before,
13 | *::after {
14 | box-sizing: inherit;
15 | }
16 |
17 | body {
18 | animation: gradient 20s ease infinite;
19 | color: #333;
20 | background: linear-gradient(76deg, #2c3e50, #4ca1af);
21 | background-size: 400% 400%;
22 | font-family: -apple-system, BlinkMacSystemFont, "Avenir Next", "Avenir",
23 | "Segoe UI", "Lucida Grande", "Helvetica Neue", "Helvetica", "Fira Sans",
24 | "Roboto", "Noto", "Droid Sans", "Cantarell", "Oxygen", "Ubuntu",
25 | "Franklin Gothic Medium", "Century Gothic", "Liberation Sans", sans-serif;
26 | padding: 18vh 1rem;
27 | }
28 |
29 | @keyframes gradient {
30 | 0% {
31 | background-position: 0% 50%
32 | }
33 | 50% {
34 | background-position: 100% 50%
35 | }
36 | 100% {
37 | background-position: 0% 50%
38 | }
39 | }
40 |
41 | .header {
42 | color: white;
43 | text-align: center;
44 | font-weight: 300;
45 | }
46 |
47 | .container {
48 | will-change: transform;
49 | margin: 0 auto;
50 | padding: 3em;
51 | background-color: white;
52 | max-width: 360px;
53 | border-radius: 5px;
54 | }
55 |
56 | input, textarea {
57 | width: 100%;
58 | }
59 |
60 | textarea {
61 | height: 100px;
62 | }
63 |
64 | .floatl + .floatl {
65 | margin-top: 1em;
66 | }
67 |
68 | footer {
69 | padding-top: 2em;
70 | margin: 2em auto 0;
71 | width: 128px;
72 | text-align: center;
73 | border-top: 1px dashed hsl(0, 0%, 90%);
74 | }
75 |
76 | .github-link {
77 | transition: opacity 300ms ease;
78 | opacity: 0.9;
79 | display: inline-block;
80 | text-decoration: none;
81 |
82 | &:hover {
83 | opacity: 0.6;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/example/source/stylesheets/vendor/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Correct the font size and margin on `h1` elements within `section` and
29 | * `article` contexts in Chrome, Firefox, and Safari.
30 | */
31 |
32 | h1 {
33 | font-size: 2em;
34 | margin: 0.67em 0;
35 | }
36 |
37 | /* Grouping content
38 | ========================================================================== */
39 |
40 | /**
41 | * 1. Add the correct box sizing in Firefox.
42 | * 2. Show the overflow in Edge and IE.
43 | */
44 |
45 | hr {
46 | box-sizing: content-box; /* 1 */
47 | height: 0; /* 1 */
48 | overflow: visible; /* 2 */
49 | }
50 |
51 | /**
52 | * 1. Correct the inheritance and scaling of font size in all browsers.
53 | * 2. Correct the odd `em` font sizing in all browsers.
54 | */
55 |
56 | pre {
57 | font-family: monospace, monospace; /* 1 */
58 | font-size: 1em; /* 2 */
59 | }
60 |
61 | /* Text-level semantics
62 | ========================================================================== */
63 |
64 | /**
65 | * Remove the gray background on active links in IE 10.
66 | */
67 |
68 | a {
69 | background-color: transparent;
70 | }
71 |
72 | /**
73 | * 1. Remove the bottom border in Chrome 57-
74 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
75 | */
76 |
77 | abbr[title] {
78 | border-bottom: none; /* 1 */
79 | text-decoration: underline; /* 2 */
80 | text-decoration: underline dotted; /* 2 */
81 | }
82 |
83 | /**
84 | * Add the correct font weight in Chrome, Edge, and Safari.
85 | */
86 |
87 | b,
88 | strong {
89 | font-weight: bolder;
90 | }
91 |
92 | /**
93 | * 1. Correct the inheritance and scaling of font size in all browsers.
94 | * 2. Correct the odd `em` font sizing in all browsers.
95 | */
96 |
97 | code,
98 | kbd,
99 | samp {
100 | font-family: monospace, monospace; /* 1 */
101 | font-size: 1em; /* 2 */
102 | }
103 |
104 | /**
105 | * Add the correct font size in all browsers.
106 | */
107 |
108 | small {
109 | font-size: 80%;
110 | }
111 |
112 | /**
113 | * Prevent `sub` and `sup` elements from affecting the line height in
114 | * all browsers.
115 | */
116 |
117 | sub,
118 | sup {
119 | font-size: 75%;
120 | line-height: 0;
121 | position: relative;
122 | vertical-align: baseline;
123 | }
124 |
125 | sub {
126 | bottom: -0.25em;
127 | }
128 |
129 | sup {
130 | top: -0.5em;
131 | }
132 |
133 | /* Embedded content
134 | ========================================================================== */
135 |
136 | /**
137 | * Remove the border on images inside links in IE 10.
138 | */
139 |
140 | img {
141 | border-style: none;
142 | }
143 |
144 | /* Forms
145 | ========================================================================== */
146 |
147 | /**
148 | * 1. Change the font styles in all browsers.
149 | * 2. Remove the margin in Firefox and Safari.
150 | */
151 |
152 | button,
153 | input,
154 | optgroup,
155 | select,
156 | textarea {
157 | font-family: inherit; /* 1 */
158 | font-size: 100%; /* 1 */
159 | line-height: 1.15; /* 1 */
160 | margin: 0; /* 2 */
161 | }
162 |
163 | /**
164 | * Show the overflow in IE.
165 | * 1. Show the overflow in Edge.
166 | */
167 |
168 | button,
169 | input { /* 1 */
170 | overflow: visible;
171 | }
172 |
173 | /**
174 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
175 | * 1. Remove the inheritance of text transform in Firefox.
176 | */
177 |
178 | button,
179 | select { /* 1 */
180 | text-transform: none;
181 | }
182 |
183 | /**
184 | * Correct the inability to style clickable types in iOS and Safari.
185 | */
186 |
187 | button,
188 | [type="button"],
189 | [type="reset"],
190 | [type="submit"] {
191 | -webkit-appearance: button;
192 | }
193 |
194 | /**
195 | * Remove the inner border and padding in Firefox.
196 | */
197 |
198 | button::-moz-focus-inner,
199 | [type="button"]::-moz-focus-inner,
200 | [type="reset"]::-moz-focus-inner,
201 | [type="submit"]::-moz-focus-inner {
202 | border-style: none;
203 | padding: 0;
204 | }
205 |
206 | /**
207 | * Restore the focus styles unset by the previous rule.
208 | */
209 |
210 | button:-moz-focusring,
211 | [type="button"]:-moz-focusring,
212 | [type="reset"]:-moz-focusring,
213 | [type="submit"]:-moz-focusring {
214 | outline: 1px dotted ButtonText;
215 | }
216 |
217 | /**
218 | * Correct the padding in Firefox.
219 | */
220 |
221 | fieldset {
222 | padding: 0.35em 0.75em 0.625em;
223 | }
224 |
225 | /**
226 | * 1. Correct the text wrapping in Edge and IE.
227 | * 2. Correct the color inheritance from `fieldset` elements in IE.
228 | * 3. Remove the padding so developers are not caught out when they zero out
229 | * `fieldset` elements in all browsers.
230 | */
231 |
232 | legend {
233 | box-sizing: border-box; /* 1 */
234 | color: inherit; /* 2 */
235 | display: table; /* 1 */
236 | max-width: 100%; /* 1 */
237 | padding: 0; /* 3 */
238 | white-space: normal; /* 1 */
239 | }
240 |
241 | /**
242 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
243 | */
244 |
245 | progress {
246 | vertical-align: baseline;
247 | }
248 |
249 | /**
250 | * Remove the default vertical scrollbar in IE 10+.
251 | */
252 |
253 | textarea {
254 | overflow: auto;
255 | }
256 |
257 | /**
258 | * 1. Add the correct box sizing in IE 10.
259 | * 2. Remove the padding in IE 10.
260 | */
261 |
262 | [type="checkbox"],
263 | [type="radio"] {
264 | box-sizing: border-box; /* 1 */
265 | padding: 0; /* 2 */
266 | }
267 |
268 | /**
269 | * Correct the cursor style of increment and decrement buttons in Chrome.
270 | */
271 |
272 | [type="number"]::-webkit-inner-spin-button,
273 | [type="number"]::-webkit-outer-spin-button {
274 | height: auto;
275 | }
276 |
277 | /**
278 | * 1. Correct the odd appearance in Chrome and Safari.
279 | * 2. Correct the outline style in Safari.
280 | */
281 |
282 | [type="search"] {
283 | -webkit-appearance: textfield; /* 1 */
284 | outline-offset: -2px; /* 2 */
285 | }
286 |
287 | /**
288 | * Remove the inner padding in Chrome and Safari on macOS.
289 | */
290 |
291 | [type="search"]::-webkit-search-decoration {
292 | -webkit-appearance: none;
293 | }
294 |
295 | /**
296 | * 1. Correct the inability to style clickable types in iOS and Safari.
297 | * 2. Change font properties to `inherit` in Safari.
298 | */
299 |
300 | ::-webkit-file-upload-button {
301 | -webkit-appearance: button; /* 1 */
302 | font: inherit; /* 2 */
303 | }
304 |
305 | /* Interactive
306 | ========================================================================== */
307 |
308 | /*
309 | * Add the correct display in Edge, IE 10+, and Firefox.
310 | */
311 |
312 | details {
313 | display: block;
314 | }
315 |
316 | /*
317 | * Add the correct display in all browsers.
318 | */
319 |
320 | summary {
321 | display: list-item;
322 | }
323 |
324 | /* Misc
325 | ========================================================================== */
326 |
327 | /**
328 | * Add the correct display in IE 10+.
329 | */
330 |
331 | template {
332 | display: none;
333 | }
334 |
335 | /**
336 | * Add the correct display in IE 10.
337 | */
338 |
339 | [hidden] {
340 | display: none;
341 | }
342 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | process.env.CHROME_BIN = require("puppeteer").executablePath();
2 |
3 | const sauceLabs = process.env.CI ? true : false;
4 | const sauceLabsLaunchers = {
5 | sl_win_chrome: {
6 | base: "SauceLabs",
7 | browserName: "chrome",
8 | platform: "Windows 10"
9 | },
10 | sl_mac_chrome: {
11 | base: "SauceLabs",
12 | browserName: "chrome",
13 | platform: "macOS 10.12"
14 | },
15 | sl_firefox: {
16 | base: "SauceLabs",
17 | browserName: "firefox",
18 | platform: "Windows 10"
19 | },
20 | sl_mac_firfox: {
21 | base: "SauceLabs",
22 | browserName: "firefox",
23 | platform: "macOS 10.12"
24 | },
25 | sl_safari: {
26 | base: "SauceLabs",
27 | browserName: "safari",
28 | platform: "macOS 10.12"
29 | },
30 | sl_edge: {
31 | base: "SauceLabs",
32 | browserName: "MicrosoftEdge",
33 | platform: "Windows 10"
34 | },
35 | sl_ie_11: {
36 | base: "SauceLabs",
37 | browserName: "internet explorer",
38 | version: "11.103",
39 | platform: "Windows 10"
40 | },
41 | sl_ie_10: {
42 | base: "SauceLabs",
43 | browserName: "internet explorer",
44 | version: "10.0",
45 | platform: "Windows 7"
46 | },
47 | sl_ios_safari_9: {
48 | base: "SauceLabs",
49 | browserName: "iphone",
50 | version: "10.3"
51 | },
52 | "SL_ANDROID4.4": {
53 | base: "SauceLabs",
54 | browserName: "android",
55 | platform: "Linux",
56 | version: "4.4"
57 | },
58 | SL_ANDROID5: {
59 | base: "SauceLabs",
60 | browserName: "android",
61 | platform: "Linux",
62 | version: "5.1"
63 | },
64 | SL_ANDROID6: {
65 | base: "SauceLabs",
66 | browserName: "Chrome",
67 | platform: "Android",
68 | version: "6.0",
69 | device: "Android Emulator"
70 | }
71 | };
72 |
73 | module.exports = function(config) {
74 | config.set({
75 | concurrency: 5,
76 |
77 | frameworks: ["jasmine", "karma-typescript"],
78 |
79 | files: ["src/**/*.ts", "spec/**/*.ts"],
80 |
81 | preprocessors: {
82 | "src/**/*.ts": ["karma-typescript", "coverage"],
83 | "spec/**/*.ts": "karma-typescript"
84 | },
85 |
86 | reporters: ["progress", "coverage", "karma-typescript", "notify"].concat(
87 | sauceLabs ? "saucelabs" : []
88 | ),
89 |
90 | customLaunchers: sauceLabs ? sauceLabsLaunchers : {},
91 |
92 | browsers: sauceLabs ? Object.keys(sauceLabsLaunchers) : ["ChromeHeadless"],
93 |
94 | coverageReporter: {
95 | type: "lcov",
96 | subdir: ".",
97 | file: "lcov.info"
98 | },
99 |
100 | sauceLabs: {
101 | build: process.env.SEMAPHORE_BUILD_NUMBER,
102 | testName: "Karma tests"
103 | }
104 | });
105 | };
106 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "floatl",
3 | "version": "2.0.1",
4 | "description": "A pragmatic implementation of the Float Label Pattern",
5 | "homepage": "https://github.com/richardvenneman/floatl",
6 | "bugs": {
7 | "url": "https://github.com/richardvenneman/floatl/issues"
8 | },
9 | "license": "MIT",
10 | "author": "Richard Venneman (richardvenneman@me.com)",
11 | "files": [
12 | "built"
13 | ],
14 | "main": "built/floatl.cjs.js",
15 | "module": "built/floatl.esm.js",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/richardvenneman/floatl.git"
19 | },
20 | "scripts": {
21 | "build": "rollup -c",
22 | "dev": "rollup -c -w",
23 | "test": "karma start --single-run",
24 | "tdd": "karma start",
25 | "prepare": "npm run build"
26 | },
27 | "browser": "built/floatl.umd.js",
28 | "dependencies": {},
29 | "devDependencies": {
30 | "@types/jasmine": "^2.8.6",
31 | "jasmine": "^3.0.0",
32 | "jasmine-core": "^2.99.1",
33 | "karma": "^3.0.0",
34 | "karma-chrome-launcher": "^2.2.0",
35 | "karma-cli": "^1.0.1",
36 | "karma-jasmine": "^1.1.1",
37 | "karma-notify-reporter": "^1.0.1",
38 | "karma-sauce-launcher": "^1.2.0",
39 | "karma-typescript": "^3.0.12",
40 | "prettier": "1.17.1",
41 | "puppeteer": "^1.0.0",
42 | "rollup": "^0.68.2",
43 | "rollup-plugin-typescript2": "^0.15.1",
44 | "tslint-config-prettier": "^1.8.0",
45 | "typescript": "^2.7.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "rollup-plugin-typescript2";
2 | import pkg from "./package.json";
3 |
4 | const banner = `/* ${pkg.name} version ${pkg.version} */`
5 | const exports = "default"
6 |
7 | export default [
8 | {
9 | input: "src/main.ts",
10 | output: { banner, exports, file: pkg.browser, format: "umd", name: "Floatl" },
11 | plugins: [typescript()]
12 | },
13 | {
14 | input: "src/main.ts",
15 | output: [
16 | { banner, exports, file: pkg.main, format: "cjs" },
17 | { banner, exports, file: pkg.module, format: "es" }
18 | ],
19 | plugins: [typescript()]
20 | }
21 | ];
22 |
--------------------------------------------------------------------------------
/spec/main.spec.ts:
--------------------------------------------------------------------------------
1 | import Floatl from "../src/main"
2 | import { trigger } from "./specHelpers"
3 |
4 | describe("Floatl", () => {
5 | let fixture
6 |
7 | beforeEach(() => {
8 | document.body.insertAdjacentHTML(
9 | "afterbegin",
10 | `
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
`
20 | )
21 | fixture = document.getElementById("fixture")
22 | })
23 |
24 | afterEach(() => {
25 | document.body.removeChild(fixture)
26 | })
27 |
28 | describe("Initialization", () => {
29 | it("Inits with a HTML element", () => {
30 | const action = () => {
31 | return new Floatl(document.getElementById("floatlOne"))
32 | }
33 |
34 | expect(action).not.toThrow()
35 | })
36 |
37 | it(`adds ${Floatl.ACTIVE_CLASS} class if input has a value`, () => {
38 | const element = document.getElementById("floatlOne")
39 | const input = document.getElementById("floatlOneInput");
40 | (input as HTMLInputElement).value = "Initial value"
41 | const floatl = new Floatl(element)
42 |
43 | expect(element.classList.contains(Floatl.ACTIVE_CLASS)).toBeTruthy()
44 | })
45 |
46 | it(`does not add ${Floatl.MULTILINE_CLASS} class if not a textarea`, () => {
47 | const element = document.getElementById("floatlOne")
48 | const floatl = new Floatl(element)
49 |
50 | expect(element.classList.contains(Floatl.MULTILINE_CLASS)).toBeFalsy()
51 | })
52 |
53 | it(`adds ${Floatl.MULTILINE_CLASS} class if applied to a textarea`, () => {
54 | const element = document.getElementById("floatlTwo")
55 | const floatl = new Floatl(element)
56 |
57 | expect(element.classList.contains(Floatl.MULTILINE_CLASS)).toBeTruthy()
58 | })
59 | })
60 |
61 | describe("Focused state", () => {
62 | let element
63 | let input
64 |
65 | beforeEach(() => {
66 | element = document.getElementById("floatlOne")
67 | input = document.getElementById("floatlOneInput")
68 | const floatl = new Floatl(element)
69 | })
70 |
71 | it(`adds ${Floatl.FOCUSED_CLASS} class on focus`, () => {
72 | expect(element.classList.contains(Floatl.FOCUSED_CLASS)).toBeFalsy()
73 |
74 | trigger(input, "focus")
75 |
76 | expect(element.classList.contains(Floatl.FOCUSED_CLASS)).toBeTruthy()
77 | })
78 |
79 | it(`removes ${Floatl.FOCUSED_CLASS} class on blur`, () => {
80 | trigger(input, "focus")
81 |
82 | expect(element.classList.contains(Floatl.FOCUSED_CLASS)).toBeTruthy()
83 |
84 | trigger(input, "blur")
85 |
86 | expect(element.classList.contains(Floatl.FOCUSED_CLASS)).toBeFalsy()
87 | })
88 | })
89 |
90 | describe("Active state", () => {
91 | let element
92 | let input
93 |
94 | beforeEach(() => {
95 | element = document.getElementById("floatlOne")
96 | input = document.getElementById("floatlOneInput")
97 | const floatl = new Floatl(element)
98 | })
99 |
100 | it(`adds ${Floatl.ACTIVE_CLASS} class when entering characters`, () => {
101 | expect(element.classList.contains(Floatl.ACTIVE_CLASS)).toBeFalsy()
102 |
103 | input.value = "test"
104 | trigger(input, "keyup")
105 |
106 | expect(element.classList.contains(Floatl.ACTIVE_CLASS)).toBeTruthy()
107 | })
108 |
109 | it(`removes ${Floatl.ACTIVE_CLASS} removing all characters`, () => {
110 | input.value = "test"
111 | trigger(input, "keyup")
112 | input.value = ""
113 | trigger(input, "keyup")
114 |
115 | expect(element.classList.contains(Floatl.ACTIVE_CLASS)).toBeFalsy()
116 | })
117 | })
118 |
119 | describe("Destroy", () => {
120 | it("removes event listeners when destroying", () => {
121 | const element = document.getElementById("floatlOne")
122 | const input = document.getElementById(
123 | "floatlOneInput"
124 | ) as HTMLInputElement
125 | const floatl = new Floatl(element)
126 |
127 | floatl.destroy()
128 |
129 | trigger(input, "focus")
130 |
131 | expect(element.classList.contains(Floatl.FOCUSED_CLASS)).toBeFalsy()
132 |
133 | input.value = "test"
134 | trigger(input, "keyup")
135 |
136 | expect(element.classList.contains(Floatl.ACTIVE_CLASS)).toBeFalsy()
137 | })
138 | })
139 | })
140 |
--------------------------------------------------------------------------------
/spec/specHelpers.ts:
--------------------------------------------------------------------------------
1 | export function trigger(element: any, eventName: string) {
2 | if (document.createEvent) {
3 | const event = document.createEvent("HTMLEvents")
4 | event.initEvent(eventName, true, false)
5 | element.dispatchEvent(event)
6 | } else {
7 | element.fireEvent(eventName)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spec/utils.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | addClass,
3 | addEventListener,
4 | removeClass,
5 | removeEventListener
6 | } from "../src/utils"
7 |
8 | describe("Utils", () => {
9 | let fixture
10 |
11 | beforeEach(() => {
12 | document.body.insertAdjacentHTML("afterbegin", ``)
13 | fixture = document.getElementById("fixture")
14 | })
15 |
16 | afterEach(() => {
17 | document.body.removeChild(fixture)
18 | })
19 |
20 | describe("addClass", () => {
21 | it("adds a className to a DOM element", () => {
22 | addClass(fixture, "test")
23 |
24 | expect(fixture.className).toEqual("test")
25 | })
26 |
27 | it("adds a className to a DOM element with existing classes", () => {
28 | fixture.insertAdjacentHTML(
29 | "afterbegin",
30 | ``
31 | )
32 | const element = document.getElementById("element")
33 |
34 | addClass(element, "test")
35 |
36 | expect(element.className).toEqual("one test")
37 | })
38 | })
39 |
40 | describe("removeClass", () => {
41 | it("removes a className from a DOM element", () => {
42 | addClass(fixture, "test")
43 |
44 | expect(fixture.className).toEqual("test")
45 |
46 | removeClass(fixture, "test")
47 |
48 | expect(fixture.className).toEqual("")
49 | })
50 | })
51 |
52 | describe("addEventListener", () => {
53 | it("executes callback on attached DOM events", () => {
54 | const event = document.createEvent("HTMLEvents")
55 | event.initEvent("click", true, true)
56 |
57 | const callback = () => {
58 | fixture.innerHTML = "callback changed value"
59 | }
60 |
61 | addEventListener(fixture, "click", callback)
62 | fixture.dispatchEvent(event)
63 |
64 | expect(fixture.innerHTML).toEqual("callback changed value")
65 | })
66 | })
67 |
68 | describe("removeEventListener", () => {
69 | it("works", () => {
70 | const cb = () => (fixture.innerHTML = "test")
71 | const event = document.createEvent("HTMLEvents")
72 | event.initEvent("click", true, true)
73 |
74 | addEventListener(fixture, "click", cb)
75 | fixture.dispatchEvent(event)
76 |
77 | expect(fixture.innerHTML).toEqual("test")
78 |
79 | fixture.innerHTML = ""
80 |
81 | removeEventListener(fixture, "click", cb)
82 | fixture.dispatchEvent(event)
83 |
84 | expect(fixture.innerHTML).toEqual("")
85 | })
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import {
2 | addClass,
3 | addEventListener,
4 | removeClass,
5 | removeEventListener
6 | } from "./utils"
7 |
8 | export default class Floatl {
9 | public static readonly FOCUSED_CLASS = "floatl--focused"
10 | public static readonly ACTIVE_CLASS = "floatl--active"
11 | public static readonly MULTILINE_CLASS = "floatl--multiline"
12 |
13 | private element: HTMLElement
14 | private label: Element
15 | private input: Element
16 |
17 | constructor(element: HTMLElement) {
18 | this.element = element
19 | this.label = element.querySelectorAll(".floatl__label")[0]
20 | this.input = element.querySelectorAll(".floatl__input")[0]
21 |
22 | // Return early if not both the label and input are present
23 | if (!this.label || !this.input) {
24 | return
25 | }
26 |
27 | if (this.input.nodeName === "TEXTAREA") {
28 | addClass(this.element, Floatl.MULTILINE_CLASS)
29 | }
30 |
31 | // Handle initial value
32 | this.handleChange()
33 |
34 | // Bind event listeners
35 | addEventListener(this.input, "focus", this.addFocusedClass)
36 | addEventListener(this.input, "blur", this.removeFocusedClass)
37 |
38 | for (const event of ["keyup", "blur", "change", "input"]) {
39 | addEventListener(this.input, event, this.handleChange)
40 | }
41 | }
42 |
43 | public destroy() {
44 | removeEventListener(this.input, "focus", this.addFocusedClass)
45 | removeEventListener(this.input, "blur", this.removeFocusedClass)
46 |
47 | for (const event of ["keyup", "blur", "change", "input"]) {
48 | removeEventListener(this.input, event, this.handleChange)
49 | }
50 | }
51 |
52 | private handleChange = () => {
53 | if ((this.input as HTMLInputElement | HTMLTextAreaElement).value === "") {
54 | removeClass(this.element, Floatl.ACTIVE_CLASS)
55 | } else {
56 | addClass(this.element, Floatl.ACTIVE_CLASS)
57 | }
58 | }
59 |
60 | private addFocusedClass = () => {
61 | addClass(this.element, Floatl.FOCUSED_CLASS)
62 | }
63 |
64 | private removeFocusedClass = () => {
65 | removeClass(this.element, Floatl.FOCUSED_CLASS)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export function addClass(element: Element, className: string) {
2 | if (element.classList) {
3 | element.classList.add(className)
4 | } else {
5 | element.className += ` ${className}`
6 | }
7 | }
8 |
9 | export function removeClass(element: Element, className: string) {
10 | if (element.classList) {
11 | element.classList.remove(className)
12 | } else {
13 | const re = new RegExp(
14 | "(^|\\b)" + className.split(" ").join("|") + "(\\b|$)",
15 | "gi"
16 | )
17 | element.className = element.className.replace(re, " ")
18 | }
19 | }
20 |
21 | export function addEventListener(element: Element, event: string, cb: any) {
22 | if (element.addEventListener) {
23 | element.addEventListener(event, cb)
24 | } else {
25 | (element as any).attachEvent(`on${event}`, () => {
26 | cb.call(element)
27 | })
28 | }
29 | }
30 |
31 | export function removeEventListener(element: Element, event: string, cb: any) {
32 | if (element.removeEventListener) {
33 | element.removeEventListener(event, cb)
34 | } else {
35 | (element as any).detachEvent(`on${event}`, cb)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": "./src",
4 | "outDir": "./built",
5 | "target": "ES3",
6 | "sourceMap": true
7 | },
8 | "include": ["./src/**/*"],
9 | "exclude": ["**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-config-prettier"],
3 | "rules": {
4 | "semicolon": [true, "never"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------