@schukai/monster 3.47.0 → 3.49.0
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +1 -1
- package/source/dom/constants.mjs +4 -4
- package/source/dom/customcontrol.mjs +63 -59
- package/source/dom/customelement.mjs +129 -132
- package/source/types/version.mjs +1 -1
- package/test/cases/dom/customcontrol.mjs +21 -45
- package/test/cases/dom/customelement-initfromscripthost.mjs +4 -4
- 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
@@ -59,9 +59,9 @@ export {
|
|
59
59
|
ATTRIBUTE_HIDDEN,
|
60
60
|
objectUpdaterLinkSymbol,
|
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
|
@@ -127,7 +127,7 @@ const ATTRIBUTE_OPTION_CALLBACK = `${ATTRIBUTE_PREFIX}option-callback`;
|
|
127
127
|
* @since 3.48.0
|
128
128
|
* @type {string}
|
129
129
|
*/
|
130
|
-
const
|
130
|
+
const initControlCallbackName = `initCustomControlCallback`;
|
131
131
|
|
132
132
|
/**
|
133
133
|
* @memberOf Monster.DOM
|
@@ -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,11 +23,11 @@ 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,
|
30
|
-
|
30
|
+
initControlCallbackName
|
31
31
|
} from "./constants.mjs";
|
32
32
|
import {findDocumentTemplate, Template} from "./template.mjs";
|
33
33
|
import {addObjectWithUpdaterToElement} from "./updater.mjs";
|
@@ -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
|
-
* `initCustomControlOptionsCallback` 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
|
|
@@ -102,13 +102,13 @@ describe('DOM', function () {
|
|
102
102
|
|
103
103
|
});
|
104
104
|
|
105
|
-
it('should found callback
|
105
|
+
it('should found callback initCustomControlCallback', function () {
|
106
106
|
|
107
107
|
let mocks = document.getElementById('mocks');
|
108
108
|
mocks.innerHTML = `<div id="call-back-host"></div><div id="container"></div>`;
|
109
109
|
|
110
110
|
const container = document.getElementById('call-back-host');
|
111
|
-
container.
|
111
|
+
container.initCustomControlCallback = function (control) {
|
112
112
|
control.setOption('test', 1);
|
113
113
|
}
|
114
114
|
|
@@ -120,14 +120,14 @@ describe('DOM', function () {
|
|
120
120
|
|
121
121
|
});
|
122
122
|
|
123
|
-
it('should found callback
|
123
|
+
it('should found callback initCustomControlCallback from self', function () {
|
124
124
|
|
125
125
|
let mocks = document.getElementById('mocks');
|
126
126
|
mocks.innerHTML = `<div id="call-back-host"></div><div id="container"></div>`;
|
127
127
|
|
128
128
|
let control = document.createElement(randomTagNumber);
|
129
129
|
expect(control.getOption('test')).is.eql(0);
|
130
|
-
control.
|
130
|
+
control.initCustomControlCallback = function (control) {
|
131
131
|
control.setOption('test', 2);
|
132
132
|
}
|
133
133
|
|
@@ -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