├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── CODE_OF_CONDUCT.md
├── Documentation.md
├── LICENSE
├── README.md
├── bower.json
├── dist
├── AutoCompose.js
├── AutoCompose.js.map
├── AutoCompose.min.js
├── AutoComposeTextarea.js
├── AutoComposeTextarea.js.map
└── AutoComposeTextarea.min.js
├── example.html
├── package-lock.json
├── package.json
├── pbt_project.yml
├── rollup.config.js
└── src
├── AutoCompose.js
├── AutoComposeTextarea.js
├── Constants.js
├── OverlaySuggestion.js
├── node-utils.js
└── utils.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "commonjs": {
4 | "presets": [
5 | [ "env", { "useBuiltIns": false } ]
6 | ]
7 | },
8 | "es": {
9 | "presets": [
10 | [ "env", { "useBuiltIns": false, "modules": false } ]
11 | ]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | es
2 | lib
3 | dist
4 | docs
5 |
6 | .eslintrc
7 | node_modules
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint", // https://github.com/babel/babel-eslint
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "globals": {
9 | "expect": true,
10 | "sinon": true,
11 | "__DEV__": true
12 | },
13 | "parserOptions": {
14 | "ecmaVersion": 6,
15 | "sourceType": "module",
16 | "ecmaFeatures": {
17 | "modules": true
18 | }
19 | },
20 | "rules": {
21 | "no-var": 2, // http://eslint.org/docs/rules/no-var
22 | "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const
23 | /**
24 | * Variables
25 | */
26 | "no-shadow": 1, // http://eslint.org/docs/rules/no-shadow
27 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
28 | "no-undef": 2, // http://eslint.org/docs/rules/no-undef
29 | "no-unused-vars": [
30 | 0,
31 | { // http://eslint.org/docs/rules/no-unused-vars
32 | "vars": "local",
33 | "args": "after-used"
34 | }
35 | ],
36 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
37 | /**
38 | * Possible errors
39 | */
40 | "no-cond-assign": [
41 | 2,
42 | "always"
43 | ], // http://eslint.org/docs/rules/no-cond-assign
44 | "no-console": 1, // http://eslint.org/docs/rules/no-console
45 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
46 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert
47 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
48 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
49 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
50 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty
51 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
52 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
53 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
54 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
55 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
56 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
57 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
58 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
59 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
60 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
61 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
62 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
63 | /**
64 | * Best practices
65 | */
66 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
67 | "curly": [
68 | 2,
69 | "multi-line"
70 | ], // http://eslint.org/docs/rules/curly
71 | "default-case": 2, // http://eslint.org/docs/rules/default-case
72 | "dot-notation": [
73 | 2,
74 | { // http://eslint.org/docs/rules/dot-notation
75 | "allowKeywords": true
76 | }
77 | ],
78 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
79 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in
80 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller
81 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
82 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
83 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval
84 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
85 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
86 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
87 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
88 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
89 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
90 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
91 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
92 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
93 | "no-new": 2, // http://eslint.org/docs/rules/no-new
94 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
95 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
96 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal
97 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
98 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
99 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto
100 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
101 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
102 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
103 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
104 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
105 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
106 | "no-with": 2, // http://eslint.org/docs/rules/no-with
107 | "radix": 2, // http://eslint.org/docs/rules/radix
108 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
109 | "yoda": 2, // http://eslint.org/docs/rules/yoda
110 | /**
111 | * Style
112 | */
113 | "indent": [
114 | 2,
115 | 4,
116 | {
117 | "SwitchCase": 1
118 | }
119 | ], // http://eslint.org/docs/rules/indent
120 | "brace-style": [
121 | 2, // http://eslint.org/docs/rules/brace-style
122 | "1tbs",
123 | {
124 | "allowSingleLine": true
125 | }
126 | ],
127 | "quotes": [
128 | 2,
129 | "single",
130 | "avoid-escape" // http://eslint.org/docs/rules/quotes
131 | ],
132 | "camelcase": [
133 | 2,
134 | { // http://eslint.org/docs/rules/camelcase
135 | "properties": "never"
136 | }
137 | ],
138 | "comma-spacing": [
139 | 2,
140 | { // http://eslint.org/docs/rules/comma-spacing
141 | "before": false,
142 | "after": true
143 | }
144 | ],
145 | "comma-style": [
146 | 2,
147 | "last"
148 | ], // http://eslint.org/docs/rules/comma-style
149 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last
150 | "func-names": 0, // http://eslint.org/docs/rules/func-names
151 | "key-spacing": [
152 | 2,
153 | { // http://eslint.org/docs/rules/key-spacing
154 | "beforeColon": false,
155 | "afterColon": true
156 | }
157 | ],
158 | "new-cap": [
159 | 0,
160 | { // http://eslint.org/docs/rules/new-cap
161 | "newIsCap": true
162 | }
163 | ],
164 | "no-multiple-empty-lines": [
165 | 2,
166 | { // http://eslint.org/docs/rules/no-multiple-empty-lines
167 | "max": 2
168 | }
169 | ],
170 | "no-nested-ternary": 0, // http://eslint.org/docs/rules/no-nested-ternary
171 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
172 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
173 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
174 | "no-extra-parens": [
175 | 2,
176 | "functions"
177 | ], // http://eslint.org/docs/rules/no-extra-parens
178 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
179 | "one-var": [
180 | 2,
181 | "never"
182 | ], // http://eslint.org/docs/rules/one-var
183 | "padded-blocks": [
184 | 2,
185 | "never"
186 | ], // http://eslint.org/docs/rules/padded-blocks
187 | "semi": [
188 | 2,
189 | "always"
190 | ], // http://eslint.org/docs/rules/semi
191 | "semi-spacing": [
192 | 2,
193 | { // http://eslint.org/docs/rules/semi-spacing
194 | "before": false,
195 | "after": true
196 | }
197 | ],
198 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
199 | "space-before-function-paren": [
200 | 2,
201 | "never"
202 | ], // http://eslint.org/docs/rules/space-before-function-paren
203 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
204 | "spaced-comment": [
205 | 2,
206 | "always",
207 | { // http://eslint.org/docs/rules/spaced-comment
208 | "exceptions": [
209 | "-",
210 | "+"
211 | ],
212 | "markers": [
213 | "=",
214 | "!"
215 | ] // space here to support sprockets directives
216 | }
217 | ],
218 | // React
219 | "jsx-quotes": [
220 | 2,
221 | "prefer-double"
222 | ],
223 | "react/display-name": 0,
224 | "react/jsx-boolean-value": 1,
225 | "react/jsx-no-undef": 2,
226 | "react/jsx-sort-prop-types": 0,
227 | "react/jsx-sort-props": 0,
228 | "react/jsx-uses-react": 1,
229 | "react/jsx-uses-vars": 1,
230 | "react/no-multi-comp": 1,
231 | "react/no-unknown-property": 2,
232 | "react/prop-types": 0,
233 | "react/react-in-jsx-scope": 2,
234 | "react/self-closing-comp": 2,
235 | "react/sort-comp": 2,
236 | "react/jsx-wrap-multilines": 2
237 | },
238 | "plugins": [
239 | "react"
240 | ]
241 | }
242 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | es
3 | lib
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist
2 | example.html
3 | bower.json
4 | rollup.config.js
5 |
6 | .babelrc
7 | .eslintignore
8 | .eslintrc
9 | .gitignore
10 | .nvmrc
11 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8.11.1
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at avcs06@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/Documentation.md:
--------------------------------------------------------------------------------
1 | # Documentation
2 | ### Installation
3 | *As npm module*
4 | ```bash
5 | npm i @avcs/autocompose --save
6 | ```
7 |
8 | *As bower component*
9 | ```bash
10 | bower install avcs-autocompose --save
11 | ```
12 |
13 | *As standalone JavaScript plugin*
14 |
15 | For contenteditable
16 | ```html
17 |
18 | ```
19 |
20 | For textarea
21 | ```html
22 |
23 | ```
24 |
25 | *Importing Components*
26 | > `AutoCompose` component should be used for contenteditable fields and `AutoComposeTextarea` should be used for textarea fields. User can import any or both of the components based on the usecase.
27 |
28 | Contenteditable component
29 | ```javascript
30 | import AutoCompose from '@avcs/autosuggest'
31 | ```
32 |
33 | ES6 Textarea component
34 | ```javascript
35 | import AutoComposeTextarea from '@avcs/autosuggest/es/AutoComposeTextarea'
36 | ```
37 |
38 | Vannila Textarea component
39 | ```javascript
40 | import AutoComposeTextarea from '@avcs/autosuggest/lib/AutoComposeTextarea'
41 | ```
42 |
43 | ## AutoCompose or AutoComposeTextarea
44 | > All the options and methods are same for both `AutoCompose` and `AutoComposeTextarea`, only internal implementation changes. The examples here only use `AutoCompose` but same examples can be used for `AutoComposeTextarea` too.
45 | ### Initialization
46 | ```javascript
47 | var instance = new AutoCompose(options, ...inputFields);
48 | ```
49 |
50 | ### Options
51 | **`composer`**: `Function < value:string, callback: Function < suggestion: string > >` *(required)*
52 | - **`value`**: Current value of the field. For textarea it's the input value. For contenteditable it's text content.
53 | - **`callback`**: Callback accepts a parameter `suggestion`, after processing the input value any suggestion should be provided through the callback.
54 |
55 | > Please call the callback with no input `callback()`, if you dont want to show any suggestion for the current input. Dangling it might cause memory leak.
56 |
57 | **`onChange`**: `Function < change: Object < suggestion: string, acceptedSuggestion: string > >` *(optional)*
58 | onChange event is triggered when user accepts a suggestion fully or partially, `change.acceptedSuggestion` provides the accepted part of the suggestion, while `change.suggestion` provides the full suggestion.
59 |
60 | **`onReject`**: `Function < change: Object < suggestion: string > >` *(optional)*
61 | onReject event is triggered when user skips or rejects the current suggestion. `change.suggestion` provides the full suggestion that was rejected.
62 |
63 | ### Methods
64 | **`addInputs`**: `Function < ...inputFields < DOMElement | Array < DOMELement > | Iterable < DOMElement > >`
65 | Enable the autocompose on new input fields after the instantiation.
66 |
67 | **`removeInputs`**: `Function < ...inputFields < DOMElement | Array < DOMELement > | Iterable < DOMElement > >`
68 | Disable the autocompose on the input fields.
69 |
70 | **`destroy`**: `Function < >`
71 | Disable autocompose on all input fields.
72 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Chandrasekhar Ambula V
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AutoCompose
2 | A JavaScript plugin to provide UI support for Gmail like smart compose in textarea and contenteditable.
3 |
4 | ### [Demo](https://avcs.pro/autocompose) | [Documentation](Documentation.md)
5 |
6 | # Features
7 | ### General
8 | 1. Supports textarea and contenteditable fields
9 | 2. No external dependencies like jquery or bootstrap
10 | 3. Can add and remove inputs dynamically.
11 | 4. **DOES NOT** provide any suggestion algorithms or database, you have to provide the `composer`, a method which takes in current value and returns the suggestion.
12 |
13 | ### Textarea
14 | 1. Provides a completely different Component `AutoComposeTextarea` to support textarea.
15 | 2. Uses overlay to show the suggestion.
16 |
17 | ### Contenteditable
18 | 1. Inserts the suggestion into HTML directly.
19 | 2. Current text style will be applied to the suggestion too.
20 |
21 | ### Suggestion
22 | 1. User can accept partial suggestion, by placing the cursor in the middle of the suggestion.
23 | 2. User can accept the full suggestion by placing the cursor at the end of suggestion or using keys `Tab`, `Left arrow` or `Down arrow`.
24 | 3. OnChange event provides `acceptedSuggestion`, which gives the partial suggestion if user accepts only partial suggestion.
25 | 4. OnReject event will be triggered if the shown suggestion is not accepted by user.
26 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "avcs-autocompose",
3 | "description": "A JavaScript plugin to provide UI support for Gmail like smart compose in textarea and contenteditable.",
4 | "main": [
5 | "dist/AutoCompose.js",
6 | "dist/AutoComposeTextarea.js"
7 | ],
8 | "authors": [
9 | "AvcS"
10 | ],
11 | "license": "MIT",
12 | "keywords": [
13 | "smart compose",
14 | "textarea",
15 | "contenteditable"
16 | ],
17 | "homepage": "https://avcs.pro/autocompose",
18 | "ignore": [
19 | "**/.*",
20 | "es",
21 | "lib",
22 | "node_modules",
23 | "bower_components",
24 | "rollup.config.js",
25 | "example.html"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/dist/AutoCompose.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (global.AutoCompose = factory());
5 | }(this, (function () { 'use strict';
6 |
7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
8 | return typeof obj;
9 | } : function (obj) {
10 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
11 | };
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | var classCallCheck = function (instance, Constructor) {
24 | if (!(instance instanceof Constructor)) {
25 | throw new TypeError("Cannot call a class as a function");
26 | }
27 | };
28 |
29 | var createClass = function () {
30 | function defineProperties(target, props) {
31 | for (var i = 0; i < props.length; i++) {
32 | var descriptor = props[i];
33 | descriptor.enumerable = descriptor.enumerable || false;
34 | descriptor.configurable = true;
35 | if ("value" in descriptor) descriptor.writable = true;
36 | Object.defineProperty(target, descriptor.key, descriptor);
37 | }
38 | }
39 |
40 | return function (Constructor, protoProps, staticProps) {
41 | if (protoProps) defineProperties(Constructor.prototype, protoProps);
42 | if (staticProps) defineProperties(Constructor, staticProps);
43 | return Constructor;
44 | };
45 | }();
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | var toConsumableArray = function (arr) {
88 | if (Array.isArray(arr)) {
89 | for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
90 |
91 | return arr2;
92 | } else {
93 | return Array.from(arr);
94 | }
95 | };
96 |
97 | // Invisible character
98 |
99 |
100 | var FONT_PROPERTIES = [
101 | // https://developer.mozilla.org/en-US/docs/Web/CSS/font
102 | 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', // might not make a difference, but better be safe
103 |
104 | 'letterSpacing', 'wordSpacing', 'tabSize', 'MozTabSize', 'whiteSpace', 'wordWrap', 'wordBreak'];
105 |
106 | var HOST_PROPERTIES = [].concat(FONT_PROPERTIES, ['direction', 'boxSizing', 'borderRightWidth', 'borderLeftWidth', 'paddingRight', 'paddingLeft']);
107 |
108 | var CLONE_PROPERTIES = [].concat(toConsumableArray(HOST_PROPERTIES), ['width', 'overflowX', 'overflowY', 'borderTopWidth', 'borderBottomWidth', 'borderStyle', 'paddingTop', 'paddingBottom', 'lineHeight']);
109 |
110 |
111 |
112 | var INLINE_SUGGESTION_ID = '___autocompose_inline_suggestion___';
113 |
114 | var ensure = function ensure(context, object, keys) {
115 | [].concat(keys).forEach(function (key) {
116 | if (typeof object[key] === 'undefined') {
117 | throw new Error('AutoCompose: Missing required parameter, ' + context + '.' + key);
118 | }
119 | });
120 | };
121 |
122 |
123 |
124 | var ensureType = function ensureType(context, object, key, type) {
125 | [].concat(object[key]).forEach(function (value) {
126 | var valueType = typeof value === 'undefined' ? 'undefined' : _typeof(value);
127 | if (valueType !== type && valueType !== 'undefined') {
128 | throw new TypeError('AutoCompose: Invalid Type for ' + context + '.' + key + ', expected ' + type);
129 | }
130 | });
131 | };
132 |
133 |
134 |
135 |
136 |
137 | var data = function data(element, key, value) {
138 | key = 'autosuggest_' + key;
139 | if (typeof value !== 'undefined') {
140 | element.dataset[key] = JSON.stringify(value);
141 | } else {
142 | value = element.dataset[key];
143 | return typeof value !== 'undefined' ? JSON.parse(element.dataset[key]) : value;
144 | }
145 | };
146 |
147 | var getSelectedTextNodes = function getSelectedTextNodes() {
148 | var selection = window.getSelection();
149 | if (!selection.isCollapsed) return {};
150 |
151 | var _selection$getRangeAt = selection.getRangeAt(0),
152 | node = _selection$getRangeAt.startContainer,
153 | offset = _selection$getRangeAt.startOffset;
154 |
155 | if (node.nodeType !== node.TEXT_NODE) {
156 | try {
157 | node = getFirstChildNode(node.childNodes[offset]);
158 | offset = 0;
159 | } catch (e) {
160 | try {
161 | node = getLastChildNode(node.childNodes[offset - 1]);
162 | offset = node.nodeValue ? node.nodeValue.length : null;
163 | } catch (e) {}
164 | }
165 | }
166 |
167 | return { node: node, offset: offset };
168 | };
169 |
170 | var createNode = function createNode(html) {
171 | var div = document.createElement('div');
172 | div.innerHTML = html.trim();
173 | return div.firstChild;
174 | };
175 |
176 | var getFirstChildNode = function getFirstChildNode(node) {
177 | var nextNode = node;
178 | while (nextNode.firstChild) {
179 | nextNode = nextNode.firstChild;
180 | }return nextNode;
181 | };
182 |
183 | var getLastChildNode = function getLastChildNode(node) {
184 | var nextNode = node;
185 | while (nextNode.lastChild) {
186 | nextNode = nextNode.lastChild;
187 | }return nextNode;
188 | };
189 |
190 | var getNextNode = function getNextNode(node, root) {
191 | var nextNode = void 0;
192 | if (node.nextSibling) nextNode = node.nextSibling;else {
193 | nextNode = node.parentNode;
194 | while (nextNode !== root && !nextNode.nextSibling) {
195 | nextNode = nextNode.parentNode;
196 | }if (nextNode && nextNode !== root) nextNode = nextNode.nextSibling;else return;
197 | }
198 |
199 | return getFirstChildNode(nextNode);
200 | };
201 |
202 | var getPrevNode = function getPrevNode(node, root) {
203 | var prevNode = void 0;
204 | if (node.previousSibling) prevNode = node.previousSibling;else {
205 | prevNode = node.parentNode;
206 | while (prevNode !== root && !prevNode.previousSibling) {
207 | prevNode = prevNode.parentNode;
208 | }if (prevNode && prevNode !== root) prevNode = prevNode.previousSibling;else return;
209 | }
210 |
211 | return getLastChildNode(prevNode);
212 | };
213 |
214 |
215 |
216 | var getNodeValue = function getNodeValue(node) {
217 | if (node.tagName && node.tagName === 'BR') return '\n';
218 | return node.nodeValue || '';
219 | };
220 |
221 | var setSelection = function setSelection(callback) {
222 | var selection = window.getSelection();
223 | var range = document.createRange();
224 | callback(range);
225 | selection.removeAllRanges();
226 | selection.addRange(range);
227 | };
228 |
229 | var AutoCompose = function () {
230 | function AutoCompose(options) {
231 | var _this = this;
232 |
233 | classCallCheck(this, AutoCompose);
234 |
235 | if (!options) throw new Error('AutoCompose: Missing required parameter, options');
236 |
237 | if (typeof options === 'function') options = { composer: options };
238 |
239 | this.inputs = [];
240 | this.onChange = options.onChange || Function.prototype;
241 | this.onReject = options.onReject || Function.prototype;
242 |
243 | ensure('AutoCompose', options, 'composer');
244 | ensureType('AutoCompose', options, 'composer', 'function');
245 | this.composer = options.composer;
246 |
247 | events: {
248 | var self = this;
249 | var handledInKeyDown = false;
250 | var activeElement = null;
251 | var suggestionNode = null;
252 | var activeSuggestion = null;
253 |
254 | var clearSuggestion = function clearSuggestion(normalize) {
255 | var parentNode = suggestionNode.parentNode;
256 | parentNode.removeChild(suggestionNode);
257 | normalize && parentNode.normalize();
258 | suggestionNode = activeSuggestion = activeElement = null;
259 | };
260 |
261 | var acceptSuggestion = function acceptSuggestion(ignoreCursor) {
262 | var suggestion = suggestionNode.firstChild.nodeValue;
263 | suggestionNode.parentNode.insertBefore(suggestionNode.firstChild, suggestionNode);
264 | var insertedNode = suggestionNode.previousSibling;
265 |
266 | _this.onChange.call(activeElement, {
267 | suggestion: activeSuggestion,
268 | acceptedSuggestion: suggestion
269 | });
270 |
271 | clearSuggestion();
272 | !ignoreCursor && setSelection(function (range) {
273 | range.setStartAfter(insertedNode);
274 | range.setEndAfter(insertedNode);
275 | });
276 | };
277 |
278 | var rejectSuggestion = function rejectSuggestion() {
279 | _this.onReject.call(activeElement, { suggestion: activeSuggestion });
280 | clearSuggestion();
281 | };
282 |
283 | var isSuggestionTextNode = function isSuggestionTextNode(node) {
284 | return node.parentNode === suggestionNode;
285 | };
286 | var isAfterSuggestionNode = function isAfterSuggestionNode(node) {
287 | while ((node = getPrevNode(node, activeElement)) && !isSuggestionTextNode(node)) {}
288 | return Boolean(node);
289 | };
290 |
291 | this.onBlurHandler = function () {
292 | return suggestionNode && clearSuggestion(true);
293 | };
294 | this.onKeyDownHandler = function (e) {
295 | if (suggestionNode) {
296 | if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {
297 | acceptSuggestion();
298 | handledInKeyDown = true;
299 | e.preventDefault();
300 | }
301 | }
302 | };
303 |
304 | var keyUpIndex = 0;
305 | this.onKeyUpHandler = function (e) {
306 | var _this2 = this;
307 |
308 | if (e.type === 'keyup' && handledInKeyDown) {
309 | handledInKeyDown = false;
310 | return;
311 | }
312 |
313 | var _getSelectedTextNodes = getSelectedTextNodes(),
314 | textNode = _getSelectedTextNodes.node,
315 | offset = _getSelectedTextNodes.offset;
316 |
317 | if (!textNode) return suggestionNode && rejectSuggestion();
318 |
319 | var isSuggestionNode = isSuggestionTextNode(textNode);
320 | if (e.type === 'mouseup' && suggestionNode) {
321 | if (isSuggestionNode && offset) {
322 | textNode.nodeValue = textNode.nodeValue.slice(0, offset);
323 | return acceptSuggestion();
324 | } else if (isAfterSuggestionNode(textNode)) {
325 | return acceptSuggestion(true);
326 | }
327 | }
328 |
329 | if (isSuggestionNode) {
330 | try {
331 | textNode = getPrevNode(suggestionNode, this);
332 | offset = textNode.nodeValue.length;
333 | } catch (e) {
334 | textNode = getNextNode(suggestionNode, this);
335 | offset = 0;
336 | }
337 | }
338 |
339 | suggestionNode && rejectSuggestion();
340 | if (textNode.nodeType !== textNode.TEXT_NODE) return;
341 |
342 | postValue: {
343 | var postValue = textNode.nodeValue.slice(offset);
344 | if (postValue.trim()) return;
345 |
346 | var node = textNode;
347 | while (node = getNextNode(node, this)) {
348 | postValue += getNodeValue(node);
349 | if (postValue.trim()) return;
350 | }
351 | }
352 |
353 | var preValue = '';
354 | preValue: {
355 | preValue = textNode.nodeValue.slice(0, offset);
356 |
357 | var _node = textNode;
358 | while (_node = getPrevNode(_node, this)) {
359 | preValue = getNodeValue(_node) + preValue;
360 | }
361 | }
362 |
363 | handlesuggestion: {
364 | keyUpIndex++;
365 | (function (asyncReference) {
366 | self.composer.call(_this2, preValue, function (result) {
367 | if (!result || asyncReference !== keyUpIndex) return;
368 | activeElement = _this2;
369 |
370 | var textAfterCursor = textNode.nodeValue.slice(offset);
371 | var parentNode = textNode.parentNode;
372 | var referenceNode = textNode.nextSibling;
373 |
374 | textNode.nodeValue = textNode.nodeValue.slice(0, offset);
375 | parentNode.insertBefore(document.createTextNode(textAfterCursor), referenceNode);
376 |
377 | activeSuggestion = result;
378 | suggestionNode = createNode('' + result + '');
379 | suggestionNode.style.opacity = 0.7;
380 | suggestionNode.id = INLINE_SUGGESTION_ID;
381 | parentNode.insertBefore(suggestionNode, referenceNode);
382 |
383 | setSelection(function (range) {
384 | range.setStartBefore(suggestionNode);
385 | range.setEndBefore(suggestionNode);
386 | });
387 | });
388 | })(keyUpIndex);
389 | }
390 | };
391 | }
392 |
393 | // initialize events on inputs
394 |
395 | for (var _len = arguments.length, inputs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
396 | inputs[_key - 1] = arguments[_key];
397 | }
398 |
399 | this.addInputs.apply(this, inputs);
400 | }
401 |
402 | createClass(AutoCompose, [{
403 | key: 'addInputs',
404 | value: function addInputs() {
405 | var _this3 = this;
406 |
407 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
408 | args[_key2] = arguments[_key2];
409 | }
410 |
411 | var inputs = Array.prototype.concat.apply([], args.map(function (d) {
412 | return d[0] ? Array.prototype.slice.call(d, 0) : d;
413 | }));
414 |
415 | inputs.forEach(function (input) {
416 | // validate element
417 | if (!input.isContentEditable) {
418 | throw new Error('AutoCompose: Invalid input: only contenteditable elements are supported');
419 | }
420 |
421 | // init events
422 | input.addEventListener('blur', _this3.onBlurHandler);
423 | input.addEventListener('keyup', _this3.onKeyUpHandler);
424 | input.addEventListener('mouseup', _this3.onKeyUpHandler);
425 | input.addEventListener('keydown', _this3.onKeyDownHandler, true);
426 |
427 | data(input, 'index', _this3.inputs.push(input) - 1);
428 | });
429 | }
430 | }, {
431 | key: 'removeInputs',
432 | value: function removeInputs() {
433 | var _this4 = this;
434 |
435 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
436 | args[_key3] = arguments[_key3];
437 | }
438 |
439 | var inputs = Array.prototype.concat.apply([], args.map(function (d) {
440 | return d[0] ? Array.prototype.slice.call(d, 0) : d;
441 | }));
442 |
443 | inputs.forEach(function (input) {
444 | var index = data(input, 'index');
445 | if (!isNaN(index)) {
446 | _this4.inputs.splice(index, 1);
447 |
448 | // destroy events
449 | input.removeEventListener('blur', _this4.onBlurHandler);
450 | input.removeEventListener('keyup', _this4.onKeyUpHandler);
451 | input.removeEventListener('mouseup', _this4.onKeyUpHandler);
452 | input.removeEventListener('keydown', _this4.onKeyDownHandler, true);
453 | }
454 | });
455 | }
456 | }, {
457 | key: 'destroy',
458 | value: function destroy() {
459 | this.removeInputs(this.inputs);
460 | }
461 | }]);
462 | return AutoCompose;
463 | }();
464 |
465 | return AutoCompose;
466 |
467 | })));
468 | //# sourceMappingURL=AutoCompose.js.map
469 |
--------------------------------------------------------------------------------
/dist/AutoCompose.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"AutoCompose.js","sources":["../src/constants.js","../src/utils.js","../src/node-utils.js","../src/AutoCompose.js"],"sourcesContent":["// Invisible character\nexport const POSITIONER_CHARACTER = \"\\ufeff\";\n\nexport const FONT_PROPERTIES = [\n // https://developer.mozilla.org/en-US/docs/Web/CSS/font\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'fontFamily',\n\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration', // might not make a difference, but better be safe\n\n 'letterSpacing',\n 'wordSpacing',\n\n 'tabSize',\n 'MozTabSize',\n\n 'whiteSpace',\n 'wordWrap',\n 'wordBreak'\n];\n\nexport const HOST_PROPERTIES = [\n ...FONT_PROPERTIES,\n 'direction',\n 'boxSizing',\n\n 'borderRightWidth',\n 'borderLeftWidth',\n\n 'paddingRight',\n 'paddingLeft',\n];\n\nexport const CLONE_PROPERTIES = [\n ...HOST_PROPERTIES,\n 'width',\n\n 'overflowX',\n 'overflowY',\n\n 'borderTopWidth',\n 'borderBottomWidth',\n 'borderStyle',\n\n 'paddingTop',\n 'paddingBottom',\n\n 'lineHeight',\n];\n\nexport const FILLER = ' ';\n\nexport const INLINE_SUGGESTION_ID = '___autocompose_inline_suggestion___';\n","export const ensure = (context, object, keys) => {\n [].concat(keys).forEach(key => {\n if (typeof object[key] === 'undefined') {\n throw new Error(`AutoCompose: Missing required parameter, ${context}.${key}`);\n }\n });\n};\n\nexport const ensureAnyOf = (context, object, keys) => {\n let currentKey;\n if (!keys.some(key => (\n typeof object[currentKey = key] !== 'undefined'\n ))) throw new Error(`AutoCompose: Missing required parameter, ${context}.${currentKey}`);\n};\n\nexport const ensureType = (context, object, key, type) => {\n [].concat(object[key]).forEach(value => {\n const valueType = typeof value;\n if (valueType !== type && valueType !== 'undefined') {\n throw new TypeError(`AutoCompose: Invalid Type for ${context}.${key}, expected ${type}`);\n }\n });\n};\n\nexport const getCursorPosition = input => {\n return [input.selectionStart, input.selectionEnd].sort((a, b) => a - b);\n};\n\nexport const makeAsyncQueueRunner = () => {\n let i = 0;\n let queue = [];\n\n return (f, j) => {\n queue[j - i] = f;\n while (queue[0]) ++i, queue.shift()();\n };\n};\n\nexport const data = (element, key, value) => {\n key = 'autosuggest_' + key;\n if (typeof value !== 'undefined') {\n element.dataset[key] = JSON.stringify(value);\n } else {\n value = element.dataset[key];\n return typeof value !== 'undefined' ? JSON.parse(element.dataset[key]) : value;\n }\n};\n\nexport const getScrollbarWidth = () => {\n // Creating invisible container\n const outer = document.createElement('div');\n outer.style.visibility = 'hidden';\n outer.style.overflow = 'scroll'; // forcing scrollbar to appear\n outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps\n document.body.appendChild(outer);\n\n // Creating inner element and placing it in the container\n const inner = document.createElement('div');\n outer.appendChild(inner);\n\n // Calculating difference between container's full width and the child width\n const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);\n\n // Removing temporary elements from the DOM\n outer.parentNode.removeChild(outer);\n return scrollbarWidth;\n};\n","export const getGlobalOffset = $0 => {\n let node = $0, top = 0, left = 0;\n\n do {\n left += node.offsetLeft;\n top += node.offsetTop;\n } while (node = node.offsetParent);\n\n return { left, top };\n};\n\nexport const getSelectedTextNodes = () => {\n const selection = window.getSelection();\n if (!selection.isCollapsed) return {};\n\n let { startContainer: node, startOffset: offset } = selection.getRangeAt(0);\n if (node.nodeType !== node.TEXT_NODE) {\n try {\n node = getFirstChildNode(node.childNodes[offset]);\n offset = 0;\n } catch (e) {\n try {\n node = getLastChildNode(node.childNodes[offset - 1]);\n offset = node.nodeValue ? node.nodeValue.length : null;\n } catch(e) {}\n }\n }\n\n return { node, offset };\n};\n\nexport const createNode = html => {\n var div = document.createElement('div');\n div.innerHTML = html.trim();\n return div.firstChild;\n};\n\nexport const getFirstChildNode = node => {\n let nextNode = node;\n while (nextNode.firstChild) nextNode = nextNode.firstChild;\n return nextNode;\n};\n\nexport const getLastChildNode = node => {\n let nextNode = node;\n while (nextNode.lastChild) nextNode = nextNode.lastChild;\n return nextNode;\n};\n\nexport const getNextNode = (node, root) => {\n let nextNode;\n if (node.nextSibling)\n nextNode = node.nextSibling;\n else {\n nextNode = node.parentNode;\n while (nextNode !== root && !nextNode.nextSibling)\n nextNode = nextNode.parentNode;\n if (nextNode && nextNode !== root)\n nextNode = nextNode.nextSibling\n else return;\n }\n\n return getFirstChildNode(nextNode);\n};\n\nexport const getPrevNode = (node, root) => {\n let prevNode;\n if (node.previousSibling)\n prevNode = node.previousSibling;\n else {\n prevNode = node.parentNode;\n while (prevNode !== root && !prevNode.previousSibling)\n prevNode = prevNode.parentNode;\n if (prevNode && prevNode !== root)\n prevNode = prevNode.previousSibling\n else return;\n }\n\n return getLastChildNode(prevNode);\n};\n\nexport const removeNodesBetween = (startContainer, endContainer) => {\n if (startContainer === endContainer) return;\n let node = getNextNode(startContainer);\n while (node !== endContainer) {\n node.parentNode.removeChild(node);\n node = getNextNode(startContainer);\n }\n};\n\nexport const getNodeValue = node => {\n if (node.tagName && node.tagName === 'BR')\n return '\\n';\n return node.nodeValue || '';\n};\n\nexport const setSelection = callback => {\n const selection = window.getSelection();\n const range = document.createRange();\n callback(range);\n selection.removeAllRanges();\n selection.addRange(range);\n};","import { INLINE_SUGGESTION_ID } from './constants';\nimport { data, ensure, ensureType } from './utils';\nimport {\n getSelectedTextNodes,\n getNodeValue,\n setSelection,\n getPrevNode,\n getNextNode,\n createNode,\n} from './node-utils';\n\nclass AutoCompose {\n constructor(options, ...inputs) {\n if (!options)\n throw new Error(`AutoCompose: Missing required parameter, options`);\n\n if (typeof options === 'function')\n options = { composer: options };\n\n this.inputs = [];\n this.onChange = options.onChange || Function.prototype;\n this.onReject = options.onReject || Function.prototype;\n\n ensure('AutoCompose', options, 'composer');\n ensureType('AutoCompose', options, 'composer', 'function');\n this.composer = options.composer;\n\n events: {\n const self = this;\n let handledInKeyDown = false;\n let activeElement = null;\n let suggestionNode = null;\n let activeSuggestion = null;\n\n const clearSuggestion = normalize => {\n const parentNode = suggestionNode.parentNode;\n parentNode.removeChild(suggestionNode);\n normalize && parentNode.normalize();\n suggestionNode = activeSuggestion = activeElement = null;\n };\n\n const acceptSuggestion = ignoreCursor => {\n const suggestion = suggestionNode.firstChild.nodeValue;\n suggestionNode.parentNode.insertBefore(suggestionNode.firstChild, suggestionNode);\n const insertedNode = suggestionNode.previousSibling;\n\n this.onChange.call(activeElement, {\n suggestion: activeSuggestion,\n acceptedSuggestion: suggestion\n });\n\n clearSuggestion();\n !ignoreCursor && setSelection(range => {\n range.setStartAfter(insertedNode);\n range.setEndAfter(insertedNode);\n });\n };\n\n const rejectSuggestion = () => {\n this.onReject.call(activeElement, { suggestion: activeSuggestion });\n clearSuggestion();\n };\n\n const isSuggestionTextNode = node => node.parentNode === suggestionNode;\n const isAfterSuggestionNode = node => {\n while ((node = getPrevNode(node, activeElement)) && !isSuggestionTextNode(node));\n return Boolean(node);\n };\n\n this.onBlurHandler = () => suggestionNode && clearSuggestion(true);\n this.onKeyDownHandler = function (e) {\n if (suggestionNode) {\n if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {\n acceptSuggestion();\n handledInKeyDown = true;\n e.preventDefault();\n }\n }\n };\n\n let keyUpIndex = 0;\n this.onKeyUpHandler = function (e) {\n if (e.type === 'keyup' && handledInKeyDown) {\n handledInKeyDown = false;\n return;\n }\n\n let { node: textNode, offset } = getSelectedTextNodes();\n if (!textNode) return suggestionNode && rejectSuggestion();\n\n const isSuggestionNode = isSuggestionTextNode(textNode);\n if (e.type === 'mouseup' && suggestionNode) {\n if (isSuggestionNode && offset) {\n textNode.nodeValue = textNode.nodeValue.slice(0, offset);\n return acceptSuggestion();\n } else if (isAfterSuggestionNode(textNode)) {\n return acceptSuggestion(true);\n }\n }\n\n if (isSuggestionNode) {\n try {\n textNode = getPrevNode(suggestionNode, this);\n offset = textNode.nodeValue.length;\n } catch(e) {\n textNode = getNextNode(suggestionNode, this);\n offset = 0;\n }\n }\n\n suggestionNode && rejectSuggestion();\n if (textNode.nodeType !== textNode.TEXT_NODE) return;\n\n postValue: {\n let postValue = textNode.nodeValue.slice(offset);\n if (postValue.trim()) return;\n\n let node = textNode;\n while (node = getNextNode(node, this)) {\n postValue += getNodeValue(node);\n if (postValue.trim()) return;\n }\n }\n\n let preValue = '';\n preValue: {\n preValue = textNode.nodeValue.slice(0, offset);\n\n let node = textNode;\n while (node = getPrevNode(node, this)) {\n preValue = getNodeValue(node) + preValue;\n }\n }\n\n handlesuggestion: {\n keyUpIndex++;\n (asyncReference => {\n self.composer.call(this, preValue, result => {\n if (!result || asyncReference !== keyUpIndex) return;\n activeElement = this;\n\n const textAfterCursor = textNode.nodeValue.slice(offset);\n const parentNode = textNode.parentNode;\n const referenceNode = textNode.nextSibling;\n\n textNode.nodeValue = textNode.nodeValue.slice(0, offset);\n parentNode.insertBefore(document.createTextNode(textAfterCursor), referenceNode);\n\n activeSuggestion = result;\n suggestionNode = createNode(`${result}`);\n suggestionNode.style.opacity = 0.7;\n suggestionNode.id = INLINE_SUGGESTION_ID;\n parentNode.insertBefore(suggestionNode, referenceNode);\n\n setSelection(range => {\n range.setStartBefore(suggestionNode);\n range.setEndBefore(suggestionNode);\n });\n });\n })(keyUpIndex);\n }\n };\n }\n\n // initialize events on inputs\n this.addInputs(...inputs);\n }\n\n addInputs(...args) {\n const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));\n\n inputs.forEach(input => {\n // validate element\n if (!input.isContentEditable) {\n throw new Error('AutoCompose: Invalid input: only contenteditable elements are supported');\n }\n\n // init events\n input.addEventListener('blur', this.onBlurHandler);\n input.addEventListener('keyup', this.onKeyUpHandler);\n input.addEventListener('mouseup', this.onKeyUpHandler);\n input.addEventListener('keydown', this.onKeyDownHandler, true);\n\n data(input, 'index', this.inputs.push(input) - 1);\n });\n }\n\n removeInputs(...args) {\n const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));\n\n inputs.forEach(input => {\n const index = data(input, 'index');\n if (!isNaN(index)) {\n this.inputs.splice(index, 1);\n\n // destroy events\n input.removeEventListener('blur', this.onBlurHandler);\n input.removeEventListener('keyup', this.onKeyUpHandler);\n input.removeEventListener('mouseup', this.onKeyUpHandler);\n input.removeEventListener('keydown', this.onKeyDownHandler, true);\n }\n });\n }\n\n destroy() {\n this.removeInputs(this.inputs);\n }\n}\n\nexport default AutoCompose;\n"],"names":["FONT_PROPERTIES","HOST_PROPERTIES","CLONE_PROPERTIES","INLINE_SUGGESTION_ID","ensure","context","object","keys","concat","forEach","key","Error","ensureType","type","valueType","value","TypeError","data","element","dataset","JSON","stringify","parse","getSelectedTextNodes","selection","window","getSelection","isCollapsed","getRangeAt","node","startContainer","offset","startOffset","nodeType","TEXT_NODE","getFirstChildNode","childNodes","e","getLastChildNode","nodeValue","length","createNode","div","document","createElement","innerHTML","html","trim","firstChild","nextNode","lastChild","getNextNode","root","nextSibling","parentNode","getPrevNode","prevNode","previousSibling","getNodeValue","tagName","setSelection","range","createRange","removeAllRanges","addRange","AutoCompose","options","composer","inputs","onChange","Function","prototype","onReject","self","handledInKeyDown","activeElement","suggestionNode","activeSuggestion","clearSuggestion","removeChild","normalize","acceptSuggestion","suggestion","insertBefore","insertedNode","call","ignoreCursor","setStartAfter","setEndAfter","rejectSuggestion","isSuggestionTextNode","isAfterSuggestionNode","Boolean","onBlurHandler","onKeyDownHandler","keyCode","preventDefault","keyUpIndex","onKeyUpHandler","textNode","isSuggestionNode","slice","postValue","preValue","result","asyncReference","textAfterCursor","referenceNode","createTextNode","style","opacity","id","setStartBefore","setEndBefore","addInputs","args","Array","apply","map","d","input","isContentEditable","addEventListener","push","index","isNaN","splice","removeEventListener","removeInputs"],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/AutoCompose.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.AutoCompose=t()}(this,function(){"use strict";var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e=function(e,t,n){return t&&o(e.prototype,t),n&&o(e,n),e};function o(e,t){for(var n=0;n"+(g=e)+"")).style.opacity=.7,m.id="___autocompose_inline_suggestion___",n.insertBefore(m,o),w(function(e){e.setStartBefore(m),e.setEndBefore(m)})}})}}}};for(var t=arguments.length,n=Array(1 '));
301 | span.firstChild.style.fontSize = '1px';
302 |
303 | var textNode = document.createTextNode('');
304 | span.appendChild(textNode);
305 |
306 | document.body.appendChild(span);
307 |
308 | var crossed = false,
309 | lastSpaceAt = -1;
310 | var suggestionLength = this.suggestion.length;
311 | for (var i = 0; i < suggestionLength; i++) {
312 | if (!crossed) {
313 | var text = this.suggestion[i];
314 | if (/\s/.test(text)) lastSpaceAt = i;
315 | textNode.nodeValue += this.suggestion[i];
316 | crossed = span.offsetWidth > firstLineWidth;
317 | if (crossed) {
318 | for (var j = lastSpaceAt + 2; j <= i; j++) {
319 | this.content.childNodes[j].style.lineHeight = elementStyles.lineHeight;
320 | }
321 | }
322 | }
323 | if (crossed) {
324 | this.content.childNodes[i + 1].style.lineHeight = elementStyles.lineHeight;
325 | }
326 | }
327 | if (crossed) {
328 | this.content.lastChild.style.lineHeight = elementStyles.lineHeight;
329 | }
330 | document.body.removeChild(span);
331 | }
332 |
333 | this.host.style.display = 'block';
334 | this.isActive = true;
335 | }
336 | }, {
337 | key: 'hide',
338 | value: function hide() {
339 | this.host.style.display = 'none';
340 | this.isActive = false;
341 | }
342 | }, {
343 | key: 'empty',
344 | value: function empty() {
345 | this.isEmpty = true;
346 | while (this.content.firstChild) {
347 | this.content.removeChild(this.content.firstChild);
348 | }
349 | }
350 | }, {
351 | key: 'fill',
352 | value: function fill(suggestion, onSet) {
353 | var _this2 = this;
354 |
355 | this.empty();
356 | this.suggestion = suggestion;
357 |
358 | this.content.appendChild(createNode(' '));
359 | this.content.firstChild.style.fontSize = '1px';
360 |
361 | suggestion.split('').concat(FILLER).forEach(function (char, i) {
362 | var charNode = createNode('' + char + '');
363 | charNode.style.opacity = 0.7;
364 | charNode.style.lineHeight = 1.5;
365 | charNode.style.pointerEvents = 'all';
366 | _this2.content.appendChild(charNode);
367 |
368 | charNode.addEventListener('mousedown', function (e) {
369 | onSet(suggestion.slice(0, i + 1));
370 | _this2.hide();
371 |
372 | e.preventDefault();
373 | e.stopPropagation();
374 | });
375 | });
376 |
377 | this.isEmpty = false;
378 | }
379 | }, {
380 | key: 'getValue',
381 | value: function getValue() {
382 | return this.suggestion;
383 | }
384 | }]);
385 | return OverlaySuggestion;
386 | }();
387 |
388 | function getCaretPosition(element) {
389 | var _getCursorPosition = getCursorPosition(element),
390 | _getCursorPosition2 = slicedToArray(_getCursorPosition, 1),
391 | cursorPosition = _getCursorPosition2[0];
392 |
393 | // pre to retain special characters
394 |
395 |
396 | var clone = document.createElement('pre');
397 | clone.id = 'autocompose-positionclone';
398 |
399 | var positioner = document.createElement('span');
400 | positioner.appendChild(document.createTextNode(POSITIONER_CHARACTER));
401 |
402 | var computed = window.getComputedStyle(element);
403 | CLONE_PROPERTIES.forEach(function (prop) {
404 | clone.style[prop] = computed[prop];
405 | });
406 |
407 | var elementPosition = getGlobalOffset(element);
408 | clone.style.opacity = 0;
409 | clone.style.position = 'absolute';
410 | clone.style.top = elementPosition.top + 'px';
411 | clone.style.left = elementPosition.left + 'px';
412 | document.body.appendChild(clone);
413 |
414 | if (element.scrollHeight > parseInt(computed.height)) clone.style.overflowY = 'scroll';else clone.style.overflowY = 'hidden';
415 |
416 | clone.appendChild(document.createTextNode(element.value.slice(0, cursorPosition)));
417 | clone.appendChild(positioner);
418 | clone.style.maxWidth = '100%';
419 |
420 | var caretPosition = getGlobalOffset(positioner);
421 | caretPosition.top -= element.scrollTop;
422 | caretPosition.left -= element.scrollLeft;
423 | document.body.removeChild(clone);
424 | return caretPosition;
425 | }
426 |
427 | var setValue = function setValue(_ref) {
428 | var element = _ref.element,
429 | suggestion = _ref.suggestion,
430 | fullSuggestion = _ref.fullSuggestion,
431 | onChange = _ref.onChange;
432 |
433 | var _getCursorPosition3 = getCursorPosition(element),
434 | _getCursorPosition4 = slicedToArray(_getCursorPosition3, 1),
435 | startPosition = _getCursorPosition4[0];
436 |
437 | var originalValue = element.value;
438 | var value = originalValue.slice(0, startPosition) + suggestion;
439 |
440 | element.value = value + originalValue.slice(startPosition);
441 | element.focus();
442 |
443 | var cursorPosition = value.length;
444 | element.setSelectionRange(cursorPosition, cursorPosition);
445 | onChange({ suggestion: fullSuggestion, acceptedSuggestion: suggestion });
446 | };
447 |
448 | var AutoComposeTextarea = function () {
449 | function AutoComposeTextarea(options) {
450 | classCallCheck(this, AutoComposeTextarea);
451 |
452 | if (!options) {
453 | throw new Error('AutoCompose Textarea: Missing required parameter, options');
454 | }
455 |
456 | if (typeof options === 'function') options = { composer: options };
457 |
458 | this.inputs = [];
459 | this.suggestion = new OverlaySuggestion();
460 | this.onChange = options.onChange || Function.prototype;
461 | this.onReject = options.onReject || Function.prototype;
462 |
463 | ensure('AutoCompose Textarea', options, 'composer');
464 | ensureType('AutoCompose Textarea', options, 'composer', 'function');
465 | this.composer = options.composer;
466 |
467 | events: {
468 | var self = this;
469 | var handledInKeyDown = false;
470 |
471 | this.onBlurHandler = function () {
472 | self.suggestion.hide();
473 | };
474 |
475 | this.onKeyDownHandler = function (e) {
476 | if (self.suggestion.isActive) {
477 | if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {
478 | var fullSuggestion = self.suggestion.getValue();
479 | setValue({
480 | element: this,
481 | fullSuggestion: fullSuggestion,
482 | suggestion: fullSuggestion,
483 | onChange: self.onChange.bind(this)
484 | });
485 |
486 | self.suggestion.hide();
487 | handledInKeyDown = true;
488 | e.preventDefault();
489 | }
490 | }
491 | };
492 |
493 | var keyUpIndex = 0;
494 | this.onKeyUpHandler = function (e) {
495 | var _this = this;
496 |
497 | if (handledInKeyDown) {
498 | handledInKeyDown = false;
499 | return;
500 | }
501 |
502 | if (self.suggestion.isActive) {
503 | self.suggestion.hide();
504 | self.onReject({ suggestion: self.suggestion.getValue() });
505 | }
506 |
507 | var _getCursorPosition5 = getCursorPosition(this),
508 | _getCursorPosition6 = slicedToArray(_getCursorPosition5, 2),
509 | startPosition = _getCursorPosition6[0],
510 | endPosition = _getCursorPosition6[1];
511 |
512 | if (startPosition !== endPosition) return;
513 |
514 | var postValue = this.value.slice(startPosition);
515 | if (postValue.trim()) return;
516 | var preValue = this.value.slice(0, startPosition);
517 |
518 | handlesuggestion: {
519 | keyUpIndex++;
520 |
521 | var caretPosition = getCaretPosition(this);
522 | (function (asyncReference) {
523 | self.composer.call(_this, preValue, function (result) {
524 | if (!result || asyncReference !== keyUpIndex) return;
525 |
526 | self.suggestion.fill(result, function (suggestion) {
527 | setValue({
528 | element: _this,
529 | suggestion: suggestion,
530 | fullSuggestion: result,
531 | onChange: self.onChange.bind(_this)
532 | });
533 | });
534 |
535 | self.suggestion.show(caretPosition, _this);
536 | });
537 | })(keyUpIndex);
538 | }
539 | };
540 | }
541 |
542 | // initialize events on inputs
543 |
544 | for (var _len = arguments.length, inputs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
545 | inputs[_key - 1] = arguments[_key];
546 | }
547 |
548 | this.addInputs.apply(this, inputs);
549 | }
550 |
551 | createClass(AutoComposeTextarea, [{
552 | key: 'addInputs',
553 | value: function addInputs() {
554 | var _this2 = this;
555 |
556 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
557 | args[_key2] = arguments[_key2];
558 | }
559 |
560 | var inputs = Array.prototype.concat.apply([], args.map(function (d) {
561 | return d[0] ? Array.prototype.slice.call(d, 0) : d;
562 | }));
563 |
564 | inputs.forEach(function (input) {
565 | // validate element
566 | if (input.tagName !== 'TEXTAREA') {
567 | throw new Error('AutoCompose Textarea: Invalid input: only textarea elements are supported');
568 | }
569 |
570 | // init events
571 | input.addEventListener('blur', _this2.onBlurHandler);
572 | input.addEventListener('keyup', _this2.onKeyUpHandler);
573 | input.addEventListener('mouseup', _this2.onKeyUpHandler);
574 | input.addEventListener('keydown', _this2.onKeyDownHandler, true);
575 |
576 | data(input, 'index', _this2.inputs.push(input) - 1);
577 | });
578 | }
579 | }, {
580 | key: 'removeInputs',
581 | value: function removeInputs() {
582 | var _this3 = this;
583 |
584 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
585 | args[_key3] = arguments[_key3];
586 | }
587 |
588 | var inputs = Array.prototype.concat.apply([], args.map(function (d) {
589 | return d[0] ? Array.prototype.slice.call(d, 0) : d;
590 | }));
591 |
592 | inputs.forEach(function (input) {
593 | var index = data(input, 'index');
594 | if (!isNaN(index)) {
595 | _this3.inputs.splice(index, 1);
596 |
597 | // destroy events
598 | input.removeEventListener('blur', _this3.onBlurHandler);
599 | input.removeEventListener('keyup', _this3.onKeyUpHandler);
600 | input.removeEventListener('mouseup', _this3.onKeyUpHandler);
601 | input.removeEventListener('keydown', _this3.onKeyDownHandler, true);
602 | }
603 | });
604 | }
605 | }, {
606 | key: 'destroy',
607 | value: function destroy() {
608 | this.removeInputs(this.inputs);
609 | }
610 | }]);
611 | return AutoComposeTextarea;
612 | }();
613 |
614 | return AutoComposeTextarea;
615 |
616 | })));
617 | //# sourceMappingURL=AutoComposeTextarea.js.map
618 |
--------------------------------------------------------------------------------
/dist/AutoComposeTextarea.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"AutoComposeTextarea.js","sources":["../src/utils.js","../src/constants.js","../src/node-utils.js","../src/OverlaySuggestion.js","../src/AutoComposeTextarea.js"],"sourcesContent":["export const ensure = (context, object, keys) => {\n [].concat(keys).forEach(key => {\n if (typeof object[key] === 'undefined') {\n throw new Error(`AutoCompose: Missing required parameter, ${context}.${key}`);\n }\n });\n};\n\nexport const ensureAnyOf = (context, object, keys) => {\n let currentKey;\n if (!keys.some(key => (\n typeof object[currentKey = key] !== 'undefined'\n ))) throw new Error(`AutoCompose: Missing required parameter, ${context}.${currentKey}`);\n};\n\nexport const ensureType = (context, object, key, type) => {\n [].concat(object[key]).forEach(value => {\n const valueType = typeof value;\n if (valueType !== type && valueType !== 'undefined') {\n throw new TypeError(`AutoCompose: Invalid Type for ${context}.${key}, expected ${type}`);\n }\n });\n};\n\nexport const getCursorPosition = input => {\n return [input.selectionStart, input.selectionEnd].sort((a, b) => a - b);\n};\n\nexport const makeAsyncQueueRunner = () => {\n let i = 0;\n let queue = [];\n\n return (f, j) => {\n queue[j - i] = f;\n while (queue[0]) ++i, queue.shift()();\n };\n};\n\nexport const data = (element, key, value) => {\n key = 'autosuggest_' + key;\n if (typeof value !== 'undefined') {\n element.dataset[key] = JSON.stringify(value);\n } else {\n value = element.dataset[key];\n return typeof value !== 'undefined' ? JSON.parse(element.dataset[key]) : value;\n }\n};\n\nexport const getScrollbarWidth = () => {\n // Creating invisible container\n const outer = document.createElement('div');\n outer.style.visibility = 'hidden';\n outer.style.overflow = 'scroll'; // forcing scrollbar to appear\n outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps\n document.body.appendChild(outer);\n\n // Creating inner element and placing it in the container\n const inner = document.createElement('div');\n outer.appendChild(inner);\n\n // Calculating difference between container's full width and the child width\n const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);\n\n // Removing temporary elements from the DOM\n outer.parentNode.removeChild(outer);\n return scrollbarWidth;\n};\n","// Invisible character\nexport const POSITIONER_CHARACTER = \"\\ufeff\";\n\nexport const FONT_PROPERTIES = [\n // https://developer.mozilla.org/en-US/docs/Web/CSS/font\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'fontFamily',\n\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration', // might not make a difference, but better be safe\n\n 'letterSpacing',\n 'wordSpacing',\n\n 'tabSize',\n 'MozTabSize',\n\n 'whiteSpace',\n 'wordWrap',\n 'wordBreak'\n];\n\nexport const HOST_PROPERTIES = [\n ...FONT_PROPERTIES,\n 'direction',\n 'boxSizing',\n\n 'borderRightWidth',\n 'borderLeftWidth',\n\n 'paddingRight',\n 'paddingLeft',\n];\n\nexport const CLONE_PROPERTIES = [\n ...HOST_PROPERTIES,\n 'width',\n\n 'overflowX',\n 'overflowY',\n\n 'borderTopWidth',\n 'borderBottomWidth',\n 'borderStyle',\n\n 'paddingTop',\n 'paddingBottom',\n\n 'lineHeight',\n];\n\nexport const FILLER = ' ';\n\nexport const INLINE_SUGGESTION_ID = '___autocompose_inline_suggestion___';\n","export const getGlobalOffset = $0 => {\n let node = $0, top = 0, left = 0;\n\n do {\n left += node.offsetLeft;\n top += node.offsetTop;\n } while (node = node.offsetParent);\n\n return { left, top };\n};\n\nexport const getSelectedTextNodes = () => {\n const selection = window.getSelection();\n if (!selection.isCollapsed) return {};\n\n let { startContainer: node, startOffset: offset } = selection.getRangeAt(0);\n if (node.nodeType !== node.TEXT_NODE) {\n try {\n node = getFirstChildNode(node.childNodes[offset]);\n offset = 0;\n } catch (e) {\n try {\n node = getLastChildNode(node.childNodes[offset - 1]);\n offset = node.nodeValue ? node.nodeValue.length : null;\n } catch(e) {}\n }\n }\n\n return { node, offset };\n};\n\nexport const createNode = html => {\n var div = document.createElement('div');\n div.innerHTML = html.trim();\n return div.firstChild;\n};\n\nexport const getFirstChildNode = node => {\n let nextNode = node;\n while (nextNode.firstChild) nextNode = nextNode.firstChild;\n return nextNode;\n};\n\nexport const getLastChildNode = node => {\n let nextNode = node;\n while (nextNode.lastChild) nextNode = nextNode.lastChild;\n return nextNode;\n};\n\nexport const getNextNode = (node, root) => {\n let nextNode;\n if (node.nextSibling)\n nextNode = node.nextSibling;\n else {\n nextNode = node.parentNode;\n while (nextNode !== root && !nextNode.nextSibling)\n nextNode = nextNode.parentNode;\n if (nextNode && nextNode !== root)\n nextNode = nextNode.nextSibling\n else return;\n }\n\n return getFirstChildNode(nextNode);\n};\n\nexport const getPrevNode = (node, root) => {\n let prevNode;\n if (node.previousSibling)\n prevNode = node.previousSibling;\n else {\n prevNode = node.parentNode;\n while (prevNode !== root && !prevNode.previousSibling)\n prevNode = prevNode.parentNode;\n if (prevNode && prevNode !== root)\n prevNode = prevNode.previousSibling\n else return;\n }\n\n return getLastChildNode(prevNode);\n};\n\nexport const removeNodesBetween = (startContainer, endContainer) => {\n if (startContainer === endContainer) return;\n let node = getNextNode(startContainer);\n while (node !== endContainer) {\n node.parentNode.removeChild(node);\n node = getNextNode(startContainer);\n }\n};\n\nexport const getNodeValue = node => {\n if (node.tagName && node.tagName === 'BR')\n return '\\n';\n return node.nodeValue || '';\n};\n\nexport const setSelection = callback => {\n const selection = window.getSelection();\n const range = document.createRange();\n callback(range);\n selection.removeAllRanges();\n selection.addRange(range);\n};","import { getScrollbarWidth } from './utils';\nimport { createNode, getGlobalOffset } from './node-utils';\nimport { POSITIONER_CHARACTER, HOST_PROPERTIES, FONT_PROPERTIES, FILLER } from './constants';\n\nlet scrollBarWidth;\n\nclass OverlaySuggestion {\n constructor() {\n if (scrollBarWidth === undefined)\n scrollBarWidth = getScrollbarWidth();\n\n this.isEmpty = true;\n this.isActive = false;\n this.suggestion = '';\n\n this.host = document.createElement('div');\n this.host.className = 'autocompose-overlay-suggestion';\n this.host.style.zIndex = 9999;\n this.host.style.cursor = 'text';\n this.host.style.position = 'absolute';\n this.host.style.borderColor = 'transparent';\n this.host.style.backgroundColor = 'transparent';\n this.host.style.overflow = 'hidden';\n this.host.style.pointerEvents = 'none';\n\n this.offset = document.createElement('div');\n this.offset.appendChild(document.createTextNode(POSITIONER_CHARACTER));\n this.offset.style.lineHeight = 1.5;\n\n this.content = document.createElement('div');\n this.content.style.lineHeight = '1px';\n\n this.hide();\n document.body.appendChild(this.host);\n this.host.appendChild(this.offset);\n this.host.appendChild(this.content);\n }\n\n show(position, element) {\n if (position) {\n const elementPosition = getGlobalOffset(element);\n const elementStyles = window.getComputedStyle(element);\n\n HOST_PROPERTIES.forEach(prop => {\n this.host.style[prop] = elementStyles[prop];\n });\n this.host.style.left = `${elementPosition.left}px`;\n this.host.style.top = `${position.top}px`;\n this.host.style.height = `${parseFloat(elementStyles.height) - position.top + elementPosition.top}px`;\n this.host.style.color = elementStyles.color;\n\n const overlayWidth = parseFloat(elementStyles.width) - scrollBarWidth;\n this.host.style.width = `${overlayWidth}px`;\n\n const leftWidth = position.left - elementPosition.left -\n parseFloat(elementStyles.paddingLeft || 0);\n const rightWidth = overlayWidth - position.left + elementPosition.left -\n parseFloat(elementStyles.paddingRight || 0);\n let firstLineWidth = 0;\n if (elementStyles.direction === 'ltr') {\n this.offset.style.float = 'left';\n this.offset.style.width = `${leftWidth}px`;\n firstLineWidth = rightWidth;\n } else {\n this.offset.style.float = 'right';\n this.offset.style.width = `${rightWidth}px`;\n firstLineWidth = leftWidth;\n }\n\n const span = document.createElement('span');\n span.style.whiteSpace = 'nowrap';\n FONT_PROPERTIES.forEach(prop => {\n span.style[prop] = elementStyles[prop];\n });\n\n span.appendChild(createNode(' '));\n span.firstChild.style.fontSize = '1px';\n\n const textNode = document.createTextNode('');\n span.appendChild(textNode);\n\n document.body.appendChild(span);\n\n let crossed = false, lastSpaceAt = -1;\n const suggestionLength = this.suggestion.length;\n for (let i = 0; i < suggestionLength; i++) {\n if (!crossed) {\n const text = this.suggestion[i];\n if (/\\s/.test(text)) lastSpaceAt = i;\n textNode.nodeValue += this.suggestion[i];\n crossed = span.offsetWidth > firstLineWidth;\n if (crossed) {\n for (let j = lastSpaceAt + 2; j <= i; j++) {\n this.content.childNodes[j].style.lineHeight = elementStyles.lineHeight;\n }\n }\n }\n if (crossed) {\n this.content.childNodes[i + 1].style.lineHeight = elementStyles.lineHeight;\n }\n }\n if (crossed) {\n this.content.lastChild.style.lineHeight = elementStyles.lineHeight;\n }\n document.body.removeChild(span);\n }\n\n this.host.style.display = 'block';\n this.isActive = true;\n }\n\n hide() {\n this.host.style.display = 'none';\n this.isActive = false;\n }\n\n empty() {\n this.isEmpty = true;\n while (this.content.firstChild)\n this.content.removeChild(this.content.firstChild);\n }\n\n fill(suggestion, onSet) {\n this.empty();\n this.suggestion = suggestion;\n\n this.content.appendChild(createNode(' '));\n this.content.firstChild.style.fontSize = '1px';\n\n suggestion.split('').concat(FILLER).forEach((char, i) => {\n const charNode = createNode(`${char}`);\n charNode.style.opacity = 0.7;\n charNode.style.lineHeight = 1.5;\n charNode.style.pointerEvents = 'all';\n this.content.appendChild(charNode);\n\n charNode.addEventListener('mousedown', e => {\n onSet(suggestion.slice(0, i + 1));\n this.hide();\n\n e.preventDefault();\n e.stopPropagation();\n });\n });\n\n this.isEmpty = false;\n }\n\n getValue() {\n return this.suggestion;\n }\n}\n\nexport default OverlaySuggestion;\n","import { data, ensure, ensureType, getCursorPosition } from './utils';\nimport { POSITIONER_CHARACTER, CLONE_PROPERTIES } from './constants';\nimport { getGlobalOffset } from './node-utils';\nimport OverlaySuggestion from './OverlaySuggestion';\n\nfunction getCaretPosition(element) {\n const [cursorPosition] = getCursorPosition(element);\n\n // pre to retain special characters\n const clone = document.createElement('pre');\n clone.id = 'autocompose-positionclone';\n\n const positioner = document.createElement('span');\n positioner.appendChild(document.createTextNode(POSITIONER_CHARACTER));\n\n const computed = window.getComputedStyle(element);\n CLONE_PROPERTIES.forEach(prop => {\n clone.style[prop] = computed[prop];\n });\n\n const elementPosition = getGlobalOffset(element);\n clone.style.opacity = 0;\n clone.style.position = 'absolute';\n clone.style.top = `${elementPosition.top}px`;\n clone.style.left = `${elementPosition.left}px`;\n document.body.appendChild(clone);\n\n if (element.scrollHeight > parseInt(computed.height))\n clone.style.overflowY = 'scroll';\n else\n clone.style.overflowY = 'hidden';\n\n clone.appendChild(document.createTextNode(element.value.slice(0, cursorPosition)));\n clone.appendChild(positioner);\n clone.style.maxWidth = '100%';\n\n const caretPosition = getGlobalOffset(positioner);\n caretPosition.top -= element.scrollTop;\n caretPosition.left -= element.scrollLeft;\n document.body.removeChild(clone);\n return caretPosition;\n}\n\nconst setValue = ({ element, suggestion, fullSuggestion, onChange }) => {\n const [startPosition] = getCursorPosition(element);\n const originalValue = element.value;\n const value = originalValue.slice(0, startPosition) + suggestion;\n\n element.value = value + originalValue.slice(startPosition);\n element.focus();\n\n const cursorPosition = value.length;\n element.setSelectionRange(cursorPosition, cursorPosition);\n onChange({ suggestion: fullSuggestion, acceptedSuggestion: suggestion });\n};\n\nclass AutoComposeTextarea {\n constructor(options, ...inputs) {\n if (!options) {\n throw new Error(`AutoCompose Textarea: Missing required parameter, options`);\n }\n\n if (typeof options === 'function')\n options = { composer: options };\n\n this.inputs = [];\n this.suggestion = new OverlaySuggestion();\n this.onChange = options.onChange || Function.prototype;\n this.onReject = options.onReject || Function.prototype;\n\n ensure('AutoCompose Textarea', options, 'composer');\n ensureType('AutoCompose Textarea', options, 'composer', 'function');\n this.composer = options.composer;\n\n events: {\n const self = this;\n let handledInKeyDown = false;\n\n this.onBlurHandler = function () {\n self.suggestion.hide();\n };\n\n this.onKeyDownHandler = function (e) {\n if (self.suggestion.isActive) {\n if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {\n const fullSuggestion = self.suggestion.getValue();\n setValue({\n element: this,\n fullSuggestion,\n suggestion: fullSuggestion,\n onChange: self.onChange.bind(this)\n });\n\n self.suggestion.hide();\n handledInKeyDown = true;\n e.preventDefault();\n }\n }\n };\n\n let keyUpIndex = 0;\n this.onKeyUpHandler = function (e) {\n if (handledInKeyDown) {\n handledInKeyDown = false;\n return;\n }\n\n if (self.suggestion.isActive) {\n self.suggestion.hide();\n self.onReject({ suggestion: self.suggestion.getValue() });\n }\n\n const [startPosition, endPosition] = getCursorPosition(this);\n if (startPosition !== endPosition) return;\n\n const postValue = this.value.slice(startPosition);\n if (postValue.trim()) return;\n const preValue = this.value.slice(0, startPosition);\n\n handlesuggestion: {\n keyUpIndex++;\n\n const caretPosition = getCaretPosition(this);\n (asyncReference => {\n self.composer.call(this, preValue, result => {\n if (!result || asyncReference !== keyUpIndex) return;\n\n self.suggestion.fill(result, suggestion => {\n setValue({\n element: this,\n suggestion: suggestion,\n fullSuggestion: result,\n onChange: self.onChange.bind(this)\n });\n });\n\n self.suggestion.show(caretPosition, this);\n });\n })(keyUpIndex);\n }\n };\n }\n\n // initialize events on inputs\n this.addInputs(...inputs);\n }\n\n addInputs(...args) {\n const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));\n\n inputs.forEach(input => {\n // validate element\n if (input.tagName !== 'TEXTAREA') {\n throw new Error('AutoCompose Textarea: Invalid input: only textarea elements are supported');\n }\n\n // init events\n input.addEventListener('blur', this.onBlurHandler);\n input.addEventListener('keyup', this.onKeyUpHandler);\n input.addEventListener('mouseup', this.onKeyUpHandler);\n input.addEventListener('keydown', this.onKeyDownHandler, true);\n\n data(input, 'index', this.inputs.push(input) - 1);\n });\n }\n\n removeInputs(...args) {\n const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));\n\n inputs.forEach(input => {\n const index = data(input, 'index');\n if (!isNaN(index)) {\n this.inputs.splice(index, 1);\n\n // destroy events\n input.removeEventListener('blur', this.onBlurHandler);\n input.removeEventListener('keyup', this.onKeyUpHandler);\n input.removeEventListener('mouseup', this.onKeyUpHandler);\n input.removeEventListener('keydown', this.onKeyDownHandler, true);\n }\n });\n }\n\n destroy() {\n this.removeInputs(this.inputs);\n }\n}\n\nexport default AutoComposeTextarea;\n"],"names":["ensure","context","object","keys","concat","forEach","key","Error","ensureType","type","valueType","value","TypeError","getCursorPosition","input","selectionStart","selectionEnd","sort","a","b","data","element","dataset","JSON","stringify","parse","getScrollbarWidth","outer","document","createElement","style","visibility","overflow","msOverflowStyle","body","appendChild","inner","scrollbarWidth","offsetWidth","parentNode","removeChild","POSITIONER_CHARACTER","FONT_PROPERTIES","HOST_PROPERTIES","CLONE_PROPERTIES","FILLER","getGlobalOffset","node","$0","top","left","offsetLeft","offsetTop","offsetParent","createNode","div","innerHTML","html","trim","firstChild","scrollBarWidth","OverlaySuggestion","undefined","isEmpty","isActive","suggestion","host","className","zIndex","cursor","position","borderColor","backgroundColor","pointerEvents","offset","createTextNode","lineHeight","content","hide","elementPosition","elementStyles","window","getComputedStyle","prop","height","parseFloat","color","overlayWidth","width","leftWidth","paddingLeft","rightWidth","paddingRight","firstLineWidth","direction","float","span","whiteSpace","fontSize","textNode","crossed","lastSpaceAt","suggestionLength","length","i","text","test","nodeValue","j","childNodes","lastChild","display","onSet","empty","split","char","charNode","opacity","addEventListener","slice","preventDefault","stopPropagation","getCaretPosition","cursorPosition","clone","id","positioner","computed","scrollHeight","parseInt","overflowY","maxWidth","caretPosition","scrollTop","scrollLeft","setValue","fullSuggestion","onChange","startPosition","originalValue","focus","setSelectionRange","acceptedSuggestion","AutoComposeTextarea","options","composer","inputs","Function","prototype","onReject","self","handledInKeyDown","onBlurHandler","onKeyDownHandler","e","keyCode","getValue","bind","keyUpIndex","onKeyUpHandler","endPosition","postValue","preValue","call","result","asyncReference","fill","show","addInputs","args","Array","apply","map","d","tagName","push","index","isNaN","splice","removeEventListener","removeInputs"],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/AutoComposeTextarea.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.AutoComposeTextarea=e()}(this,function(){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},t=function(t,e,n){return e&&s(t.prototype,e),n&&s(t,n),t};function s(t,e){for(var n=0;n ")),l.firstChild.style.fontSize="1px";var d=document.createTextNode("");l.appendChild(d),document.body.appendChild(l);for(var h=!1,u=-1,c=this.suggestion.length,f=0;f ")),this.content.firstChild.style.fontSize="1px",s.split("").concat(" ").forEach(function(t,e){var n=y(""+t+"");n.style.opacity=.7,n.style.lineHeight=1.5,n.style.pointerEvents="all",i.content.appendChild(n),n.addEventListener("mousedown",function(t){o(s.slice(0,e+1)),i.hide(),t.preventDefault(),t.stopPropagation()})}),this.isEmpty=!1}},{key:"getValue",value:function(){return this.suggestion}}]),e);function e(){o(this,e),void 0===C&&(C=function(){var t=document.createElement("div");t.style.visibility="hidden",t.style.overflow="scroll",t.style.msOverflowStyle="scrollbar",document.body.appendChild(t);var e=document.createElement("div");t.appendChild(e);var n=t.offsetWidth-e.offsetWidth;return t.parentNode.removeChild(t),n}()),this.isEmpty=!0,this.isActive=!1,this.suggestion="",this.host=document.createElement("div"),this.host.className="autocompose-overlay-suggestion",this.host.style.zIndex=9999,this.host.style.cursor="text",this.host.style.position="absolute",this.host.style.borderColor="transparent",this.host.style.backgroundColor="transparent",this.host.style.overflow="hidden",this.host.style.pointerEvents="none",this.offset=document.createElement("div"),this.offset.appendChild(document.createTextNode(c)),this.offset.style.lineHeight=1.5,this.content=document.createElement("div"),this.content.style.lineHeight="1px",this.hide(),document.body.appendChild(this.host),this.host.appendChild(this.offset),this.host.appendChild(this.content)}function b(t){var e=t.element,n=t.suggestion,s=t.fullSuggestion,o=t.onChange,i=u(e),r=h(i,1)[0],p=e.value,a=p.slice(0,r)+n;e.value=a+p.slice(r),e.focus();var l=a.length;e.setSelectionRange(l,l),o({suggestion:s,acceptedSuggestion:n})}function w(t){if(o(this,w),!t)throw Error("AutoCompose Textarea: Missing required parameter, options");"function"==typeof t&&(t={composer:t}),this.inputs=[],this.suggestion=new p,this.onChange=t.onChange||Function.prototype,this.onReject=t.onReject||Function.prototype,function(e,n,t){[].concat(t).forEach(function(t){if(void 0===n[t])throw Error("AutoCompose: Missing required parameter, "+e+"."+t)})}("AutoCompose Textarea",t,"composer"),function(n,t,s,o){[].concat(t[s]).forEach(function(t){var e=void 0===t?"undefined":i(t);if(e!==o&&"undefined"!==e)throw new TypeError("AutoCompose: Invalid Type for "+n+"."+s+", expected "+o)})}("AutoCompose Textarea",t,"composer","function"),this.composer=t.composer;var a=this,l=!1;this.onBlurHandler=function(){a.suggestion.hide()},this.onKeyDownHandler=function(t){if(a.suggestion.isActive&&(9===t.keyCode||39===t.keyCode||40===t.keyCode)){var e=a.suggestion.getValue();b({element:this,fullSuggestion:e,suggestion:e,onChange:a.onChange.bind(this)}),a.suggestion.hide(),l=!0,t.preventDefault()}};var d=0;this.onKeyUpHandler=function(t){var n=this;if(l)l=!1;else{a.suggestion.isActive&&(a.suggestion.hide(),a.onReject({suggestion:a.suggestion.getValue()}));var e=u(this),s=h(e,2),o=s[0];if(o===s[1])if(!this.value.slice(o).trim()){var i=this.value.slice(0,o);d++;var r,p=function(t){var e=u(t),n=h(e,1)[0],s=document.createElement("pre");s.id="autocompose-positionclone";var o=document.createElement("span");o.appendChild(document.createTextNode(c));var i=window.getComputedStyle(t);f.forEach(function(t){s.style[t]=i[t]});var r=m(t);s.style.opacity=0,s.style.position="absolute",s.style.top=r.top+"px",s.style.left=r.left+"px",document.body.appendChild(s),s.style.overflowY=parseInt(i.height)
2 |
3 |
4 |
5 |
6 |
7 | Auto Compose
8 |
9 |
10 |
11 |
12 |
13 |
57 |
58 |
59 |
60 |
61 |
Textarea Demo
62 |
63 |
Contenteditable Demo
64 |
65 |
66 |
67 |
68 |
69 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@avcs/autocompose",
3 | "version": "1.0.2-beta",
4 | "description": "A JavaScript plugin to provide UI support for Gmail like smart compose in textarea and contenteditable.",
5 | "main": "lib/AutoCompose.js",
6 | "repository": "git@github.com:avcs06/AutoCompose.git",
7 | "scripts": {
8 | "clean": "rimraf lib dist es",
9 | "build": "npm run clean && npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run build:es",
10 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib",
11 | "build:commonjs:watch": "npm run build:commonjs -- --watch",
12 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es",
13 | "build:es:watch": "npm run build:es -- --watch",
14 | "build:umd": "npm run build:umd:main && npm run build:umd:text",
15 | "build:umd:main": "cross-env BABEL_ENV=es NODE_ENV=development rollup src/AutoCompose.js --config --sourcemap --name AutoCompose --output dist/AutoCompose.js",
16 | "build:umd:main:watch": "npm run build:umd:main -- --watch",
17 | "build:umd:text": "cross-env BABEL_ENV=es NODE_ENV=development rollup src/AutoComposeTextarea.js --config --sourcemap --name AutoComposeTextarea --output dist/AutoComposeTextarea.js",
18 | "build:umd:text:watch": "npm run build:umd:text -- --watch",
19 | "build:umd:min": "npm run build:umd:main:min && npm run build:umd:text:min",
20 | "build:umd:main:min": "cross-env BABEL_ENV=es NODE_ENV=production rollup src/AutoCompose.js --config --name AutoCompose --output dist/AutoCompose.min.js",
21 | "build:umd:text:min": "cross-env BABEL_ENV=es NODE_ENV=production rollup src/AutoComposeTextarea.js --config --name AutoComposeTextarea --output dist/AutoComposeTextarea.min.js",
22 | "prepublish": "npm run build"
23 | },
24 | "keywords": [
25 | "smart compose",
26 | "textarea",
27 | "contenteditable"
28 | ],
29 | "author": "AvcS",
30 | "license": "MIT",
31 | "devDependencies": {
32 | "babel-cli": "^6.24.1",
33 | "babel-core": "^6.26.3",
34 | "babel-eslint": "^7.2.3",
35 | "babel-plugin-external-helpers": "^6.22.0",
36 | "babel-preset-env": "^1.7.0",
37 | "cross-env": "^5.0.1",
38 | "eslint": "^4.1.1",
39 | "rimraf": "^2.6.1",
40 | "rollup": "^0.43.0",
41 | "rollup-plugin-babel": "^2.7.1",
42 | "rollup-plugin-commonjs": "^8.0.2",
43 | "rollup-plugin-node-resolve": "^3.0.0",
44 | "rollup-plugin-uglify": "^2.0.1",
45 | "rollup-watch": "^4.0.0"
46 | },
47 | "dependencies": {}
48 | }
49 |
--------------------------------------------------------------------------------
/pbt_project.yml:
--------------------------------------------------------------------------------
1 | name: TestGithub1
2 | description: ''
3 | version: 0.0.1-SNAPSHOT
4 | author: ayush+11@simpledatalabs.com
5 | language: python
6 | buildSystem: wheel
7 | pipelines: {}
8 | datasets: {}
9 | templates: {}
10 | jobs: {}
11 | libraries: []
12 | subgraphs: {}
13 | sqlModels: {}
14 | sqlPipeline: null
15 | dependencies: '[]'
16 | projectDependencies: {}
17 | pipelineConfigurations: {}
18 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import nodeResolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import babel from 'rollup-plugin-babel';
4 | import uglify from 'rollup-plugin-uglify';
5 |
6 | var env = process.env.NODE_ENV
7 | var config = {
8 | format: 'umd',
9 | plugins: [
10 | nodeResolve({
11 | jsnext: true
12 | }),
13 | // due to https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module
14 | commonjs({
15 | include: 'node_modules/**'
16 | }),
17 | babel({
18 | exclude: 'node_modules/**',
19 | "plugins": [ "external-helpers" ]
20 | })
21 | ]
22 | }
23 |
24 | if (env === 'production') {
25 | config.plugins.push(
26 | uglify({
27 | compress: {
28 | pure_getters: true,
29 | unsafe: true,
30 | unsafe_comps: true
31 | }
32 | })
33 | )
34 | }
35 |
36 | export default config
37 |
--------------------------------------------------------------------------------
/src/AutoCompose.js:
--------------------------------------------------------------------------------
1 | import { INLINE_SUGGESTION_ID } from './constants';
2 | import { data, ensure, ensureType } from './utils';
3 | import {
4 | getSelectedTextNodes,
5 | getNodeValue,
6 | setSelection,
7 | getPrevNode,
8 | getNextNode,
9 | createNode,
10 | } from './node-utils';
11 |
12 | class AutoCompose {
13 | constructor(options, ...inputs) {
14 | if (!options)
15 | throw new Error(`AutoCompose: Missing required parameter, options`);
16 |
17 | if (typeof options === 'function')
18 | options = { composer: options };
19 |
20 | this.inputs = [];
21 | this.onChange = options.onChange || Function.prototype;
22 | this.onReject = options.onReject || Function.prototype;
23 |
24 | ensure('AutoCompose', options, 'composer');
25 | ensureType('AutoCompose', options, 'composer', 'function');
26 | this.composer = options.composer;
27 |
28 | events: {
29 | const self = this;
30 | let handledInKeyDown = false;
31 | let activeElement = null;
32 | let suggestionNode = null;
33 | let activeSuggestion = null;
34 |
35 | const clearSuggestion = normalize => {
36 | const parentNode = suggestionNode.parentNode;
37 | parentNode.removeChild(suggestionNode);
38 | normalize && parentNode.normalize();
39 | suggestionNode = activeSuggestion = activeElement = null;
40 | };
41 |
42 | const acceptSuggestion = ignoreCursor => {
43 | const suggestion = suggestionNode.firstChild.nodeValue;
44 | suggestionNode.parentNode.insertBefore(suggestionNode.firstChild, suggestionNode);
45 | const insertedNode = suggestionNode.previousSibling;
46 |
47 | this.onChange.call(activeElement, {
48 | suggestion: activeSuggestion,
49 | acceptedSuggestion: suggestion
50 | });
51 |
52 | clearSuggestion();
53 | !ignoreCursor && setSelection(range => {
54 | range.setStartAfter(insertedNode);
55 | range.setEndAfter(insertedNode);
56 | });
57 | };
58 |
59 | const rejectSuggestion = () => {
60 | this.onReject.call(activeElement, { suggestion: activeSuggestion });
61 | clearSuggestion();
62 | };
63 |
64 | const isSuggestionTextNode = node => node.parentNode === suggestionNode;
65 | const isAfterSuggestionNode = node => {
66 | while ((node = getPrevNode(node, activeElement)) && !isSuggestionTextNode(node));
67 | return Boolean(node);
68 | };
69 |
70 | this.onBlurHandler = () => suggestionNode && clearSuggestion(true);
71 | this.onKeyDownHandler = function (e) {
72 | if (suggestionNode) {
73 | if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {
74 | acceptSuggestion();
75 | handledInKeyDown = true;
76 | e.preventDefault();
77 | }
78 | }
79 | };
80 |
81 | let keyUpIndex = 0;
82 | this.onKeyUpHandler = function (e) {
83 | if (e.type === 'keyup' && handledInKeyDown) {
84 | handledInKeyDown = false;
85 | return;
86 | }
87 |
88 | let { node: textNode, offset } = getSelectedTextNodes();
89 | if (!textNode) return suggestionNode && rejectSuggestion();
90 |
91 | const isSuggestionNode = isSuggestionTextNode(textNode);
92 | if (e.type === 'mouseup' && suggestionNode) {
93 | if (isSuggestionNode && offset) {
94 | textNode.nodeValue = textNode.nodeValue.slice(0, offset);
95 | return acceptSuggestion();
96 | } else if (isAfterSuggestionNode(textNode)) {
97 | return acceptSuggestion(true);
98 | }
99 | }
100 |
101 | if (isSuggestionNode) {
102 | try {
103 | textNode = getPrevNode(suggestionNode, this);
104 | offset = textNode.nodeValue.length;
105 | } catch(e) {
106 | textNode = getNextNode(suggestionNode, this);
107 | offset = 0;
108 | }
109 | }
110 |
111 | suggestionNode && rejectSuggestion();
112 | if (textNode.nodeType !== textNode.TEXT_NODE) return;
113 |
114 | postValue: {
115 | let postValue = textNode.nodeValue.slice(offset);
116 | if (postValue.trim()) return;
117 |
118 | let node = textNode;
119 | while (node = getNextNode(node, this)) {
120 | postValue += getNodeValue(node);
121 | if (postValue.trim()) return;
122 | }
123 | }
124 |
125 | let preValue = '';
126 | preValue: {
127 | preValue = textNode.nodeValue.slice(0, offset);
128 |
129 | let node = textNode;
130 | while (node = getPrevNode(node, this)) {
131 | preValue = getNodeValue(node) + preValue;
132 | }
133 | }
134 |
135 | handlesuggestion: {
136 | keyUpIndex++;
137 | (asyncReference => {
138 | self.composer.call(this, preValue, result => {
139 | if (!result || asyncReference !== keyUpIndex) return;
140 | activeElement = this;
141 |
142 | const textAfterCursor = textNode.nodeValue.slice(offset);
143 | const parentNode = textNode.parentNode;
144 | const referenceNode = textNode.nextSibling;
145 |
146 | textNode.nodeValue = textNode.nodeValue.slice(0, offset);
147 | parentNode.insertBefore(document.createTextNode(textAfterCursor), referenceNode);
148 |
149 | activeSuggestion = result;
150 | suggestionNode = createNode(`${result}`);
151 | suggestionNode.style.opacity = 0.7;
152 | suggestionNode.id = INLINE_SUGGESTION_ID;
153 | parentNode.insertBefore(suggestionNode, referenceNode);
154 |
155 | setSelection(range => {
156 | range.setStartBefore(suggestionNode);
157 | range.setEndBefore(suggestionNode);
158 | });
159 | });
160 | })(keyUpIndex);
161 | }
162 | };
163 | }
164 |
165 | // initialize events on inputs
166 | this.addInputs(...inputs);
167 | }
168 |
169 | addInputs(...args) {
170 | const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));
171 |
172 | inputs.forEach(input => {
173 | // validate element
174 | if (!input.isContentEditable) {
175 | throw new Error('AutoCompose: Invalid input: only contenteditable elements are supported');
176 | }
177 |
178 | // init events
179 | input.addEventListener('blur', this.onBlurHandler);
180 | input.addEventListener('keyup', this.onKeyUpHandler);
181 | input.addEventListener('mouseup', this.onKeyUpHandler);
182 | input.addEventListener('keydown', this.onKeyDownHandler, true);
183 |
184 | data(input, 'index', this.inputs.push(input) - 1);
185 | });
186 | }
187 |
188 | removeInputs(...args) {
189 | const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));
190 |
191 | inputs.forEach(input => {
192 | const index = data(input, 'index');
193 | if (!isNaN(index)) {
194 | this.inputs.splice(index, 1);
195 |
196 | // destroy events
197 | input.removeEventListener('blur', this.onBlurHandler);
198 | input.removeEventListener('keyup', this.onKeyUpHandler);
199 | input.removeEventListener('mouseup', this.onKeyUpHandler);
200 | input.removeEventListener('keydown', this.onKeyDownHandler, true);
201 | }
202 | });
203 | }
204 |
205 | destroy() {
206 | this.removeInputs(this.inputs);
207 | }
208 | }
209 |
210 | export default AutoCompose;
211 |
--------------------------------------------------------------------------------
/src/AutoComposeTextarea.js:
--------------------------------------------------------------------------------
1 | import { data, ensure, ensureType, getCursorPosition } from './utils';
2 | import { POSITIONER_CHARACTER, CLONE_PROPERTIES } from './constants';
3 | import { getGlobalOffset } from './node-utils';
4 | import OverlaySuggestion from './OverlaySuggestion';
5 |
6 | function getCaretPosition(element) {
7 | const [cursorPosition] = getCursorPosition(element);
8 |
9 | // pre to retain special characters
10 | const clone = document.createElement('pre');
11 | clone.id = 'autocompose-positionclone';
12 |
13 | const positioner = document.createElement('span');
14 | positioner.appendChild(document.createTextNode(POSITIONER_CHARACTER));
15 |
16 | const computed = window.getComputedStyle(element);
17 | CLONE_PROPERTIES.forEach(prop => {
18 | clone.style[prop] = computed[prop];
19 | });
20 |
21 | const elementPosition = getGlobalOffset(element);
22 | clone.style.opacity = 0;
23 | clone.style.position = 'absolute';
24 | clone.style.top = `${elementPosition.top}px`;
25 | clone.style.left = `${elementPosition.left}px`;
26 | document.body.appendChild(clone);
27 |
28 | if (element.scrollHeight > parseInt(computed.height))
29 | clone.style.overflowY = 'scroll';
30 | else
31 | clone.style.overflowY = 'hidden';
32 |
33 | clone.appendChild(document.createTextNode(element.value.slice(0, cursorPosition)));
34 | clone.appendChild(positioner);
35 | clone.style.maxWidth = '100%';
36 |
37 | const caretPosition = getGlobalOffset(positioner);
38 | caretPosition.top -= element.scrollTop;
39 | caretPosition.left -= element.scrollLeft;
40 | document.body.removeChild(clone);
41 | return caretPosition;
42 | }
43 |
44 | const setValue = ({ element, suggestion, fullSuggestion, onChange }) => {
45 | const [startPosition] = getCursorPosition(element);
46 | const originalValue = element.value;
47 | const value = originalValue.slice(0, startPosition) + suggestion;
48 |
49 | element.value = value + originalValue.slice(startPosition);
50 | element.focus();
51 |
52 | const cursorPosition = value.length;
53 | element.setSelectionRange(cursorPosition, cursorPosition);
54 | onChange({ suggestion: fullSuggestion, acceptedSuggestion: suggestion });
55 | };
56 |
57 | class AutoComposeTextarea {
58 | constructor(options, ...inputs) {
59 | if (!options) {
60 | throw new Error(`AutoCompose Textarea: Missing required parameter, options`);
61 | }
62 |
63 | if (typeof options === 'function')
64 | options = { composer: options };
65 |
66 | this.inputs = [];
67 | this.suggestion = new OverlaySuggestion();
68 | this.onChange = options.onChange || Function.prototype;
69 | this.onReject = options.onReject || Function.prototype;
70 |
71 | ensure('AutoCompose Textarea', options, 'composer');
72 | ensureType('AutoCompose Textarea', options, 'composer', 'function');
73 | this.composer = options.composer;
74 |
75 | events: {
76 | const self = this;
77 | let handledInKeyDown = false;
78 |
79 | this.onBlurHandler = function () {
80 | self.suggestion.hide();
81 | };
82 |
83 | this.onKeyDownHandler = function (e) {
84 | if (self.suggestion.isActive) {
85 | if (e.keyCode === 9 || e.keyCode === 39 || e.keyCode === 40) {
86 | const fullSuggestion = self.suggestion.getValue();
87 | setValue({
88 | element: this,
89 | fullSuggestion,
90 | suggestion: fullSuggestion,
91 | onChange: self.onChange.bind(this)
92 | });
93 |
94 | self.suggestion.hide();
95 | handledInKeyDown = true;
96 | e.preventDefault();
97 | }
98 | }
99 | };
100 |
101 | let keyUpIndex = 0;
102 | this.onKeyUpHandler = function (e) {
103 | if (handledInKeyDown) {
104 | handledInKeyDown = false;
105 | return;
106 | }
107 |
108 | if (self.suggestion.isActive) {
109 | self.suggestion.hide();
110 | self.onReject({ suggestion: self.suggestion.getValue() });
111 | }
112 |
113 | const [startPosition, endPosition] = getCursorPosition(this);
114 | if (startPosition !== endPosition) return;
115 |
116 | const postValue = this.value.slice(startPosition);
117 | if (postValue.trim()) return;
118 | const preValue = this.value.slice(0, startPosition);
119 |
120 | handlesuggestion: {
121 | keyUpIndex++;
122 |
123 | const caretPosition = getCaretPosition(this);
124 | (asyncReference => {
125 | self.composer.call(this, preValue, result => {
126 | if (!result || asyncReference !== keyUpIndex) return;
127 |
128 | self.suggestion.fill(result, suggestion => {
129 | setValue({
130 | element: this,
131 | suggestion: suggestion,
132 | fullSuggestion: result,
133 | onChange: self.onChange.bind(this)
134 | });
135 | });
136 |
137 | self.suggestion.show(caretPosition, this);
138 | });
139 | })(keyUpIndex);
140 | }
141 | };
142 | }
143 |
144 | // initialize events on inputs
145 | this.addInputs(...inputs);
146 | }
147 |
148 | addInputs(...args) {
149 | const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));
150 |
151 | inputs.forEach(input => {
152 | // validate element
153 | if (input.tagName !== 'TEXTAREA') {
154 | throw new Error('AutoCompose Textarea: Invalid input: only textarea elements are supported');
155 | }
156 |
157 | // init events
158 | input.addEventListener('blur', this.onBlurHandler);
159 | input.addEventListener('keyup', this.onKeyUpHandler);
160 | input.addEventListener('mouseup', this.onKeyUpHandler);
161 | input.addEventListener('keydown', this.onKeyDownHandler, true);
162 |
163 | data(input, 'index', this.inputs.push(input) - 1);
164 | });
165 | }
166 |
167 | removeInputs(...args) {
168 | const inputs = Array.prototype.concat.apply([], args.map(d => d[0] ? Array.prototype.slice.call(d, 0) : d));
169 |
170 | inputs.forEach(input => {
171 | const index = data(input, 'index');
172 | if (!isNaN(index)) {
173 | this.inputs.splice(index, 1);
174 |
175 | // destroy events
176 | input.removeEventListener('blur', this.onBlurHandler);
177 | input.removeEventListener('keyup', this.onKeyUpHandler);
178 | input.removeEventListener('mouseup', this.onKeyUpHandler);
179 | input.removeEventListener('keydown', this.onKeyDownHandler, true);
180 | }
181 | });
182 | }
183 |
184 | destroy() {
185 | this.removeInputs(this.inputs);
186 | }
187 | }
188 |
189 | export default AutoComposeTextarea;
190 |
--------------------------------------------------------------------------------
/src/Constants.js:
--------------------------------------------------------------------------------
1 | // Invisible character
2 | export const POSITIONER_CHARACTER = "\ufeff";
3 |
4 | export const FONT_PROPERTIES = [
5 | // https://developer.mozilla.org/en-US/docs/Web/CSS/font
6 | 'fontStyle',
7 | 'fontVariant',
8 | 'fontWeight',
9 | 'fontStretch',
10 | 'fontSize',
11 | 'fontSizeAdjust',
12 | 'fontFamily',
13 |
14 | 'textAlign',
15 | 'textTransform',
16 | 'textIndent',
17 | 'textDecoration', // might not make a difference, but better be safe
18 |
19 | 'letterSpacing',
20 | 'wordSpacing',
21 |
22 | 'tabSize',
23 | 'MozTabSize',
24 |
25 | 'whiteSpace',
26 | 'wordWrap',
27 | 'wordBreak'
28 | ];
29 |
30 | export const HOST_PROPERTIES = [
31 | ...FONT_PROPERTIES,
32 | 'direction',
33 | 'boxSizing',
34 |
35 | 'borderRightWidth',
36 | 'borderLeftWidth',
37 |
38 | 'paddingRight',
39 | 'paddingLeft',
40 | ];
41 |
42 | export const CLONE_PROPERTIES = [
43 | ...HOST_PROPERTIES,
44 | 'width',
45 |
46 | 'overflowX',
47 | 'overflowY',
48 |
49 | 'borderTopWidth',
50 | 'borderBottomWidth',
51 | 'borderStyle',
52 |
53 | 'paddingTop',
54 | 'paddingBottom',
55 |
56 | 'lineHeight',
57 | ];
58 |
59 | export const FILLER = ' ';
60 |
61 | export const INLINE_SUGGESTION_ID = '___autocompose_inline_suggestion___';
62 |
--------------------------------------------------------------------------------
/src/OverlaySuggestion.js:
--------------------------------------------------------------------------------
1 | import { getScrollbarWidth } from './utils';
2 | import { createNode, getGlobalOffset } from './node-utils';
3 | import { POSITIONER_CHARACTER, HOST_PROPERTIES, FONT_PROPERTIES, FILLER } from './constants';
4 |
5 | let scrollBarWidth;
6 |
7 | class OverlaySuggestion {
8 | constructor() {
9 | if (scrollBarWidth === undefined)
10 | scrollBarWidth = getScrollbarWidth();
11 |
12 | this.isEmpty = true;
13 | this.isActive = false;
14 | this.suggestion = '';
15 |
16 | this.host = document.createElement('div');
17 | this.host.className = 'autocompose-overlay-suggestion';
18 | this.host.style.zIndex = 9999;
19 | this.host.style.cursor = 'text';
20 | this.host.style.position = 'absolute';
21 | this.host.style.borderColor = 'transparent';
22 | this.host.style.backgroundColor = 'transparent';
23 | this.host.style.overflow = 'hidden';
24 | this.host.style.pointerEvents = 'none';
25 |
26 | this.offset = document.createElement('div');
27 | this.offset.appendChild(document.createTextNode(POSITIONER_CHARACTER));
28 | this.offset.style.lineHeight = 1.5;
29 |
30 | this.content = document.createElement('div');
31 | this.content.style.lineHeight = '1px';
32 |
33 | this.hide();
34 | document.body.appendChild(this.host);
35 | this.host.appendChild(this.offset);
36 | this.host.appendChild(this.content);
37 | }
38 |
39 | show(position, element) {
40 | if (position) {
41 | const elementPosition = getGlobalOffset(element);
42 | const elementStyles = window.getComputedStyle(element);
43 |
44 | HOST_PROPERTIES.forEach(prop => {
45 | this.host.style[prop] = elementStyles[prop];
46 | });
47 | this.host.style.left = `${elementPosition.left}px`;
48 | this.host.style.top = `${position.top}px`;
49 | this.host.style.height = `${parseFloat(elementStyles.height) - position.top + elementPosition.top}px`;
50 | this.host.style.color = elementStyles.color;
51 |
52 | const overlayWidth = parseFloat(elementStyles.width) - scrollBarWidth;
53 | this.host.style.width = `${overlayWidth}px`;
54 |
55 | const leftWidth = position.left - elementPosition.left -
56 | parseFloat(elementStyles.paddingLeft || 0);
57 | const rightWidth = overlayWidth - position.left + elementPosition.left -
58 | parseFloat(elementStyles.paddingRight || 0);
59 | let firstLineWidth = 0;
60 | if (elementStyles.direction === 'ltr') {
61 | this.offset.style.float = 'left';
62 | this.offset.style.width = `${leftWidth}px`;
63 | firstLineWidth = rightWidth;
64 | } else {
65 | this.offset.style.float = 'right';
66 | this.offset.style.width = `${rightWidth}px`;
67 | firstLineWidth = leftWidth;
68 | }
69 |
70 | const span = document.createElement('span');
71 | span.style.whiteSpace = 'nowrap';
72 | FONT_PROPERTIES.forEach(prop => {
73 | span.style[prop] = elementStyles[prop];
74 | });
75 |
76 | span.appendChild(createNode(' '));
77 | span.firstChild.style.fontSize = '1px';
78 |
79 | const textNode = document.createTextNode('');
80 | span.appendChild(textNode);
81 |
82 | document.body.appendChild(span);
83 |
84 | let crossed = false, lastSpaceAt = -1;
85 | const suggestionLength = this.suggestion.length;
86 | for (let i = 0; i < suggestionLength; i++) {
87 | if (!crossed) {
88 | const text = this.suggestion[i];
89 | if (/\s/.test(text)) lastSpaceAt = i;
90 | textNode.nodeValue += this.suggestion[i];
91 | crossed = span.offsetWidth > firstLineWidth;
92 | if (crossed) {
93 | for (let j = lastSpaceAt + 2; j <= i; j++) {
94 | this.content.childNodes[j].style.lineHeight = elementStyles.lineHeight;
95 | }
96 | }
97 | }
98 | if (crossed) {
99 | this.content.childNodes[i + 1].style.lineHeight = elementStyles.lineHeight;
100 | }
101 | }
102 | if (crossed) {
103 | this.content.lastChild.style.lineHeight = elementStyles.lineHeight;
104 | }
105 | document.body.removeChild(span);
106 | }
107 |
108 | this.host.style.display = 'block';
109 | this.isActive = true;
110 | }
111 |
112 | hide() {
113 | this.host.style.display = 'none';
114 | this.isActive = false;
115 | }
116 |
117 | empty() {
118 | this.isEmpty = true;
119 | while (this.content.firstChild)
120 | this.content.removeChild(this.content.firstChild);
121 | }
122 |
123 | fill(suggestion, onSet) {
124 | this.empty();
125 | this.suggestion = suggestion;
126 |
127 | this.content.appendChild(createNode(' '));
128 | this.content.firstChild.style.fontSize = '1px';
129 |
130 | suggestion.split('').concat(FILLER).forEach((char, i) => {
131 | const charNode = createNode(`${char}`);
132 | charNode.style.opacity = 0.7;
133 | charNode.style.lineHeight = 1.5;
134 | charNode.style.pointerEvents = 'all';
135 | this.content.appendChild(charNode);
136 |
137 | charNode.addEventListener('mousedown', e => {
138 | onSet(suggestion.slice(0, i + 1));
139 | this.hide();
140 |
141 | e.preventDefault();
142 | e.stopPropagation();
143 | });
144 | });
145 |
146 | this.isEmpty = false;
147 | }
148 |
149 | getValue() {
150 | return this.suggestion;
151 | }
152 | }
153 |
154 | export default OverlaySuggestion;
155 |
--------------------------------------------------------------------------------
/src/node-utils.js:
--------------------------------------------------------------------------------
1 | export const getGlobalOffset = $0 => {
2 | let node = $0, top = 0, left = 0;
3 |
4 | do {
5 | left += node.offsetLeft;
6 | top += node.offsetTop;
7 | } while (node = node.offsetParent);
8 |
9 | return { left, top };
10 | };
11 |
12 | export const getSelectedTextNodes = () => {
13 | const selection = window.getSelection();
14 | if (!selection.isCollapsed) return {};
15 |
16 | let { startContainer: node, startOffset: offset } = selection.getRangeAt(0);
17 | if (node.nodeType !== node.TEXT_NODE) {
18 | try {
19 | node = getFirstChildNode(node.childNodes[offset]);
20 | offset = 0;
21 | } catch (e) {
22 | try {
23 | node = getLastChildNode(node.childNodes[offset - 1]);
24 | offset = node.nodeValue ? node.nodeValue.length : null;
25 | } catch(e) {}
26 | }
27 | }
28 |
29 | return { node, offset };
30 | };
31 |
32 | export const createNode = html => {
33 | var div = document.createElement('div');
34 | div.innerHTML = html.trim();
35 | return div.firstChild;
36 | };
37 |
38 | export const getFirstChildNode = node => {
39 | let nextNode = node;
40 | while (nextNode.firstChild) nextNode = nextNode.firstChild;
41 | return nextNode;
42 | };
43 |
44 | export const getLastChildNode = node => {
45 | let nextNode = node;
46 | while (nextNode.lastChild) nextNode = nextNode.lastChild;
47 | return nextNode;
48 | };
49 |
50 | export const getNextNode = (node, root) => {
51 | let nextNode;
52 | if (node.nextSibling)
53 | nextNode = node.nextSibling;
54 | else {
55 | nextNode = node.parentNode;
56 | while (nextNode !== root && !nextNode.nextSibling)
57 | nextNode = nextNode.parentNode;
58 | if (nextNode && nextNode !== root)
59 | nextNode = nextNode.nextSibling
60 | else return;
61 | }
62 |
63 | return getFirstChildNode(nextNode);
64 | };
65 |
66 | export const getPrevNode = (node, root) => {
67 | let prevNode;
68 | if (node.previousSibling)
69 | prevNode = node.previousSibling;
70 | else {
71 | prevNode = node.parentNode;
72 | while (prevNode !== root && !prevNode.previousSibling)
73 | prevNode = prevNode.parentNode;
74 | if (prevNode && prevNode !== root)
75 | prevNode = prevNode.previousSibling
76 | else return;
77 | }
78 |
79 | return getLastChildNode(prevNode);
80 | };
81 |
82 | export const removeNodesBetween = (startContainer, endContainer) => {
83 | if (startContainer === endContainer) return;
84 | let node = getNextNode(startContainer);
85 | while (node !== endContainer) {
86 | node.parentNode.removeChild(node);
87 | node = getNextNode(startContainer);
88 | }
89 | };
90 |
91 | export const getNodeValue = node => {
92 | if (node.tagName && node.tagName === 'BR')
93 | return '\n';
94 | return node.nodeValue || '';
95 | };
96 |
97 | export const setSelection = callback => {
98 | const selection = window.getSelection();
99 | const range = document.createRange();
100 | callback(range);
101 | selection.removeAllRanges();
102 | selection.addRange(range);
103 | };
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export const ensure = (context, object, keys) => {
2 | [].concat(keys).forEach(key => {
3 | if (typeof object[key] === 'undefined') {
4 | throw new Error(`AutoCompose: Missing required parameter, ${context}.${key}`);
5 | }
6 | });
7 | };
8 |
9 | export const ensureAnyOf = (context, object, keys) => {
10 | let currentKey;
11 | if (!keys.some(key => (
12 | typeof object[currentKey = key] !== 'undefined'
13 | ))) throw new Error(`AutoCompose: Missing required parameter, ${context}.${currentKey}`);
14 | };
15 |
16 | export const ensureType = (context, object, key, type) => {
17 | [].concat(object[key]).forEach(value => {
18 | const valueType = typeof value;
19 | if (valueType !== type && valueType !== 'undefined') {
20 | throw new TypeError(`AutoCompose: Invalid Type for ${context}.${key}, expected ${type}`);
21 | }
22 | });
23 | };
24 |
25 | export const getCursorPosition = input => {
26 | return [input.selectionStart, input.selectionEnd].sort((a, b) => a - b);
27 | };
28 |
29 | export const makeAsyncQueueRunner = () => {
30 | let i = 0;
31 | let queue = [];
32 |
33 | return (f, j) => {
34 | queue[j - i] = f;
35 | while (queue[0]) ++i, queue.shift()();
36 | };
37 | };
38 |
39 | export const data = (element, key, value) => {
40 | key = 'autosuggest_' + key;
41 | if (typeof value !== 'undefined') {
42 | element.dataset[key] = JSON.stringify(value);
43 | } else {
44 | value = element.dataset[key];
45 | return typeof value !== 'undefined' ? JSON.parse(element.dataset[key]) : value;
46 | }
47 | };
48 |
49 | export const getScrollbarWidth = () => {
50 | // Creating invisible container
51 | const outer = document.createElement('div');
52 | outer.style.visibility = 'hidden';
53 | outer.style.overflow = 'scroll'; // forcing scrollbar to appear
54 | outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
55 | document.body.appendChild(outer);
56 |
57 | // Creating inner element and placing it in the container
58 | const inner = document.createElement('div');
59 | outer.appendChild(inner);
60 |
61 | // Calculating difference between container's full width and the child width
62 | const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);
63 |
64 | // Removing temporary elements from the DOM
65 | outer.parentNode.removeChild(outer);
66 | return scrollbarWidth;
67 | };
68 |
--------------------------------------------------------------------------------