@rhtml/custom-attributes 0.0.112 → 0.0.115
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/README.md +35 -2
- package/dist/attribute.d.ts +15 -0
- package/dist/attribute.js +30 -0
- package/dist/attribute.js.map +1 -0
- package/dist/custom-registry.d.ts +18 -0
- package/dist/custom-registry.js +155 -0
- package/dist/custom-registry.js.map +1 -0
- package/dist/decorators/index.d.ts +21 -0
- package/dist/decorators/index.js +56 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.js +36 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +6 -89
- package/dist/index.js +20 -310
- package/dist/index.js.map +1 -1
- package/dist/media-attribute.d.ts +23 -0
- package/dist/media-attribute.js +69 -0
- package/dist/media-attribute.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -11
- package/src/attribute.ts +35 -0
- package/src/custom-registry.ts +171 -0
- package/src/decorators/index.ts +56 -0
- package/src/helpers/index.ts +35 -0
- package/src/index.ts +6 -388
- package/src/media-attribute.ts +90 -0
- package/src/types.ts +34 -0
- package/.eslintrc.js +0 -26
- package/.prettierrc +0 -4
- package/jest.config.js +0 -16
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Attribute } from './attribute';
|
|
2
|
+
import { observe } from './helpers';
|
|
3
|
+
import { Constructor } from './types';
|
|
4
|
+
|
|
5
|
+
export class CustomAttributeRegistry {
|
|
6
|
+
private _attrMap: Map<string, Constructor<Attribute>> = new Map();
|
|
7
|
+
private _elementMap: Map<HTMLElement, Map<string, Attribute>> = new Map();
|
|
8
|
+
private observer: MutationObserver;
|
|
9
|
+
|
|
10
|
+
constructor(private parent: HTMLElement) {
|
|
11
|
+
if (!parent) {
|
|
12
|
+
throw new Error('Must be given a parent element');
|
|
13
|
+
}
|
|
14
|
+
this.observe();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
define(attrName: string, Constructor: Constructor<Attribute>) {
|
|
18
|
+
this._attrMap.set(attrName.toLowerCase(), Constructor);
|
|
19
|
+
this.upgradeAttribute(attrName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get(element: HTMLElement, attrName: string) {
|
|
23
|
+
const map = this._elementMap.get(element);
|
|
24
|
+
if (!map) return;
|
|
25
|
+
return map.get(attrName.toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private getConstructor(attrName: string) {
|
|
29
|
+
return this._attrMap.get(attrName.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private observe() {
|
|
33
|
+
this.observer = new MutationObserver((mutations) => {
|
|
34
|
+
for (const mutation of mutations) {
|
|
35
|
+
if (mutation.type === 'attributes') {
|
|
36
|
+
const attr = this.getConstructor(mutation.attributeName);
|
|
37
|
+
if (attr) {
|
|
38
|
+
this.found(
|
|
39
|
+
mutation.attributeName,
|
|
40
|
+
mutation.target as never,
|
|
41
|
+
mutation.oldValue
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
for (const node of mutation.removedNodes) {
|
|
46
|
+
this.downgrade(node as never);
|
|
47
|
+
}
|
|
48
|
+
for (const node of mutation.addedNodes) {
|
|
49
|
+
this.upgradeElement(node as never);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.observer.observe(this.parent?.['shadowRoot'] ?? this.parent, {
|
|
55
|
+
childList: true,
|
|
56
|
+
subtree: true,
|
|
57
|
+
attributes: true,
|
|
58
|
+
attributeOldValue: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
unsubscribe() {
|
|
63
|
+
this.observer?.disconnect();
|
|
64
|
+
const values = [...this._elementMap.values()];
|
|
65
|
+
for (const elModifiers of values) {
|
|
66
|
+
const modifiers = [...elModifiers.values()];
|
|
67
|
+
for (const modifier of modifiers) {
|
|
68
|
+
modifier.OnDestroy();
|
|
69
|
+
if (modifier.observer) {
|
|
70
|
+
modifier.observer.disconnect();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
elModifiers.clear();
|
|
74
|
+
}
|
|
75
|
+
this._elementMap.clear();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private upgradeAttribute(attrName: string, doc?: HTMLElement) {
|
|
79
|
+
const parent = doc || this.parent;
|
|
80
|
+
|
|
81
|
+
const matches = parent.querySelectorAll('[' + attrName + ']');
|
|
82
|
+
|
|
83
|
+
for (const match of [...matches]) {
|
|
84
|
+
this.found(attrName, match as never);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private upgradeElement(element: HTMLElement) {
|
|
89
|
+
if (element.nodeType !== 1) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const attr of element.attributes) {
|
|
93
|
+
if (this.getConstructor(attr.name)) {
|
|
94
|
+
this.found(attr.name, element);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [attr] of this._attrMap) {
|
|
98
|
+
this.upgradeAttribute(attr, element);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private downgrade(element: HTMLElement) {
|
|
103
|
+
const map = this._elementMap.get(element);
|
|
104
|
+
if (!map) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const [, instance] of map) {
|
|
108
|
+
if (instance.OnDestroy) {
|
|
109
|
+
instance.OnDestroy();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this._elementMap.delete(element);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private found(attributeName: string, el: HTMLElement, oldVal?: string) {
|
|
116
|
+
let map = this._elementMap.get(el);
|
|
117
|
+
if (!map) {
|
|
118
|
+
map = new Map();
|
|
119
|
+
this._elementMap.set(el, map);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let modifier = map.get(attributeName);
|
|
123
|
+
const attribute = el.getAttribute(attributeName);
|
|
124
|
+
|
|
125
|
+
if (!modifier) {
|
|
126
|
+
const Modifier = this.getConstructor(attributeName);
|
|
127
|
+
modifier = new Modifier();
|
|
128
|
+
if (Modifier.options?.observedAttributes?.length) {
|
|
129
|
+
for (const observedAttribute of Modifier.options.observedAttributes) {
|
|
130
|
+
observe(modifier, observedAttribute);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
modifier.element = el;
|
|
134
|
+
modifier.selector = attributeName;
|
|
135
|
+
|
|
136
|
+
modifier.value = attribute || modifier.value;
|
|
137
|
+
modifier.parent = this.parent;
|
|
138
|
+
|
|
139
|
+
if (modifier.OnInit) {
|
|
140
|
+
modifier.OnInit();
|
|
141
|
+
}
|
|
142
|
+
if (Modifier.options.observe) {
|
|
143
|
+
modifier.observer = new MutationObserver((records) =>
|
|
144
|
+
modifier.OnChange(records)
|
|
145
|
+
);
|
|
146
|
+
modifier.observer.observe(modifier.element, Modifier.options.observe);
|
|
147
|
+
}
|
|
148
|
+
map.set(attributeName, modifier);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (attribute == null && !!modifier.value) {
|
|
153
|
+
modifier.value = attribute;
|
|
154
|
+
if (modifier.OnDestroy) {
|
|
155
|
+
modifier.OnDestroy();
|
|
156
|
+
}
|
|
157
|
+
if (modifier.observer) {
|
|
158
|
+
modifier.observer.disconnect();
|
|
159
|
+
}
|
|
160
|
+
map.delete(attributeName);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (attribute !== modifier.value) {
|
|
164
|
+
modifier.value = attribute;
|
|
165
|
+
if (modifier.OnUpdate) {
|
|
166
|
+
modifier.OnUpdate(oldVal, attribute);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { noop, observe } from '../helpers';
|
|
2
|
+
import { InputOptions, ModifierOptions } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decorator @Input
|
|
6
|
+
* Used to get attribute from element
|
|
7
|
+
*/
|
|
8
|
+
export const Input =
|
|
9
|
+
(options?: InputOptions) => (target, memberName: string) => {
|
|
10
|
+
const OnInit = target.OnInit || noop;
|
|
11
|
+
Object.defineProperty(target, 'OnInit', {
|
|
12
|
+
value() {
|
|
13
|
+
let originalValue = this[memberName];
|
|
14
|
+
|
|
15
|
+
const element = this.element ?? this;
|
|
16
|
+
Object.defineProperty(this, memberName, {
|
|
17
|
+
get: function () {
|
|
18
|
+
originalValue = element.getAttribute(memberName.toLowerCase());
|
|
19
|
+
return originalValue;
|
|
20
|
+
},
|
|
21
|
+
set(value) {
|
|
22
|
+
element.setAttribute(memberName.toLowerCase(), value);
|
|
23
|
+
originalValue = value;
|
|
24
|
+
},
|
|
25
|
+
configurable: true,
|
|
26
|
+
});
|
|
27
|
+
return OnInit.call(this);
|
|
28
|
+
},
|
|
29
|
+
configurable: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (options?.observe) {
|
|
33
|
+
observe(target, memberName);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Decorator @Modifier
|
|
39
|
+
* Accepts parameter options with selector and registry
|
|
40
|
+
*/
|
|
41
|
+
export const Modifier = (options: ModifierOptions) => {
|
|
42
|
+
return (target) => {
|
|
43
|
+
target['options'] = options;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decorator @CustomAttribute
|
|
49
|
+
* Accepts parameter options with selector and registry
|
|
50
|
+
*/
|
|
51
|
+
export const CustomAttribute = Modifier;
|
|
52
|
+
/**
|
|
53
|
+
* Decorator @Directive
|
|
54
|
+
* Accepts parameter options with selector and registry
|
|
55
|
+
*/
|
|
56
|
+
export const Directive = Modifier;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const noop = function () {
|
|
2
|
+
/* */
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const observe = (target: unknown, memberName: string) => {
|
|
6
|
+
const prototype = target.constructor.prototype;
|
|
7
|
+
const OnInit = prototype.OnInit || noop;
|
|
8
|
+
const OnDestroy = prototype.OnDestroy || noop;
|
|
9
|
+
const OnUpdateAttribute = prototype.OnUpdateAttribute || noop;
|
|
10
|
+
|
|
11
|
+
let observer: MutationObserver;
|
|
12
|
+
prototype.OnInit = function () {
|
|
13
|
+
const element = this.element ?? this;
|
|
14
|
+
if (observer) {
|
|
15
|
+
observer.disconnect();
|
|
16
|
+
}
|
|
17
|
+
observer = new MutationObserver(() => {
|
|
18
|
+
OnUpdateAttribute.call(
|
|
19
|
+
this,
|
|
20
|
+
memberName,
|
|
21
|
+
element.getAttribute(memberName)
|
|
22
|
+
);
|
|
23
|
+
target[memberName] = element.getAttribute(memberName);
|
|
24
|
+
});
|
|
25
|
+
observer.observe(element, {
|
|
26
|
+
attributeFilter: [memberName],
|
|
27
|
+
attributes: true,
|
|
28
|
+
});
|
|
29
|
+
return OnInit.call(this);
|
|
30
|
+
};
|
|
31
|
+
prototype.OnDestroy = function () {
|
|
32
|
+
observer.disconnect();
|
|
33
|
+
return OnDestroy.call(this);
|
|
34
|
+
};
|
|
35
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,388 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const noop = function() {
|
|
8
|
-
/* */
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
interface ModifierOptions {
|
|
12
|
-
selector: string;
|
|
13
|
-
registry?(this: HTMLElement): CustomAttributeRegistry;
|
|
14
|
-
observedAttributes?: string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface InputOptions {
|
|
18
|
-
/**
|
|
19
|
-
* If enabled will trigger OnUpdate method on the Attribute
|
|
20
|
-
* */
|
|
21
|
-
observe: true;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const observe = (target: unknown, memberName: string) => {
|
|
25
|
-
const prototype = target.constructor.prototype;
|
|
26
|
-
const OnInit = prototype.OnInit || noop;
|
|
27
|
-
const OnDestroy = prototype.OnDestroy || noop;
|
|
28
|
-
const OnUpdateAttribute = prototype.OnUpdateAttribute || noop;
|
|
29
|
-
|
|
30
|
-
let observer: MutationObserver;
|
|
31
|
-
prototype.OnInit = function() {
|
|
32
|
-
const element = this.element ?? this;
|
|
33
|
-
if (observer) {
|
|
34
|
-
observer.disconnect();
|
|
35
|
-
}
|
|
36
|
-
observer = new MutationObserver(() => {
|
|
37
|
-
OnUpdateAttribute.call(
|
|
38
|
-
this,
|
|
39
|
-
memberName,
|
|
40
|
-
element.getAttribute(memberName)
|
|
41
|
-
);
|
|
42
|
-
target[memberName] = element.getAttribute(memberName);
|
|
43
|
-
});
|
|
44
|
-
observer.observe(element, {
|
|
45
|
-
attributeFilter: [memberName],
|
|
46
|
-
attributes: true
|
|
47
|
-
});
|
|
48
|
-
return OnInit.call(this);
|
|
49
|
-
};
|
|
50
|
-
prototype.OnDestroy = function() {
|
|
51
|
-
observer.disconnect();
|
|
52
|
-
return OnDestroy.call(this);
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Decorator @Input
|
|
58
|
-
* Used to get attribute from element
|
|
59
|
-
*/
|
|
60
|
-
export const Input = (options?: InputOptions) => (
|
|
61
|
-
target,
|
|
62
|
-
memberName: string
|
|
63
|
-
) => {
|
|
64
|
-
const OnInit = target.OnInit || noop;
|
|
65
|
-
Object.defineProperty(target, 'OnInit', {
|
|
66
|
-
value() {
|
|
67
|
-
let originalValue = this[memberName];
|
|
68
|
-
|
|
69
|
-
const element = this.element ?? this;
|
|
70
|
-
Object.defineProperty(this, memberName, {
|
|
71
|
-
get: function() {
|
|
72
|
-
originalValue = element.getAttribute(memberName.toLowerCase());
|
|
73
|
-
return originalValue;
|
|
74
|
-
},
|
|
75
|
-
set(value) {
|
|
76
|
-
element.setAttribute(memberName.toLowerCase(), value);
|
|
77
|
-
originalValue = value;
|
|
78
|
-
},
|
|
79
|
-
configurable: true
|
|
80
|
-
});
|
|
81
|
-
return OnInit.call(this);
|
|
82
|
-
},
|
|
83
|
-
configurable: true
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (options?.observe) {
|
|
87
|
-
observe(target, memberName);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Decorator @Modifier
|
|
93
|
-
* Accepts parameter options with selector and registry
|
|
94
|
-
*/
|
|
95
|
-
export const Modifier = (options: ModifierOptions) => {
|
|
96
|
-
return (target: Function) => {
|
|
97
|
-
target['options'] = options;
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Decorator @CustomAttribute
|
|
103
|
-
* Accepts parameter options with selector and registry
|
|
104
|
-
*/
|
|
105
|
-
export const CustomAttribute = Modifier;
|
|
106
|
-
/**
|
|
107
|
-
* Decorator @Directive
|
|
108
|
-
* Accepts parameter options with selector and registry
|
|
109
|
-
*/
|
|
110
|
-
export const Directive = Modifier;
|
|
111
|
-
|
|
112
|
-
/* */
|
|
113
|
-
export abstract class Attribute<T = {}> {
|
|
114
|
-
public static options: ModifierOptions;
|
|
115
|
-
public element?: HTMLElement;
|
|
116
|
-
public value?: string;
|
|
117
|
-
public selector?: string;
|
|
118
|
-
public parent?: HTMLElement;
|
|
119
|
-
setStyles(keys: T) {
|
|
120
|
-
return (div: HTMLElement | Element | HTMLDivElement) => {
|
|
121
|
-
for (const [key, value] of Object.entries(keys)) {
|
|
122
|
-
div['style'][key] = value;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
OnInit(): void {
|
|
127
|
-
/* */
|
|
128
|
-
}
|
|
129
|
-
OnDestroy(): void {
|
|
130
|
-
/* */
|
|
131
|
-
}
|
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
133
|
-
OnUpdate(_oldValue: string, _newValue: string) {
|
|
134
|
-
/* */
|
|
135
|
-
}
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
137
|
-
OnUpdateAttribute(_name: string, _value: string) {
|
|
138
|
-
/* */
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Media query Attribute
|
|
144
|
-
* for performance reasons it is key value pair
|
|
145
|
-
*/
|
|
146
|
-
export const MediaMatchers = new Map([
|
|
147
|
-
['screen and (max-width: 599px)', 'xs'],
|
|
148
|
-
['screen and (min-width: 600px) and (max-width: 959px)', 'sm'],
|
|
149
|
-
['screen and (min-width: 960px) and (max-width: 1279px)', 'md'],
|
|
150
|
-
['screen and (min-width: 1280px) and (max-width: 1919px)', 'lg'],
|
|
151
|
-
['screen and (min-width: 1920px) and (max-width: 5000px)', 'xl'],
|
|
152
|
-
['screen and (max-width: 959px)', 'lt-md'],
|
|
153
|
-
['screen and (max-width: 1279px)', 'lt-lg'],
|
|
154
|
-
['screen and (max-width: 1919px)', 'lt-xl'],
|
|
155
|
-
['screen and (min-width: 600px)', 'gt-xs'],
|
|
156
|
-
['screen and (min-width: 960px)', 'gt-sm'],
|
|
157
|
-
['screen and (min-width: 1280px)', 'gt-md'],
|
|
158
|
-
['screen and (min-width: 1920px)', 'gt-lg']
|
|
159
|
-
]);
|
|
160
|
-
type MediaEvent = MediaQueryList | MediaQueryListEvent;
|
|
161
|
-
export type EnterMediaQueryAttributes = [MediaEvent, Attr];
|
|
162
|
-
export type ExitMediaQueryAttributes = [MediaEvent, string];
|
|
163
|
-
|
|
164
|
-
export interface OnUpdateMediaQuery {
|
|
165
|
-
OnEnterMediaQuery(tuple: [MediaEvent, Attr]): void;
|
|
166
|
-
OnExitMediaQuery(tuple: [MediaEvent, string]): void;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export const Breakpoints = [...MediaMatchers.values()];
|
|
170
|
-
|
|
171
|
-
export const createFiltersFromSelector = (selector: string) => [
|
|
172
|
-
...Breakpoints.map(breakpoint => `${selector}.${breakpoint}`),
|
|
173
|
-
selector
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
export abstract class MediaQueryAttribute<T> extends Attribute<T>
|
|
177
|
-
implements OnUpdateMediaQuery {
|
|
178
|
-
prevValue: string;
|
|
179
|
-
originalValue: string;
|
|
180
|
-
|
|
181
|
-
private matchers: Map<MediaQueryList, MediaQueryList> = new Map();
|
|
182
|
-
private cachedAttributes: Map<string, Attr> = new Map();
|
|
183
|
-
|
|
184
|
-
listener = (event: MediaQueryList | MediaQueryListEvent) => {
|
|
185
|
-
const key = `${this.selector.toLowerCase()}.${MediaMatchers.get(
|
|
186
|
-
event.media
|
|
187
|
-
)}`;
|
|
188
|
-
const attribute = this.cachedAttributes.get(key);
|
|
189
|
-
|
|
190
|
-
if (event.matches && attribute) {
|
|
191
|
-
return this.OnEnterMediaQuery([event, attribute]);
|
|
192
|
-
}
|
|
193
|
-
return this.OnExitMediaQuery([event, key]);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
OnInit() {
|
|
197
|
-
if (this.OnEnterMediaQuery || this.OnExitMediaQuery) {
|
|
198
|
-
this.originalValue = this.value;
|
|
199
|
-
for (const query of MediaMatchers.keys()) {
|
|
200
|
-
const matcher = window.matchMedia(query);
|
|
201
|
-
|
|
202
|
-
const attr = Object.values(this.element.attributes).find(
|
|
203
|
-
v =>
|
|
204
|
-
v.name ===
|
|
205
|
-
`${this.selector.toLowerCase()}.${MediaMatchers.get(query)}`
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
if (attr) {
|
|
209
|
-
this.cachedAttributes.set(attr.name, attr);
|
|
210
|
-
matcher.addEventListener('change', this.listener);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (attr && matcher.matches) {
|
|
214
|
-
this.listener(matcher);
|
|
215
|
-
}
|
|
216
|
-
this.matchers.set(matcher, matcher);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
OnDestroy() {
|
|
222
|
-
for (const matcher of this.matchers.values()) {
|
|
223
|
-
matcher.removeEventListener('change', this.listener);
|
|
224
|
-
}
|
|
225
|
-
this.cachedAttributes.clear();
|
|
226
|
-
this.matchers.clear();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
abstract OnEnterMediaQuery(tuple: [MediaEvent, Attr]): void;
|
|
230
|
-
abstract OnExitMediaQuery(tuple: [MediaEvent, string]): void;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/* Media query Attribute END */
|
|
234
|
-
|
|
235
|
-
export class CustomAttributeRegistry {
|
|
236
|
-
private _attrMap: Map<string, Constructor<Attribute>> = new Map();
|
|
237
|
-
private _elementMap: Map<HTMLElement, Map<string, Attribute>> = new Map();
|
|
238
|
-
private observer: MutationObserver;
|
|
239
|
-
|
|
240
|
-
constructor(private parent: HTMLElement) {
|
|
241
|
-
if (!parent) {
|
|
242
|
-
throw new Error('Must be given a parent element');
|
|
243
|
-
}
|
|
244
|
-
this.observe();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
define(attrName: string, Constructor: Constructor<Attribute>) {
|
|
248
|
-
this._attrMap.set(attrName.toLowerCase(), Constructor);
|
|
249
|
-
this.upgradeAttribute(attrName);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
get(element: HTMLElement, attrName: string) {
|
|
253
|
-
const map = this._elementMap.get(element);
|
|
254
|
-
if (!map) return;
|
|
255
|
-
return map.get(attrName.toLowerCase());
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private getConstructor(attrName: string) {
|
|
259
|
-
return this._attrMap.get(attrName.toLowerCase());
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private observe() {
|
|
263
|
-
this.observer = new MutationObserver(mutations => {
|
|
264
|
-
for (const mutation of mutations) {
|
|
265
|
-
if (mutation.type === 'attributes') {
|
|
266
|
-
const attr = this.getConstructor(mutation.attributeName);
|
|
267
|
-
if (attr) {
|
|
268
|
-
this.found(
|
|
269
|
-
mutation.attributeName,
|
|
270
|
-
mutation.target as never,
|
|
271
|
-
mutation.oldValue
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
} else {
|
|
275
|
-
for (const node of mutation.removedNodes) {
|
|
276
|
-
this.downgrade(node as never);
|
|
277
|
-
}
|
|
278
|
-
for (const node of mutation.addedNodes) {
|
|
279
|
-
this.upgradeElement(node as never);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
this.observer.observe(this.parent?.['shadowRoot'] ?? this.parent, {
|
|
285
|
-
childList: true,
|
|
286
|
-
subtree: true,
|
|
287
|
-
attributes: true,
|
|
288
|
-
attributeOldValue: true
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
unsubscribe() {
|
|
293
|
-
this.observer?.disconnect();
|
|
294
|
-
const values = [...this._elementMap.values()];
|
|
295
|
-
for (const elModifiers of values) {
|
|
296
|
-
const modifiers = [...elModifiers.values()];
|
|
297
|
-
for (const modifier of modifiers) {
|
|
298
|
-
modifier.OnDestroy();
|
|
299
|
-
}
|
|
300
|
-
elModifiers.clear();
|
|
301
|
-
}
|
|
302
|
-
this._elementMap.clear();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private upgradeAttribute(attrName: string, doc?: HTMLElement) {
|
|
306
|
-
const parent = doc || this.parent;
|
|
307
|
-
|
|
308
|
-
const matches = parent.querySelectorAll('[' + attrName + ']');
|
|
309
|
-
|
|
310
|
-
for (const match of [...matches]) {
|
|
311
|
-
this.found(attrName, match as never);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
private upgradeElement(element: HTMLElement) {
|
|
316
|
-
if (element.nodeType !== 1) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
for (const attr of element.attributes) {
|
|
320
|
-
if (this.getConstructor(attr.name)) {
|
|
321
|
-
this.found(attr.name, element);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
for (const [attr] of this._attrMap) {
|
|
325
|
-
this.upgradeAttribute(attr, element);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private downgrade(element: HTMLElement) {
|
|
330
|
-
const map = this._elementMap.get(element);
|
|
331
|
-
if (!map) {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
for (const [, instance] of map) {
|
|
335
|
-
if (instance.OnDestroy) {
|
|
336
|
-
instance.OnDestroy();
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
this._elementMap.delete(element);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private found(attributeName: string, el: HTMLElement, oldVal?: string) {
|
|
343
|
-
let map = this._elementMap.get(el);
|
|
344
|
-
if (!map) {
|
|
345
|
-
map = new Map();
|
|
346
|
-
this._elementMap.set(el, map);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const modifier = map.get(attributeName);
|
|
350
|
-
const attribute = el.getAttribute(attributeName);
|
|
351
|
-
|
|
352
|
-
if (!modifier) {
|
|
353
|
-
const Modifier = this.getConstructor(attributeName);
|
|
354
|
-
const modifier = new Modifier();
|
|
355
|
-
if (Modifier.options?.observedAttributes?.length) {
|
|
356
|
-
for (const observedAttribute of Modifier.options.observedAttributes) {
|
|
357
|
-
observe(modifier, observedAttribute);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
map.set(attributeName, modifier);
|
|
361
|
-
modifier.element = el;
|
|
362
|
-
modifier.selector = attributeName;
|
|
363
|
-
modifier.value = attribute || modifier.value;
|
|
364
|
-
modifier.parent = this.parent;
|
|
365
|
-
|
|
366
|
-
if (modifier.OnInit) {
|
|
367
|
-
modifier.OnInit();
|
|
368
|
-
}
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (attribute == null && !!modifier.value) {
|
|
373
|
-
modifier.value = attribute;
|
|
374
|
-
if (modifier.OnDestroy) {
|
|
375
|
-
modifier.OnDestroy();
|
|
376
|
-
}
|
|
377
|
-
map.delete(attributeName);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (attribute !== modifier.value) {
|
|
381
|
-
modifier.value = attribute;
|
|
382
|
-
if (modifier.OnUpdate) {
|
|
383
|
-
modifier.OnUpdate(oldVal, attribute);
|
|
384
|
-
}
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
1
|
+
export * from './attribute';
|
|
2
|
+
export * from './custom-registry';
|
|
3
|
+
export * from './decorators';
|
|
4
|
+
export * from './helpers';
|
|
5
|
+
export * from './media-attribute';
|
|
6
|
+
export * from './types';
|