├── cjs
├── package.json
└── index.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── rollup.config.js
├── LICENSE
├── test
├── index.html
├── effect.html
└── test.js
├── esm
└── index.js
├── package.json
├── README.md
├── index.d.ts
├── min.js
└── index.js
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 | .DS_Store
4 | package-lock.json
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | img/
3 | node_modules/
4 | test/
5 | _config.yml
6 | .DS_Store
7 | .gitignore
8 | .travis.yml
9 | package-lock.json
10 | rollup.config.js
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | git:
5 | depth: 1
6 | branches:
7 | only:
8 | - master
9 | - /^greenkeeper/.*$/
10 | after_success:
11 | - "npm run coveralls"
12 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | export default {
4 | input: 'esm/index.js',
5 | plugins: [
6 | resolve(),
7 | babel({
8 | runtimeHelpers: true,
9 | presets: ['@babel/preset-env']
10 | })
11 | ],
12 | context: 'null',
13 | moduleContext: 'null',
14 | output: {
15 | exports: 'named',
16 | file: 'index.js',
17 | format: 'iife',
18 | name: 'augmentor'
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2018-8-8-8-8-8-8-8-, Andrea Giammarchi, @WebReflection
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | DOM Augmentor - useHTML example
8 |
9 |
10 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/test/effect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
52 |
53 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi - ISC */
2 | import CustomEvent from '@ungap/custom-event';
3 | import WeakSet from '@ungap/weakset';
4 |
5 | import disconnected from 'disconnected';
6 |
7 | import {
8 | augmentor as $augmentor,
9 | dropEffect, hasEffect
10 | } from 'augmentor';
11 |
12 | const find = node => {
13 | let {firstChild} = node;
14 | while (firstChild && firstChild.nodeType !== 1)
15 | firstChild = firstChild.nextSibling;
16 | if (firstChild)
17 | return firstChild;
18 | throw 'unobservable';
19 | };
20 |
21 | const observe = disconnected({Event: CustomEvent, WeakSet});
22 |
23 | const observer = (element, handler) => {
24 | const {nodeType} = element;
25 | if (nodeType) {
26 | const node = nodeType === 1 ? element : find(element);
27 | observe(node);
28 | node.addEventListener('disconnected', handler, false);
29 | }
30 | else {
31 | const value = element.valueOf();
32 | // give a chance to facades to return a reasonable value
33 | if (value !== element)
34 | observer(value, handler);
35 | }
36 | };
37 |
38 | export const augmentor = fn => {
39 | let handler = null;
40 | const hook = $augmentor(fn);
41 | return function () {
42 | const node = hook.apply(this, arguments);
43 | if (hasEffect(hook))
44 | observer(node, handler || (handler = dropEffect.bind(null, hook)));
45 | return node;
46 | };
47 | };
48 |
49 | export {
50 | contextual,
51 | useState,
52 | useEffect, useLayoutEffect,
53 | useContext, createContext,
54 | useReducer,
55 | useCallback,
56 | useMemo,
57 | useRef
58 | } from 'augmentor';
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dom-augmentor",
3 | "version": "2.0.8",
4 | "description": "DOM oriented useEffect hooks for the augmentor",
5 | "main": "cjs/index.js",
6 | "module": "esm/index.js",
7 | "unpkg": "min.js",
8 | "scripts": {
9 | "build": "npm run rollup && npm run cjs && npm run min && npm run test && npm run size",
10 | "cjs": "ascjs esm cjs",
11 | "coveralls": "cat ./coverage/lcov.info | coveralls",
12 | "min": "echo '/*! (c) Andrea Giammarchi - ISC */' > min.js && uglifyjs index.js -c -m >> min.js",
13 | "rollup": "rollup --config rollup.config.js && drop-babel-typeof index.js",
14 | "size": "cat index.js | wc -c;cat min.js | wc -c;gzip -c9 min.js | wc -c",
15 | "test": "istanbul cover test/test.js"
16 | },
17 | "keywords": [
18 | "hooks",
19 | "dom",
20 | "observer",
21 | "augmentor"
22 | ],
23 | "author": "Andrea Giammarchi",
24 | "license": "ISC",
25 | "greenkeeper": {
26 | "ignore": [
27 | "rollup",
28 | "rollup-plugin-babel",
29 | "rollup-plugin-node-resolve"
30 | ]
31 | },
32 | "dependencies": {
33 | "@ungap/custom-event": "^0.2.0",
34 | "@ungap/weakset": "^0.1.5",
35 | "augmentor": "^2.2.0",
36 | "disconnected": "^0.2.1"
37 | },
38 | "devDependencies": {
39 | "@babel/core": "^7.9.0",
40 | "@babel/preset-env": "^7.9.5",
41 | "ascjs": "^3.1.2",
42 | "basichtml": "^2.2.3",
43 | "coveralls": "^3.0.11",
44 | "drop-babel-typeof": "^1.0.3",
45 | "http-server": "^0.12.1",
46 | "istanbul": "^0.4.5",
47 | "rollup": "^2.6.0",
48 | "rollup-plugin-babel": "^4.4.0",
49 | "rollup-plugin-node-resolve": "^5.2.0",
50 | "uglify-es": "^3.3.9"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dom-augmentor
2 |
3 | [](https://travis-ci.com/WebReflection/dom-augmentor) [](https://coveralls.io/github/WebReflection/dom-augmentor?branch=master) [](https://greenkeeper.io/) 
4 |
5 | **Social Media Photo by [stephan sorkin](https://unsplash.com/@sorkin) on [Unsplash](https://unsplash.com/)**
6 |
7 | This is exactly the same as the [augmentor](https://github.com/WebReflection/augmentor) module, except it handles automatically effects per DOM nodes.
8 |
9 | Compatible with any function that returns a DOM node, or a fragment, or a hyperhtml like Wire instance.
10 |
11 |
12 |
13 | ### Breaking in v1
14 |
15 | * the default export has been removed, it's `import {augmentor} from 'augmentor'` now
16 | * the `augmentor` library is the v1 one
17 | * effects now work more reliably and there are no constrains for the guards
18 |
19 |
20 |
21 | ### Example
22 |
23 | **[Live Demo](https://codepen.io/WebReflection/pen/maQXwq)**
24 |
25 | ```js
26 | const {augmentor: $, useEffect, useRef, useState} = augmentor;
27 | const {render, hook} = lighterhtml;
28 | const {html, svg} = hook(useRef);
29 |
30 | const Button = (text) => $(() => {
31 | useEffect(
32 | () => {
33 | console.log('connected');
34 | return () => console.log('disconnected');
35 | },
36 | []
37 | );
38 | const [i, increment] = useState(0);
39 | return html`
40 | `;
43 | });
44 |
45 | const button = Button('hello');
46 |
47 | render(document.body, button);
48 | // alternatively
49 | // document.body.appendChild(button());
50 | ```
51 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*! (c) Andrea Giammarchi - ISC */
3 | const CustomEvent = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/custom-event'));
4 | const WeakSet = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('@ungap/weakset'));
5 |
6 | const disconnected = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('disconnected'));
7 |
8 | const {
9 | augmentor: $augmentor,
10 | dropEffect,
11 | hasEffect
12 | } = require('augmentor');
13 |
14 | const find = node => {
15 | let {firstChild} = node;
16 | while (firstChild && firstChild.nodeType !== 1)
17 | firstChild = firstChild.nextSibling;
18 | if (firstChild)
19 | return firstChild;
20 | throw 'unobservable';
21 | };
22 |
23 | const observe = disconnected({Event: CustomEvent, WeakSet});
24 |
25 | const observer = (element, handler) => {
26 | const {nodeType} = element;
27 | if (nodeType) {
28 | const node = nodeType === 1 ? element : find(element);
29 | observe(node);
30 | node.addEventListener('disconnected', handler, false);
31 | }
32 | else {
33 | const value = element.valueOf();
34 | // give a chance to facades to return a reasonable value
35 | if (value !== element)
36 | observer(value, handler);
37 | }
38 | };
39 |
40 | const augmentor = fn => {
41 | let handler = null;
42 | const hook = $augmentor(fn);
43 | return function () {
44 | const node = hook.apply(this, arguments);
45 | if (hasEffect(hook))
46 | observer(node, handler || (handler = dropEffect.bind(null, hook)));
47 | return node;
48 | };
49 | };
50 | exports.augmentor = augmentor;
51 |
52 | (m => {
53 | exports.contextual = m.contextual;
54 | exports.useState = m.useState;
55 | exports.useEffect = m.useEffect;
56 | exports.useLayoutEffect = m.useLayoutEffect;
57 | exports.useContext = m.useContext;
58 | exports.createContext = m.createContext;
59 | exports.useReducer = m.useReducer;
60 | exports.useCallback = m.useCallback;
61 | exports.useMemo = m.useMemo;
62 | exports.useRef = m.useRef;
63 | })(require('augmentor'));
64 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | type Context = { value: T; provide: (value: T) => void; };
2 |
3 | type RefList = ReadonlyArray;
4 |
5 | type EffectCallback = () => (void | (() => void | undefined));
6 |
7 | type SetStateAction = S | ((prevState: S) => S);
8 | type Dispatch = (value: A) => void;
9 | type Reducer = (prevState: S, action: A) => S;
10 | type ReducerState > =
11 | R extends Reducer ? S : never;
12 | type ReducerAction > =
13 | R extends Reducer ? A : never;
14 |
15 | type RefObject = { current: T | null; };
16 | type MutableRefObject = { current: T; };
17 |
18 | declare module "dom-augmentor" {
19 |
20 | function augmentor any> (input: T): T;
21 |
22 | function useContext (context: Context): T;
23 |
24 | function createContext (initialValue: T): Context;
25 |
26 | function useMemo (callback: () => T, refs: ReadonlyArray): T;
27 |
28 | function useCallback any> (callback: T, refs: RefList): T;
29 |
30 | function useEffect (effect: EffectCallback, refs?: RefList): void;
31 |
32 | function useLayoutEffect (effect: EffectCallback, refs?: RefList): void;
33 |
34 | function useReducer, I>(
35 | reducer: R, initializerArg: I & ReducerState, initializer: (arg: I & ReducerState) => ReducerState,
36 | ): [ReducerState, Dispatch>];
37 |
38 | function useReducer, I>(
39 | reducer: R, initializerArg: I, initializer: (arg: I) => ReducerState,
40 | ): [ReducerState, Dispatch>];
41 |
42 | function useReducer>(
43 | reducer: R, initialState: ReducerState, initializer?: undefined,
44 | ): [ReducerState, Dispatch>];
45 |
46 | function useRef (initialValue: T): MutableRefObject;
47 | function useRef (initialValue: T | null): RefObject;
48 | function useRef (): MutableRefObject;
49 |
50 | function useState (initialState: S | (() => S)): [S, Dispatch>];
51 | function useState (): [S | undefined, Dispatch>];
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | require('basichtml').init();
2 | const {
3 | augmentor,
4 | useEffect,
5 | useRef,
6 | useState
7 | } = require('../cjs');
8 |
9 | const NoEffect = () => augmentor(text => {
10 | const [i, increment] = useState(0);
11 | const {current: button} = useRef(createButton());
12 | button.textContent = `${text} ${i}`;
13 | button.onclick = () => increment(i + 1);
14 | return button;
15 | });
16 |
17 | NoEffect()('nope');
18 |
19 | const Button = () => augmentor(text => {
20 | useEffect(
21 | () => {
22 | console.log('connected');
23 | return () => console.log('disconnected');
24 | },
25 | []
26 | );
27 | const [i, increment] = useState(0);
28 | const {current: button} = useRef(createButton());
29 | button.textContent = `${text} ${i}`;
30 | button.onclick = () => increment(i + 1);
31 | return button;
32 | });
33 |
34 | const Unknown = () => augmentor(text => {
35 | useEffect(
36 | () => {
37 | console.log('connected');
38 | return () => console.log('disconnected');
39 | },
40 | []
41 | );
42 | const [i, increment] = useState(0);
43 | return {
44 | valueOf: () => createButton()
45 | };
46 | });
47 |
48 | const Nope = () => augmentor(text => {
49 | useEffect(
50 | () => {
51 | console.log('connected');
52 | return () => console.log('disconnected');
53 | },
54 | []
55 | );
56 | return {};
57 | });
58 |
59 | const Maybe = () => augmentor(text => {
60 | useEffect(
61 | () => {
62 | console.log('connected');
63 | return () => console.log('disconnected');
64 | }
65 | );
66 | return {};
67 | });
68 |
69 | const Never = () => augmentor(text => {
70 | useEffect(
71 | () => {
72 | console.log('connected');
73 | return () => console.log('disconnected');
74 | },
75 | [1]
76 | );
77 | return {};
78 | });
79 |
80 | const Many = () => augmentor(text => {
81 | useEffect(
82 | () => {
83 | console.log('connected');
84 | return () => console.log('disconnected');
85 | },
86 | []
87 | );
88 | const fragment = document.createDocumentFragment();
89 | fragment.appendChild(document.createTextNode());
90 | fragment.appendChild(createButton()).textContent = text;
91 | return fragment;
92 | });
93 |
94 | const Thrower = () => augmentor(() => {
95 | useEffect(
96 | () => {
97 | console.log('connected');
98 | return () => console.log('disconnected');
99 | },
100 | []
101 | );
102 | return document.createDocumentFragment();
103 | });
104 |
105 | const button = Button()('hello');
106 | const unknown = Unknown()('darkness');
107 |
108 | Nope()();
109 |
110 | Maybe()();
111 | Never()();
112 |
113 |
114 | document.body.appendChild(button);
115 | document.body.appendChild(unknown);
116 | document.body.appendChild(Many()('test'));
117 |
118 | button.click();
119 | button.dispatchEvent(event('connected'));
120 | button.dispatchEvent(event('disconnected'));
121 |
122 | try {
123 | Thrower()();
124 | console.assert(false);
125 | } catch(ok) {
126 | console.log('all good');
127 | }
128 |
129 | function createButton() {
130 | return document.createElement('button');
131 | }
132 |
133 | function event(type) {
134 | const e = document.createEvent('Event');
135 | e.initEvent(type, false, false);
136 | return e;
137 | }
138 |
--------------------------------------------------------------------------------
/min.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi - ISC */
2 | var augmentor=function(n){"use strict";var t={};t.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(n){return t.prototype=new t("").constructor.prototype,t;function t(n,t){t||(t={});var e=document.createEvent("CustomEvent");return e.initCustomEvent(n,!!t.bubbles,!!t.cancelable,t.detail),e}}();var e=t.CustomEvent,r={};try{r.WeakSet=WeakSet}catch(n){!function(n){var t=new n,e=u.prototype;function u(e){t.set(this,new n),e&&e.forEach(this.add,this)}e.add=function(n){return t.get(this).set(n,1),this},e.delete=function(n){return t.get(this).delete(n)},e.has=function(n){return t.get(this).has(n)},r.WeakSet=u}(WeakMap)}var u=r.WeakSet;var o="function"==typeof cancelAnimationFrame,a=o?cancelAnimationFrame:clearTimeout,i=o?requestAnimationFrame:setTimeout;function c(n){var t,e,r,u,c;return f(),function(n,o,a){return r=n,u=o,c=a,e||(e=i(s)),--t<0&&l(!0),l};function s(){f(),r.apply(u,c||[])}function f(){t=n||1/0,e=o?0:null}function l(n){var t=!!e;return t&&(a(e),n&&s()),t}}var s=function(n){return{get:function(t){return n.get(t)},set:function(t,e){return n.set(t,e),e}}},f=null,l=function(n){var t=[];return function e(){var r=f,u=[];f={hook:e,args:arguments,stack:t,i:0,length:t.length,after:u};try{return n.apply(null,arguments)}finally{f=r;for(var o=0,a=u.length;o