@rhtml/custom-attributes 0.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +26 -0
- package/.prettierrc +4 -0
- package/README.md +60 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +16 -0
- package/package.json +40 -0
- package/src/index.ts +157 -0
- package/tsconfig.json +25 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
// Specifies the ESLint parser
|
|
3
|
+
parser: "@typescript-eslint/parser",
|
|
4
|
+
extends: [
|
|
5
|
+
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
|
6
|
+
"plugin:@typescript-eslint/recommended",
|
|
7
|
+
// Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
|
8
|
+
"prettier/@typescript-eslint",
|
|
9
|
+
// Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
|
10
|
+
"plugin:prettier/recommended"
|
|
11
|
+
],
|
|
12
|
+
parserOptions: {
|
|
13
|
+
// Allows for the parsing of modern ECMAScript features
|
|
14
|
+
ecmaVersion: 2018,
|
|
15
|
+
// Allows for the use of imports
|
|
16
|
+
sourceType: "module"
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
"@typescript-eslint/explicit-function-return-type": 0,
|
|
20
|
+
"simple-import-sort/sort": "error",
|
|
21
|
+
"sort-imports": "off",
|
|
22
|
+
"import/order": "off",
|
|
23
|
+
"@typescript-eslint/camelcase": 0
|
|
24
|
+
},
|
|
25
|
+
plugins: ["simple-import-sort"]
|
|
26
|
+
};
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @rhtml/custom-attributes
|
|
2
|
+
|
|
3
|
+
#### Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @rhtml/custom-attributes
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
#### Usage
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { CustomAttributeRegistry, Attribute } from '@rhtml/custom-attributes';
|
|
13
|
+
|
|
14
|
+
const customAttributes = new CustomAttributeRegistry(document);
|
|
15
|
+
|
|
16
|
+
class BackgroundColor extends Attribute {
|
|
17
|
+
OnInit() {
|
|
18
|
+
console.log('Attribute initialized');
|
|
19
|
+
this.setColor();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
OnDestroy() {
|
|
23
|
+
console.log('Attribute destroyed');
|
|
24
|
+
this.element.style.backgroundColor = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
OnUpdate(oldValue: string, newValue: string) {
|
|
28
|
+
console.log('Attribute updated');
|
|
29
|
+
this.setColor();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setColor() {
|
|
33
|
+
this.element.style.backgroundColor = this.value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
customAttributes.define('red', BackgroundColor);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Interface of class Attribute
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
export abstract class Attribute {
|
|
44
|
+
public element: HTMLElement;
|
|
45
|
+
public value: string;
|
|
46
|
+
public name: string;
|
|
47
|
+
|
|
48
|
+
OnInit(): void {
|
|
49
|
+
/* */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
OnDestroy(): void {
|
|
53
|
+
/* */
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
OnUpdate(_oldValue: string, _newValue: string) {
|
|
57
|
+
/* */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare type Constructor<T = {}> = new (...args: never[]) => T;
|
|
2
|
+
export declare abstract class Attribute {
|
|
3
|
+
element?: HTMLElement;
|
|
4
|
+
value?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
OnInit(): void;
|
|
7
|
+
OnDestroy(): void;
|
|
8
|
+
OnUpdate(_oldValue: string, _newValue: string): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class CustomAttributeRegistry {
|
|
11
|
+
private parent;
|
|
12
|
+
private _attrMap;
|
|
13
|
+
private _elementMap;
|
|
14
|
+
private observer;
|
|
15
|
+
constructor(parent: HTMLElement | ShadowRoot);
|
|
16
|
+
define(attrName: string, Constructor: Constructor<Attribute>): void;
|
|
17
|
+
get(element: HTMLElement, attrName: string): Attribute;
|
|
18
|
+
private getConstructor;
|
|
19
|
+
private observe;
|
|
20
|
+
private upgradeAttribute;
|
|
21
|
+
private upgradeElement;
|
|
22
|
+
private downgrade;
|
|
23
|
+
private found;
|
|
24
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomAttributeRegistry = exports.Attribute = void 0;
|
|
4
|
+
class Attribute {
|
|
5
|
+
OnInit() {
|
|
6
|
+
/* */
|
|
7
|
+
}
|
|
8
|
+
OnDestroy() {
|
|
9
|
+
/* */
|
|
10
|
+
}
|
|
11
|
+
OnUpdate(_oldValue, _newValue) {
|
|
12
|
+
/* */
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.Attribute = Attribute;
|
|
16
|
+
class CustomAttributeRegistry {
|
|
17
|
+
constructor(parent) {
|
|
18
|
+
this.parent = parent;
|
|
19
|
+
this._attrMap = new Map();
|
|
20
|
+
this._elementMap = new WeakMap();
|
|
21
|
+
if (!parent) {
|
|
22
|
+
throw new Error('Must be given a parent element');
|
|
23
|
+
}
|
|
24
|
+
this.observe();
|
|
25
|
+
}
|
|
26
|
+
define(attrName, Constructor) {
|
|
27
|
+
this._attrMap.set(attrName, Constructor);
|
|
28
|
+
this.upgradeAttribute(attrName);
|
|
29
|
+
}
|
|
30
|
+
get(element, attrName) {
|
|
31
|
+
const map = this._elementMap.get(element);
|
|
32
|
+
if (!map)
|
|
33
|
+
return;
|
|
34
|
+
return map.get(attrName);
|
|
35
|
+
}
|
|
36
|
+
getConstructor(attrName) {
|
|
37
|
+
return this._attrMap.get(attrName);
|
|
38
|
+
}
|
|
39
|
+
observe() {
|
|
40
|
+
this.observer = new MutationObserver(mutations => {
|
|
41
|
+
for (const mutation of mutations) {
|
|
42
|
+
if (mutation.type === 'attributes') {
|
|
43
|
+
const attr = this.getConstructor(mutation.attributeName);
|
|
44
|
+
if (attr) {
|
|
45
|
+
this.found(mutation.attributeName, mutation.target, mutation.oldValue);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
for (const node of mutation.removedNodes) {
|
|
50
|
+
this.downgrade(node);
|
|
51
|
+
}
|
|
52
|
+
for (const node of mutation.addedNodes) {
|
|
53
|
+
this.upgradeElement(node);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
this.observer.observe(this.parent, {
|
|
59
|
+
childList: true,
|
|
60
|
+
subtree: true,
|
|
61
|
+
attributes: true,
|
|
62
|
+
attributeOldValue: true
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
upgradeAttribute(attrName, doc) {
|
|
66
|
+
const parent = doc || this.parent;
|
|
67
|
+
const matches = parent.querySelectorAll('[' + attrName + ']');
|
|
68
|
+
for (const match of [...matches]) {
|
|
69
|
+
this.found(attrName, match);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
upgradeElement(element) {
|
|
73
|
+
if (element.nodeType !== 1) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const attr of element.attributes) {
|
|
77
|
+
if (this.getConstructor(attr.name)) {
|
|
78
|
+
this.found(attr.name, element);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const [attr] of this._attrMap) {
|
|
82
|
+
this.upgradeAttribute(attr, element);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
downgrade(element) {
|
|
86
|
+
const map = this._elementMap.get(element);
|
|
87
|
+
if (!map) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const [, instance] of map) {
|
|
91
|
+
if (instance.OnDestroy) {
|
|
92
|
+
instance.OnDestroy();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this._elementMap.delete(element);
|
|
96
|
+
}
|
|
97
|
+
found(attrName, el, oldVal) {
|
|
98
|
+
let map = this._elementMap.get(el);
|
|
99
|
+
if (!map) {
|
|
100
|
+
map = new Map();
|
|
101
|
+
this._elementMap.set(el, map);
|
|
102
|
+
}
|
|
103
|
+
const modifier = map.get(attrName);
|
|
104
|
+
const newValue = el.getAttribute(attrName);
|
|
105
|
+
if (!modifier) {
|
|
106
|
+
const Constructor = this.getConstructor(attrName);
|
|
107
|
+
const modifier = new Constructor();
|
|
108
|
+
map.set(attrName, modifier);
|
|
109
|
+
modifier.element = el;
|
|
110
|
+
modifier.name = attrName;
|
|
111
|
+
modifier.value = newValue;
|
|
112
|
+
if (modifier.OnInit) {
|
|
113
|
+
modifier.OnInit();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (newValue == null && !!modifier.value) {
|
|
118
|
+
modifier.value = newValue;
|
|
119
|
+
if (modifier.OnDestroy) {
|
|
120
|
+
modifier.OnDestroy();
|
|
121
|
+
}
|
|
122
|
+
map.delete(attrName);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (newValue !== modifier.value) {
|
|
126
|
+
modifier.value = newValue;
|
|
127
|
+
if (modifier.OnUpdate) {
|
|
128
|
+
modifier.OnUpdate(oldVal, newValue);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.CustomAttributeRegistry = CustomAttributeRegistry;
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
testEnvironment: 'node',
|
|
3
|
+
testPathIgnorePatterns: ['/node_modules/'],
|
|
4
|
+
coverageReporters: ['lcov', 'html'],
|
|
5
|
+
rootDir: './',
|
|
6
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'node'],
|
|
7
|
+
globals: {
|
|
8
|
+
__DEV__: true
|
|
9
|
+
},
|
|
10
|
+
transform: {
|
|
11
|
+
'\\.(ts|tsx)$': 'ts-jest'
|
|
12
|
+
},
|
|
13
|
+
testRegex: '/src/.*\\.spec.(ts|tsx|js)$',
|
|
14
|
+
verbose: true,
|
|
15
|
+
collectCoverage: true
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rhtml/custom-attributes",
|
|
3
|
+
"version": "0.0.95",
|
|
4
|
+
"description": "Custom Attribute Registry",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "npx parcel ./examples/index.html --out-dir build/examples",
|
|
7
|
+
"patch": "npm run build && npm version patch && npm publish --update-readme --access public && npm run delete-dist",
|
|
8
|
+
"delete-dist": "rm -rf dist",
|
|
9
|
+
"clean": "git clean -dxf",
|
|
10
|
+
"lint": "npx eslint . --ext .ts",
|
|
11
|
+
"lint-fix": "npx eslint . --fix --ext .ts",
|
|
12
|
+
"build": "rm -rf dist && npx npx tsc"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git@github.com:rhtml/rhtml.git"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@rxdi/lit-html": "^0.7.133"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"eslint": "^6.7.2",
|
|
23
|
+
"eslint-config-prettier": "^6.7.0",
|
|
24
|
+
"eslint-plugin-prettier": "^3.1.1",
|
|
25
|
+
"eslint-plugin-simple-import-sort": "^5.0.0",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
|
27
|
+
"@typescript-eslint/parser": "^2.10.0",
|
|
28
|
+
"prettier": "^1.19.1",
|
|
29
|
+
"ts-jest": "25.2.1"
|
|
30
|
+
},
|
|
31
|
+
"author": "Kristiyan Tachev",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"browserslist": [
|
|
34
|
+
"last 1 chrome versions"
|
|
35
|
+
],
|
|
36
|
+
"main": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"module": "./dist/index.js",
|
|
39
|
+
"typings": "./dist/index.d.ts"
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
export type Constructor<T = {}> = new (...args: never[]) => T;
|
|
3
|
+
|
|
4
|
+
export abstract class Attribute {
|
|
5
|
+
public element?: HTMLElement;
|
|
6
|
+
public value?: string;
|
|
7
|
+
public name?: string;
|
|
8
|
+
OnInit(): void {
|
|
9
|
+
/* */
|
|
10
|
+
}
|
|
11
|
+
OnDestroy(): void {
|
|
12
|
+
/* */
|
|
13
|
+
}
|
|
14
|
+
OnUpdate(_oldValue: string, _newValue: string) {
|
|
15
|
+
/* */
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CustomAttributeRegistry {
|
|
20
|
+
private _attrMap: Map<string, Constructor<Attribute>> = new Map();
|
|
21
|
+
private _elementMap: WeakMap<
|
|
22
|
+
HTMLElement,
|
|
23
|
+
Map<string, Attribute>
|
|
24
|
+
> = new WeakMap();
|
|
25
|
+
private observer: MutationObserver;
|
|
26
|
+
|
|
27
|
+
constructor(private parent: HTMLElement | ShadowRoot) {
|
|
28
|
+
if (!parent) {
|
|
29
|
+
throw new Error('Must be given a parent element');
|
|
30
|
+
}
|
|
31
|
+
this.observe();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
define(attrName: string, Constructor: Constructor<Attribute>) {
|
|
35
|
+
this._attrMap.set(attrName, Constructor);
|
|
36
|
+
this.upgradeAttribute(attrName);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(element: HTMLElement, attrName: string) {
|
|
40
|
+
const map = this._elementMap.get(element);
|
|
41
|
+
if (!map) return;
|
|
42
|
+
return map.get(attrName);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private getConstructor(attrName: string) {
|
|
46
|
+
return this._attrMap.get(attrName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private observe() {
|
|
50
|
+
this.observer = new MutationObserver(mutations => {
|
|
51
|
+
for (const mutation of mutations) {
|
|
52
|
+
if (mutation.type === 'attributes') {
|
|
53
|
+
const attr = this.getConstructor(mutation.attributeName);
|
|
54
|
+
if (attr) {
|
|
55
|
+
this.found(
|
|
56
|
+
mutation.attributeName,
|
|
57
|
+
mutation.target as never,
|
|
58
|
+
mutation.oldValue
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
for (const node of mutation.removedNodes) {
|
|
63
|
+
this.downgrade(node as never);
|
|
64
|
+
}
|
|
65
|
+
for (const node of mutation.addedNodes) {
|
|
66
|
+
this.upgradeElement(node as never);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.observer.observe(this.parent, {
|
|
73
|
+
childList: true,
|
|
74
|
+
subtree: true,
|
|
75
|
+
attributes: true,
|
|
76
|
+
attributeOldValue: true
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private upgradeAttribute(attrName: string, doc?: HTMLElement) {
|
|
81
|
+
const parent = doc || this.parent;
|
|
82
|
+
|
|
83
|
+
const matches = parent.querySelectorAll('[' + attrName + ']');
|
|
84
|
+
|
|
85
|
+
for (const match of [...matches]) {
|
|
86
|
+
this.found(attrName, match as never);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private upgradeElement(element: HTMLElement) {
|
|
91
|
+
if (element.nodeType !== 1) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const attr of element.attributes) {
|
|
95
|
+
if (this.getConstructor(attr.name)) {
|
|
96
|
+
this.found(attr.name, element);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const [attr] of this._attrMap) {
|
|
100
|
+
this.upgradeAttribute(attr, element);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private downgrade(element: HTMLElement) {
|
|
105
|
+
const map = this._elementMap.get(element);
|
|
106
|
+
if (!map) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
for (const [, instance] of map) {
|
|
110
|
+
if (instance.OnDestroy) {
|
|
111
|
+
instance.OnDestroy();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this._elementMap.delete(element);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private found(attrName: string, el: HTMLElement, oldVal?: string) {
|
|
118
|
+
let map = this._elementMap.get(el);
|
|
119
|
+
if (!map) {
|
|
120
|
+
map = new Map();
|
|
121
|
+
this._elementMap.set(el, map);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const modifier = map.get(attrName);
|
|
125
|
+
const newValue = el.getAttribute(attrName);
|
|
126
|
+
|
|
127
|
+
if (!modifier) {
|
|
128
|
+
const Constructor = this.getConstructor(attrName);
|
|
129
|
+
const modifier = new Constructor();
|
|
130
|
+
map.set(attrName, modifier);
|
|
131
|
+
modifier.element = el;
|
|
132
|
+
modifier.name = attrName;
|
|
133
|
+
modifier.value = newValue;
|
|
134
|
+
if (modifier.OnInit) {
|
|
135
|
+
modifier.OnInit();
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (newValue == null && !!modifier.value) {
|
|
141
|
+
modifier.value = newValue;
|
|
142
|
+
if (modifier.OnDestroy) {
|
|
143
|
+
modifier.OnDestroy();
|
|
144
|
+
}
|
|
145
|
+
map.delete(attrName);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (newValue !== modifier.value) {
|
|
150
|
+
modifier.value = newValue;
|
|
151
|
+
if (modifier.OnUpdate) {
|
|
152
|
+
modifier.OnUpdate(oldVal, newValue);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".",
|
|
4
|
+
"sourceMap": true,
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"emitDecoratorMetadata": true,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"target": "es6",
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"typeRoots": ["node_modules/@types", "./typings.d.ts"],
|
|
14
|
+
"lib": [
|
|
15
|
+
"es2015",
|
|
16
|
+
"es2016",
|
|
17
|
+
"es6",
|
|
18
|
+
"esnext.asynciterable",
|
|
19
|
+
"dom.Iterable",
|
|
20
|
+
"es2017",
|
|
21
|
+
"dom"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"include": ["./typings.d.ts", "src/**/*"]
|
|
25
|
+
}
|