@schukai/monster 3.48.0 → 3.49.0
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/package.json +1 -1
- package/source/dom/constants.mjs +2 -2
- package/source/dom/customcontrol.mjs +63 -59
- package/source/dom/customelement.mjs +128 -131
- package/source/types/version.mjs +1 -1
- package/test/cases/dom/customcontrol.mjs +21 -45
- package/test/cases/dom/customelement.mjs +2 -1
- package/test/cases/monster.mjs +1 -1
package/package.json
CHANGED
package/source/dom/constants.mjs
CHANGED
|
@@ -61,7 +61,7 @@ export {
|
|
|
61
61
|
customElementUpdaterLinkSymbol,
|
|
62
62
|
initControlCallbackName,
|
|
63
63
|
ATTRIBUTE_SCRIPT_HOST,
|
|
64
|
-
|
|
64
|
+
ATTRIBUTE_INIT_CALLBACK
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -117,7 +117,7 @@ const ATTRIBUTE_OPTIONS_SELECTOR = `${ATTRIBUTE_PREFIX}options-selector`;
|
|
|
117
117
|
* @since 3.48.0
|
|
118
118
|
* @type {string}
|
|
119
119
|
*/
|
|
120
|
-
const
|
|
120
|
+
const ATTRIBUTE_INIT_CALLBACK = `${ATTRIBUTE_PREFIX}init-callback`;
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* This is the name of the callback to pass the callback to a control
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {extend} from "../data/extend.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import {addAttributeToken} from "./attributes.mjs";
|
|
10
|
+
import {ATTRIBUTE_ERRORMESSAGE} from "./constants.mjs";
|
|
10
11
|
import {CustomElement, attributeObserverSymbol} from "./customelement.mjs";
|
|
11
12
|
import {instanceSymbol} from "../constants.mjs";
|
|
12
13
|
|
|
@@ -19,57 +20,66 @@ export {CustomControl};
|
|
|
19
20
|
const attachedInternalSymbol = Symbol("attachedInternal");
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
+
* This is a base class for creating custom controls using the power of CustomElement.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* After defining a `CustomElement`, the `registerCustomElement` method must be called with the new class name. Only then
|
|
26
|
+
* will the tag defined via the `getTag` method be made known to the DOM.
|
|
26
27
|
*
|
|
27
28
|
* <img src="./images/customcontrol-class.png">
|
|
28
29
|
*
|
|
29
|
-
* This control uses `attachInternals()` to integrate the control into a form.
|
|
30
|
-
*
|
|
30
|
+
* This control uses `attachInternals()` to integrate the control into a form. If the target environment does not support
|
|
31
|
+
* this method, the [polyfill](https://www.npmjs.com/package/element-internals-polyfill) can be used.
|
|
31
32
|
*
|
|
32
|
-
* You can create the object
|
|
33
|
+
* You can create the object using the function `document.createElement()`.
|
|
33
34
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* skinparam shadowing false
|
|
37
|
-
* HTMLElement <|-- CustomElement
|
|
38
|
-
* CustomElement <|-- CustomControl
|
|
39
|
-
* @enduml
|
|
35
|
+
* This control uses `attachInternals()` to integrate the control into a form. If the target environment does not support
|
|
36
|
+
* this method, the Polyfill for attachInternals() can be used: {@link https://www.npmjs.com/package/element-internals-polyfill|element-internals-polyfill}.
|
|
40
37
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
38
|
+
* Learn more about WICG Web Components: {@link https://github.com/WICG/webcomponents|WICG Web Components}.
|
|
39
|
+
*
|
|
40
|
+
* Read the HTML specification for Custom Elements: {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements|Custom Elements}.
|
|
41
|
+
*
|
|
42
|
+
* Read the HTML specification for Custom Element Reactions: {@link https://html.spec.whatwg.org/dev/custom-elements.html#custom-element-reactions|Custom Element Reactions}.
|
|
43
|
+
*
|
|
44
|
+
* @summary A base class for custom controls based on CustomElement.
|
|
45
|
+
* @copyright schukai GmbH
|
|
46
46
|
* @license AGPLv3
|
|
47
47
|
* @since 1.14.0
|
|
48
|
-
* @copyright schukai GmbH
|
|
49
48
|
* @memberOf Monster.DOM
|
|
49
|
+
* @extends Monster.DOM.CustomElement
|
|
50
50
|
*/
|
|
51
51
|
class CustomControl extends CustomElement {
|
|
52
|
+
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
+
* The constructor method of CustomControl, which is called when creating a new instance.
|
|
55
|
+
* It checks whether the element supports `attachInternals()` and initializes an internal form-associated element
|
|
56
|
+
* if supported. Additionally, it initializes a MutationObserver to watch for attribute changes.
|
|
57
|
+
*
|
|
58
|
+
* See the links below for more information:
|
|
59
|
+
* {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define|CustomElementRegistry.define()}
|
|
60
|
+
* {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get|CustomElementRegistry.get()}
|
|
61
|
+
* and {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals|ElementInternals}
|
|
54
62
|
*
|
|
63
|
+
* @inheritdoc
|
|
55
64
|
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
|
|
56
|
-
* @
|
|
65
|
+
* @since 1.7.0
|
|
57
66
|
*/
|
|
58
67
|
constructor() {
|
|
59
68
|
super();
|
|
60
69
|
|
|
70
|
+
// check if element supports `attachInternals()`
|
|
61
71
|
if (typeof this["attachInternals"] === "function") {
|
|
62
|
-
/**
|
|
63
|
-
* currently only supported by chrome
|
|
64
|
-
* @property {Object}
|
|
65
|
-
* @private
|
|
66
|
-
*/
|
|
67
72
|
this[attachedInternalSymbol] = this.attachInternals();
|
|
73
|
+
} else {
|
|
74
|
+
// `attachInternals()` is not supported, so a polyfill is necessary
|
|
75
|
+
throw Error("the ElementInternals is not supported and a polyfill is necessary");
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
// initialize a MutationObserver to watch for attribute changes
|
|
70
79
|
initObserver.call(this);
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
|
|
73
83
|
/**
|
|
74
84
|
* This method is called by the `instanceof` operator.
|
|
75
85
|
* @returns {symbol}
|
|
@@ -90,40 +100,27 @@ class CustomControl extends CustomElement {
|
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
/**
|
|
93
|
-
* Adding a static formAssociated property, with a true value, makes an autonomous custom element a form-associated custom element.
|
|
103
|
+
* Adding a static `formAssociated` property, with a true value, makes an autonomous custom element a form-associated custom element.
|
|
94
104
|
*
|
|
95
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
|
|
96
|
-
* @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example}
|
|
105
|
+
* @see [attachInternals()]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
|
|
106
|
+
* @see [Custom Elements Face Example]{@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example}
|
|
97
107
|
* @since 1.14.0
|
|
98
108
|
* @return {boolean}
|
|
99
109
|
*/
|
|
100
|
-
static formAssociated = true
|
|
110
|
+
static formAssociated = true;
|
|
101
111
|
|
|
102
112
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* ```
|
|
106
|
-
* get defaults() {
|
|
107
|
-
* return extends{}, super.defaults, {
|
|
108
|
-
* myValue:true
|
|
109
|
-
* });
|
|
110
|
-
* }
|
|
111
|
-
* ```
|
|
112
|
-
*
|
|
113
|
-
* @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example}
|
|
114
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
|
|
115
|
-
* @return {object}
|
|
113
|
+
* @inheritdoc
|
|
116
114
|
* @since 1.14.0
|
|
117
|
-
|
|
115
|
+
**/
|
|
118
116
|
get defaults() {
|
|
119
|
-
return extend({
|
|
120
|
-
}, super.defaults);
|
|
117
|
+
return extend({}, super.defaults);
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
/**
|
|
124
121
|
* Must be overridden by a derived class and return the value of the control.
|
|
125
122
|
*
|
|
126
|
-
* This is a method of [internal
|
|
123
|
+
* This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements.
|
|
127
124
|
*
|
|
128
125
|
* @since 1.14.0
|
|
129
126
|
* @throws {Error} the value getter must be overwritten by the derived class
|
|
@@ -133,11 +130,11 @@ class CustomControl extends CustomElement {
|
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
/**
|
|
136
|
-
* Must be overridden by a derived class and
|
|
133
|
+
* Must be overridden by a derived class and set the value of the control.
|
|
137
134
|
*
|
|
138
|
-
* This is a method of [internal
|
|
135
|
+
* This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements.
|
|
139
136
|
*
|
|
140
|
-
* @param {*} value
|
|
137
|
+
* @param {*} value The value to set.
|
|
141
138
|
* @since 1.14.0
|
|
142
139
|
* @throws {Error} the value setter must be overwritten by the derived class
|
|
143
140
|
*/
|
|
@@ -145,6 +142,7 @@ class CustomControl extends CustomElement {
|
|
|
145
142
|
throw Error("the value setter must be overwritten by the derived class");
|
|
146
143
|
}
|
|
147
144
|
|
|
145
|
+
|
|
148
146
|
/**
|
|
149
147
|
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
|
|
150
148
|
*
|
|
@@ -180,8 +178,8 @@ class CustomControl extends CustomElement {
|
|
|
180
178
|
*
|
|
181
179
|
* @return {ValidityState}
|
|
182
180
|
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
|
|
183
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState}
|
|
184
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/validity}
|
|
181
|
+
* @see [ValidityState]{@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState}
|
|
182
|
+
* @see [validity]{@link https://developer.mozilla.org/en-US/docs/Web/API/validity}
|
|
185
183
|
*/
|
|
186
184
|
get validity() {
|
|
187
185
|
return getInternal.call(this)?.validity;
|
|
@@ -214,7 +212,7 @@ class CustomControl extends CustomElement {
|
|
|
214
212
|
/**
|
|
215
213
|
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
|
|
216
214
|
*
|
|
217
|
-
* @return {
|
|
215
|
+
* @return {boolean}
|
|
218
216
|
* @since 1.14.0
|
|
219
217
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states
|
|
220
218
|
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
|
|
@@ -301,12 +299,15 @@ class CustomControl extends CustomElement {
|
|
|
301
299
|
}
|
|
302
300
|
|
|
303
301
|
/**
|
|
304
|
-
*
|
|
302
|
+
* Sets the `form` attribute of the custom control to the `id` of the passed form element.
|
|
303
|
+
* If no form element is passed, removes the `form` attribute.
|
|
304
|
+
*
|
|
305
|
+
* @param {HTMLFormElement} form - The form element to associate with the control
|
|
305
306
|
*/
|
|
306
307
|
formAssociatedCallback(form) {
|
|
307
308
|
if (form) {
|
|
308
|
-
if(form.id) {
|
|
309
|
-
this.setAttribute("form", form.id);
|
|
309
|
+
if (form.id) {
|
|
310
|
+
this.setAttribute("form", form.id);
|
|
310
311
|
}
|
|
311
312
|
} else {
|
|
312
313
|
this.removeAttribute("form");
|
|
@@ -314,7 +315,9 @@ class CustomControl extends CustomElement {
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
/**
|
|
317
|
-
*
|
|
318
|
+
* Sets or removes the `disabled` attribute of the custom control based on the passed value.
|
|
319
|
+
*
|
|
320
|
+
* @param {boolean} disabled - Whether or not the control should be disabled
|
|
318
321
|
*/
|
|
319
322
|
formDisabledCallback(disabled) {
|
|
320
323
|
if (disabled) {
|
|
@@ -324,19 +327,20 @@ class CustomControl extends CustomElement {
|
|
|
324
327
|
}
|
|
325
328
|
}
|
|
326
329
|
|
|
330
|
+
|
|
327
331
|
/**
|
|
328
332
|
* @param {string} state
|
|
329
333
|
* @param {string} mode
|
|
330
334
|
*/
|
|
331
335
|
formStateRestoreCallback(state, mode) {
|
|
332
|
-
|
|
336
|
+
|
|
333
337
|
}
|
|
334
338
|
|
|
335
339
|
/**
|
|
336
340
|
*
|
|
337
341
|
*/
|
|
338
342
|
formResetCallback() {
|
|
339
|
-
|
|
343
|
+
this.value = "";
|
|
340
344
|
}
|
|
341
345
|
|
|
342
346
|
}
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
ATTRIBUTE_DISABLED,
|
|
24
24
|
ATTRIBUTE_ERRORMESSAGE,
|
|
25
25
|
ATTRIBUTE_OPTIONS,
|
|
26
|
-
|
|
26
|
+
ATTRIBUTE_INIT_CALLBACK,
|
|
27
27
|
ATTRIBUTE_OPTIONS_SELECTOR,
|
|
28
28
|
ATTRIBUTE_SCRIPT_HOST,
|
|
29
29
|
customElementUpdaterLinkSymbol,
|
|
@@ -121,15 +121,12 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
|
|
|
121
121
|
*/
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* The `CustomElement` class provides a way to define a new HTML element using the power of Custom Elements.
|
|
125
125
|
*
|
|
126
|
-
* IMPORTANT
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* <img src="./images/customelement-class.png">
|
|
130
|
-
*
|
|
131
|
-
* You can create the object via the function `document.createElement()`.
|
|
126
|
+
* **IMPORTANT:** After defining a `CustomElement`, the `registerCustomElement` method must be called with the new class name
|
|
127
|
+
* to make the tag defined via the `getTag` method known to the DOM.
|
|
132
128
|
*
|
|
129
|
+
* You can create an instance of the object via the `document.createElement()` function.
|
|
133
130
|
*
|
|
134
131
|
* ## Interaction
|
|
135
132
|
*
|
|
@@ -137,15 +134,13 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
|
|
|
137
134
|
*
|
|
138
135
|
* ## Styling
|
|
139
136
|
*
|
|
140
|
-
*
|
|
137
|
+
* To display custom elements optimally, the `:defined` pseudo-class can be used. To prevent custom elements from being displayed and flickering until the control is registered,
|
|
138
|
+
* it is recommended to create a CSS directive.
|
|
141
139
|
*
|
|
142
|
-
*
|
|
140
|
+
* In the simplest case, you can simply hide the control:
|
|
143
141
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* ```
|
|
142
|
+
* ```html
|
|
147
143
|
* <style>
|
|
148
|
-
*
|
|
149
144
|
* my-custom-element:not(:defined) {
|
|
150
145
|
* display: none;
|
|
151
146
|
* }
|
|
@@ -153,62 +148,64 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
|
|
|
153
148
|
* my-custom-element:defined {
|
|
154
149
|
* display: flex;
|
|
155
150
|
* }
|
|
156
|
-
*
|
|
157
151
|
* </style>
|
|
158
152
|
* ```
|
|
159
153
|
*
|
|
160
|
-
* Alternatively you can
|
|
154
|
+
* Alternatively, you can display a loader:
|
|
161
155
|
*
|
|
162
|
-
* ```
|
|
156
|
+
* ```css
|
|
163
157
|
* my-custom-element:not(:defined) {
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
158
|
+
* display: flex;
|
|
159
|
+
* box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15);
|
|
160
|
+
* border-radius: 4px;
|
|
161
|
+
* height: 200px;
|
|
162
|
+
* position: relative;
|
|
163
|
+
* overflow: hidden;
|
|
164
|
+
* }
|
|
171
165
|
*
|
|
172
166
|
* my-custom-element:not(:defined)::before {
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
167
|
+
* content: '';
|
|
168
|
+
* display: block;
|
|
169
|
+
* position: absolute;
|
|
170
|
+
* left: -150px;
|
|
171
|
+
* top: 0;
|
|
172
|
+
* height: 100%;
|
|
173
|
+
* width: 150px;
|
|
174
|
+
* background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%);
|
|
175
|
+
* animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
|
|
176
|
+
* }
|
|
183
177
|
*
|
|
184
178
|
* @keyframes load {
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
179
|
+
* from {
|
|
180
|
+
* left: -150px;
|
|
181
|
+
* }
|
|
182
|
+
* to {
|
|
183
|
+
* left: 100%;
|
|
184
|
+
* }
|
|
185
|
+
* }
|
|
192
186
|
*
|
|
193
187
|
* my-custom-element:defined {
|
|
194
|
-
*
|
|
195
|
-
*
|
|
188
|
+
* display: flex;
|
|
189
|
+
* }
|
|
196
190
|
* ```
|
|
191
|
+
*
|
|
192
|
+
* More information about Custom Elements can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements).
|
|
193
|
+
* And in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
|
|
197
194
|
*
|
|
198
195
|
* @externalExample ../../example/dom/theme.mjs
|
|
199
|
-
* @see https://github.com/WICG/webcomponents
|
|
200
|
-
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements
|
|
201
196
|
* @license AGPLv3
|
|
202
197
|
* @since 1.7.0
|
|
203
198
|
* @copyright schukai GmbH
|
|
204
199
|
* @memberOf Monster.DOM
|
|
205
200
|
* @extends external:HTMLElement
|
|
206
|
-
* @summary A base class for HTML5
|
|
201
|
+
* @summary A base class for HTML5 custom controls.
|
|
207
202
|
*/
|
|
208
203
|
class CustomElement extends HTMLElement {
|
|
209
204
|
/**
|
|
210
205
|
* A new object is created. First the `initOptions` method is called. Here the
|
|
211
206
|
* options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
|
|
207
|
+
*
|
|
208
|
+
* IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
|
|
212
209
|
*
|
|
213
210
|
* @throws {Error} the options attribute does not contain a valid json definition.
|
|
214
211
|
* @since 1.7.0
|
|
@@ -270,53 +267,25 @@ class CustomElement extends HTMLElement {
|
|
|
270
267
|
}
|
|
271
268
|
|
|
272
269
|
/**
|
|
273
|
-
*
|
|
270
|
+
* The `defaults` property defines the default values for a control. If you want to override these,
|
|
271
|
+
* you can use various methods, which are described in the documentation available at
|
|
272
|
+
* {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
|
|
274
273
|
*
|
|
275
|
-
*
|
|
276
|
-
* get defaults() {
|
|
277
|
-
* return Object.assign({}, super.defaults, {
|
|
278
|
-
* myValue:true
|
|
279
|
-
* });
|
|
280
|
-
* }
|
|
281
|
-
* ```
|
|
282
|
-
*
|
|
283
|
-
* To set the options via the html tag the attribute data-monster-options must be set.
|
|
284
|
-
* As value a JSON object with the desired values must be defined.
|
|
285
|
-
*
|
|
286
|
-
* Since 1.18.0 the JSON can be specified as a DataURI.
|
|
287
|
-
*
|
|
288
|
-
* ```
|
|
289
|
-
* new Monster.Types.DataUrl(btoa(JSON.stringify({
|
|
290
|
-
* shadowMode: 'open',
|
|
291
|
-
* delegatesFocus: true,
|
|
292
|
-
* templates: {
|
|
293
|
-
* main: undefined
|
|
294
|
-
* }
|
|
295
|
-
* })),'application/json',true).toString()
|
|
296
|
-
* ```
|
|
297
|
-
*
|
|
298
|
-
* The attribute data-monster-options-selector can be used to access a script tag that contains additional configuration.
|
|
299
|
-
*
|
|
300
|
-
* As value a selector must be specified, which belongs to a script tag and contains the configuration as json.
|
|
301
|
-
*
|
|
302
|
-
* ```
|
|
303
|
-
* <script id="id-for-this-config" type="application/json">
|
|
304
|
-
* {
|
|
305
|
-
* "config-key": "config-value"
|
|
306
|
-
* }
|
|
307
|
-
* </script>
|
|
308
|
-
* ```
|
|
274
|
+
* The individual configuration values are listed below:
|
|
309
275
|
*
|
|
310
|
-
*
|
|
276
|
+
* More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
|
|
277
|
+
* in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
|
|
311
278
|
*
|
|
312
|
-
*
|
|
313
|
-
* @property {string} shadowMode=open `open` Elements of the shadow root are accessible from JavaScript outside the root, for example using. `close` Denies access to the node(s) of a closed shadow root from JavaScript outside it
|
|
314
|
-
* @property {Boolean} delegatesFocus=true A boolean that, when set to true, specifies behavior that mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
|
|
315
|
-
* @property {Object} templates Templates
|
|
316
|
-
* @property {string} templates.main=undefined Main template
|
|
317
|
-
* @property {Object} templateMapping Template mapping
|
|
279
|
+
* More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
|
|
318
280
|
*
|
|
319
|
-
*
|
|
281
|
+
* More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).
|
|
282
|
+
*
|
|
283
|
+
* @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form.
|
|
284
|
+
* @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it.
|
|
285
|
+
* @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
|
|
286
|
+
* @property {Object} templates Specifies the templates used by the control.
|
|
287
|
+
* @property {string} templates.main=undefined Specifies the main template used by the control.
|
|
288
|
+
* @property {Object} templateMapping Specifies the mapping of templates.
|
|
320
289
|
* @since 1.8.0
|
|
321
290
|
*/
|
|
322
291
|
get defaults() {
|
|
@@ -383,39 +352,51 @@ class CustomElement extends HTMLElement {
|
|
|
383
352
|
}
|
|
384
353
|
|
|
385
354
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
355
|
+
* The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
|
|
356
|
+
* by the derived class.
|
|
388
357
|
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
358
|
+
* Note that there is no check on the name of the tag in this class. It is the responsibility of
|
|
359
|
+
* the developer to assign an appropriate tag name. If the name is not valid, the
|
|
360
|
+
* `registerCustomElement()` method will issue an error.
|
|
361
|
+
*
|
|
362
|
+
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
|
363
|
+
* @throws {Error} This method must be overridden by the derived class.
|
|
364
|
+
* @return {string} The tag name associated with the custom element.
|
|
392
365
|
* @since 1.7.0
|
|
393
366
|
*/
|
|
394
367
|
static getTag() {
|
|
395
|
-
throw new Error("
|
|
368
|
+
throw new Error("The method `getTag()` must be overridden by the derived class.");
|
|
396
369
|
}
|
|
397
370
|
|
|
398
371
|
/**
|
|
399
|
-
*
|
|
400
|
-
* support
|
|
372
|
+
* The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
|
|
373
|
+
* If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
|
|
374
|
+
*
|
|
375
|
+
* If `undefined` is returned, then the shadow root does not receive a stylesheet.
|
|
401
376
|
*
|
|
402
|
-
*
|
|
377
|
+
* Example usage:
|
|
403
378
|
*
|
|
379
|
+
* ```js
|
|
380
|
+
* static getCSSStyleSheet() {
|
|
381
|
+
* const sheet = new CSSStyleSheet();
|
|
382
|
+
* sheet.replaceSync("p { color: red; }");
|
|
383
|
+
* return sheet;
|
|
384
|
+
* }
|
|
404
385
|
* ```
|
|
405
|
-
* const doc = document.implementation.createHTMLDocument('title');
|
|
406
386
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
387
|
+
* If the environment does not support the `CSSStyleSheet` constructor,
|
|
388
|
+
* you can use the following workaround to create the stylesheet:
|
|
409
389
|
*
|
|
410
|
-
*
|
|
390
|
+
* ```js
|
|
391
|
+
* const doc = document.implementation.createHTMLDocument('title');
|
|
392
|
+
* let style = doc.createElement("style");
|
|
393
|
+
* style.innerHTML = "p { color: red; }";
|
|
411
394
|
* style.appendChild(document.createTextNode(""));
|
|
412
|
-
* // Add the <style> element to the page
|
|
413
395
|
* doc.head.appendChild(style);
|
|
414
396
|
* return doc.styleSheets[0];
|
|
415
|
-
* ;
|
|
416
397
|
* ```
|
|
417
398
|
*
|
|
418
|
-
* @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined}
|
|
399
|
+
* @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied.
|
|
419
400
|
*/
|
|
420
401
|
static getCSSStyleSheet() {
|
|
421
402
|
return undefined;
|
|
@@ -511,9 +492,14 @@ class CustomElement extends HTMLElement {
|
|
|
511
492
|
}
|
|
512
493
|
|
|
513
494
|
/**
|
|
514
|
-
*
|
|
495
|
+
* This method is called once when the object is included in the DOM for the first time. It performs the following actions:
|
|
496
|
+
* 1. Extracts the options from the attributes and the script tag of the element and sets them.
|
|
497
|
+
* 2. Initializes the shadow root and its CSS stylesheet (if specified).
|
|
498
|
+
* 3. Initializes the HTML content of the element.
|
|
499
|
+
* 4. Initializes the custom elements inside the shadow root and the slotted elements.
|
|
500
|
+
* 5. Attaches a mutation observer to observe changes to the attributes of the element.
|
|
515
501
|
*
|
|
516
|
-
* @return {CustomElement}
|
|
502
|
+
* @return {CustomElement} - The updated custom element.
|
|
517
503
|
* @since 1.8.0
|
|
518
504
|
*/
|
|
519
505
|
[assembleMethodSymbol]() {
|
|
@@ -521,22 +507,25 @@ class CustomElement extends HTMLElement {
|
|
|
521
507
|
let elements;
|
|
522
508
|
let nodeList;
|
|
523
509
|
|
|
510
|
+
// Extract options from attributes and set them
|
|
524
511
|
const AttributeOptions = getOptionsFromAttributes.call(self);
|
|
525
512
|
if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) {
|
|
526
513
|
self.setOptions(AttributeOptions);
|
|
527
514
|
}
|
|
528
515
|
|
|
516
|
+
// Extract options from script tag and set them
|
|
529
517
|
const ScriptOptions = getOptionsFromScriptTag.call(self);
|
|
530
518
|
if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
|
|
531
519
|
self.setOptions(ScriptOptions);
|
|
532
520
|
}
|
|
533
521
|
|
|
534
|
-
|
|
522
|
+
// Initialize the shadow root and its CSS stylesheet
|
|
535
523
|
if (self.getOption("shadowMode", false) !== false) {
|
|
536
524
|
try {
|
|
537
525
|
initShadowRoot.call(self);
|
|
538
526
|
elements = self.shadowRoot.childNodes;
|
|
539
527
|
} catch (e) {
|
|
528
|
+
addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
|
|
540
529
|
}
|
|
541
530
|
|
|
542
531
|
try {
|
|
@@ -546,21 +535,19 @@ class CustomElement extends HTMLElement {
|
|
|
546
535
|
}
|
|
547
536
|
}
|
|
548
537
|
|
|
538
|
+
// If the elements are not found inside the shadow root, initialize the HTML content of the element
|
|
549
539
|
if (!(elements instanceof NodeList)) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
elements = this.childNodes;
|
|
553
|
-
}
|
|
540
|
+
initHtmlContent.call(this);
|
|
541
|
+
elements = this.childNodes;
|
|
554
542
|
}
|
|
555
543
|
|
|
544
|
+
// Initialize the custom elements inside the shadow root and the slotted elements
|
|
556
545
|
initFromCallbackHost.call(this);
|
|
557
|
-
|
|
558
546
|
try {
|
|
559
547
|
nodeList = new Set([...elements, ...getSlottedElements.call(self)]);
|
|
560
548
|
} catch (e) {
|
|
561
549
|
nodeList = elements;
|
|
562
550
|
}
|
|
563
|
-
|
|
564
551
|
addObjectWithUpdaterToElement.call(
|
|
565
552
|
self,
|
|
566
553
|
nodeList,
|
|
@@ -568,26 +555,33 @@ class CustomElement extends HTMLElement {
|
|
|
568
555
|
clone(self[internalSymbol].getRealSubject()["options"]),
|
|
569
556
|
);
|
|
570
557
|
|
|
558
|
+
// Attach a mutation observer to observe changes to the attributes of the element
|
|
571
559
|
attachAttributeChangeMutationObserver.call(this);
|
|
572
560
|
|
|
573
561
|
return self;
|
|
574
562
|
}
|
|
575
563
|
|
|
576
564
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
565
|
+
* This method is called every time the element is inserted into the DOM. It checks if the custom element
|
|
566
|
+
* has already been initialized and if not, calls the assembleMethod to initialize it.
|
|
579
567
|
*
|
|
580
568
|
* @return {void}
|
|
581
569
|
* @since 1.7.0
|
|
570
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
|
|
582
571
|
*/
|
|
583
572
|
connectedCallback() {
|
|
584
|
-
|
|
573
|
+
const self = this;
|
|
574
|
+
|
|
575
|
+
// Check if the object has already been initialized
|
|
585
576
|
if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
|
|
577
|
+
|
|
578
|
+
// If not, call the assembleMethod to initialize the object
|
|
586
579
|
self[assembleMethodSymbol]();
|
|
587
580
|
}
|
|
588
|
-
|
|
589
581
|
}
|
|
590
582
|
|
|
583
|
+
|
|
584
|
+
|
|
591
585
|
/**
|
|
592
586
|
* Called every time the element is removed from the DOM. Useful for running clean up code.
|
|
593
587
|
*
|
|
@@ -725,31 +719,34 @@ function callControlCallback(callBackFunctionName, ...args) {
|
|
|
725
719
|
}
|
|
726
720
|
|
|
727
721
|
/**
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
* It looks for the attribute `data-monster-option-callback`. Is this attribute is not set, the default callback
|
|
731
|
-
* `initCustomControlCallback` is called.
|
|
722
|
+
* Initializes the custom element based on the provided callback function.
|
|
732
723
|
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
724
|
+
* This function is called when the element is attached to the DOM. It checks if the
|
|
725
|
+
* `data-monster-option-callback` attribute is set, and if not, the default callback
|
|
726
|
+
* `initCustomControlCallback` is called. The callback function is searched for in this
|
|
727
|
+
* element and in the host element. If the callback is found, it is called with the element
|
|
728
|
+
* as a parameter.
|
|
737
729
|
*
|
|
738
730
|
* @this CustomElement
|
|
731
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#providing_a_construction_callback
|
|
732
|
+
* @since 1.8.0
|
|
739
733
|
*/
|
|
740
734
|
function initFromCallbackHost() {
|
|
741
735
|
const self = this;
|
|
742
736
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
737
|
+
// Set the default callback function name
|
|
738
|
+
let callBackFunctionName = initControlCallbackName;
|
|
739
|
+
|
|
740
|
+
// If the `data-monster-option-callback` attribute is set, use its value as the callback function name
|
|
741
|
+
if (self.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
|
|
742
|
+
callBackFunctionName = self.getAttribute(ATTRIBUTE_INIT_CALLBACK);
|
|
746
743
|
}
|
|
747
744
|
|
|
745
|
+
// Call the callback function with the element as a parameter if it exists
|
|
748
746
|
callControlCallback.call(self, callBackFunctionName);
|
|
749
|
-
|
|
750
|
-
|
|
751
747
|
}
|
|
752
748
|
|
|
749
|
+
|
|
753
750
|
/**
|
|
754
751
|
* This method is called when the element is first created.
|
|
755
752
|
*
|
package/source/types/version.mjs
CHANGED
|
@@ -22,6 +22,10 @@ describe('DOM', function () {
|
|
|
22
22
|
before(function (done) {
|
|
23
23
|
initJSDOM().then(() => {
|
|
24
24
|
|
|
25
|
+
import("element-internals-polyfill").then((m) => {
|
|
26
|
+
m.polyfill();
|
|
27
|
+
});
|
|
28
|
+
|
|
25
29
|
// jsdom does not support ElementInternals
|
|
26
30
|
jsdomFlag = navigator.userAgent.includes("jsdom");
|
|
27
31
|
|
|
@@ -72,8 +76,13 @@ describe('DOM', function () {
|
|
|
72
76
|
|
|
73
77
|
describe('create', function () {
|
|
74
78
|
it('should return custom-element object', function () {
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
try {
|
|
80
|
+
let d = new TestComponent();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
expect(e).to.be.not.null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
expect(typeof d).is.equal('undefined');
|
|
77
86
|
});
|
|
78
87
|
});
|
|
79
88
|
|
|
@@ -84,7 +93,7 @@ describe('DOM', function () {
|
|
|
84
93
|
document.getElementById('test1').appendChild(d);
|
|
85
94
|
expect(document.getElementsByTagName('monster-customcontrol').length).is.equal(1);
|
|
86
95
|
// no data-monster-objectlink="Symbol(monsterUpdater)" because it has nothing to update
|
|
87
|
-
expect(document.getElementById('test1')).contain.html('<monster-customcontrol></monster-customcontrol>')
|
|
96
|
+
expect(document.getElementById('test1')).contain.html('<monster-customcontrol data-monster-error="Error: html is not set."></monster-customcontrol>')
|
|
88
97
|
});
|
|
89
98
|
});
|
|
90
99
|
|
|
@@ -129,11 +138,13 @@ describe('DOM', function () {
|
|
|
129
138
|
let d = document.createElement('monster-customcontrol');
|
|
130
139
|
form.appendChild(d);
|
|
131
140
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
141
|
+
expect(d.form).to.be.instanceof(HTMLFormElement)
|
|
142
|
+
|
|
143
|
+
// if (jsdomFlag) {
|
|
144
|
+
// expect(() => d.form).to.throw(Error);
|
|
145
|
+
// } else {
|
|
146
|
+
// expect(d.form).to.be.instanceof(HTMLFormElement)
|
|
147
|
+
// }
|
|
137
148
|
|
|
138
149
|
|
|
139
150
|
});
|
|
@@ -160,13 +171,7 @@ describe('DOM', function () {
|
|
|
160
171
|
|
|
161
172
|
let d = document.createElement('monster-customcontrol');
|
|
162
173
|
form.appendChild(d);
|
|
163
|
-
|
|
164
|
-
if (jsdomFlag) {
|
|
165
|
-
expect(() => d.setFormValue()).to.throw(Error);
|
|
166
|
-
} else {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
174
|
+
|
|
170
175
|
});
|
|
171
176
|
|
|
172
177
|
it('name getter', function () {
|
|
@@ -191,11 +196,6 @@ describe('DOM', function () {
|
|
|
191
196
|
|
|
192
197
|
let d = document.createElement('monster-customcontrol');
|
|
193
198
|
form.appendChild(d);
|
|
194
|
-
if (jsdomFlag) {
|
|
195
|
-
expect(() => d.validity).to.throw(Error);
|
|
196
|
-
} else {
|
|
197
|
-
|
|
198
|
-
}
|
|
199
199
|
|
|
200
200
|
});
|
|
201
201
|
|
|
@@ -204,11 +204,6 @@ describe('DOM', function () {
|
|
|
204
204
|
let d = document.createElement('monster-customcontrol');
|
|
205
205
|
form.appendChild(d);
|
|
206
206
|
|
|
207
|
-
if (jsdomFlag) {
|
|
208
|
-
expect(() => d.validity).to.throw(Error);
|
|
209
|
-
} else {
|
|
210
|
-
|
|
211
|
-
}
|
|
212
207
|
|
|
213
208
|
});
|
|
214
209
|
|
|
@@ -217,11 +212,6 @@ describe('DOM', function () {
|
|
|
217
212
|
let d = document.createElement('monster-customcontrol');
|
|
218
213
|
form.appendChild(d);
|
|
219
214
|
|
|
220
|
-
if (jsdomFlag) {
|
|
221
|
-
expect(() => d.willValidate).to.throw(Error);
|
|
222
|
-
} else {
|
|
223
|
-
|
|
224
|
-
}
|
|
225
215
|
|
|
226
216
|
});
|
|
227
217
|
it('checkValidity()', function () {
|
|
@@ -229,11 +219,6 @@ describe('DOM', function () {
|
|
|
229
219
|
let d = document.createElement('monster-customcontrol');
|
|
230
220
|
form.appendChild(d);
|
|
231
221
|
|
|
232
|
-
if (jsdomFlag) {
|
|
233
|
-
expect(() => d.checkValidity()).to.throw(Error);
|
|
234
|
-
} else {
|
|
235
|
-
|
|
236
|
-
}
|
|
237
222
|
|
|
238
223
|
});
|
|
239
224
|
|
|
@@ -242,11 +227,6 @@ describe('DOM', function () {
|
|
|
242
227
|
let d = document.createElement('monster-customcontrol');
|
|
243
228
|
form.appendChild(d);
|
|
244
229
|
|
|
245
|
-
if (jsdomFlag) {
|
|
246
|
-
expect(() => d.reportValidity()).to.throw(Error);
|
|
247
|
-
} else {
|
|
248
|
-
|
|
249
|
-
}
|
|
250
230
|
|
|
251
231
|
});
|
|
252
232
|
|
|
@@ -255,11 +235,7 @@ describe('DOM', function () {
|
|
|
255
235
|
|
|
256
236
|
let d = document.createElement('monster-customcontrol');
|
|
257
237
|
form.appendChild(d);
|
|
258
|
-
|
|
259
|
-
expect(() => d.setValidity()).to.throw(Error);
|
|
260
|
-
} else {
|
|
261
|
-
expect(d.setValidity({'valueMissing': true}, "my message")).to.be.undefined;
|
|
262
|
-
}
|
|
238
|
+
expect(d.setValidity({'valueMissing': true}, "my message")).to.be.undefined;
|
|
263
239
|
|
|
264
240
|
});
|
|
265
241
|
|
|
@@ -239,7 +239,8 @@ describe('DOM', function () {
|
|
|
239
239
|
document.getElementById('test1').appendChild(d);
|
|
240
240
|
expect(document.getElementsByTagName('monster-testclass').length).is.equal(1);
|
|
241
241
|
// no data-monster-objectlink="Symbol(monsterUpdater)" because it has nothing to update
|
|
242
|
-
|
|
242
|
+
// but data-monster-error="Error: html is not set."
|
|
243
|
+
expect(document.getElementById('test1')).contain.html('<monster-testclass data-monster-error="Error: html is not set."></monster-testclass>');
|
|
243
244
|
});
|
|
244
245
|
});
|
|
245
246
|
|
package/test/cases/monster.mjs
CHANGED