@twobirds/microcomponents 0.9.0 → 0.10.1
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 +2 -16
- package/dist/cli/bundle.d.ts +106 -0
- package/dist/cli/bundle.d.ts.map +1 -0
- package/dist/cli/bundler.d.ts +44 -0
- package/dist/cli/bundler.d.ts.map +1 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/dev.d.ts +87 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dispatch.d.ts +6 -2
- package/dist/cli/dispatch.d.ts.map +1 -1
- package/dist/cli/index.js +1807 -1
- package/dist/cli/inliner.d.ts +42 -0
- package/dist/cli/inliner.d.ts.map +1 -0
- package/dist/cli/preview.d.ts +48 -0
- package/dist/cli/preview.d.ts.map +1 -0
- package/dist/core/elements.js +1294 -75
- package/dist/core/mcGuards.js +5 -8
- package/dist/mcp/api.json +2 -17
- package/dist/mcp/concepts/project-layout.md +9 -8
- package/dist/mcp/index.js +450 -230
- package/dist/microcomponents.js +2882 -9
- package/dist/state/formValues.js +2236 -281
- package/dist/state/observables.js +1794 -342
- package/dist/utils/events.js +171 -197
- package/package.json +7 -8
- package/dist/cli/create.js +0 -653
- package/dist/cli/dispatch.js +0 -316
- package/dist/cli/scaffold.js +0 -109
- package/dist/cli/writeScaffold.js +0 -114
- package/dist/core/MC.js +0 -837
- package/dist/core/index.js +0 -72
- package/dist/state/index.js +0 -5
- package/dist/state/placeholders.js +0 -187
- package/dist/types/index.js +0 -1
- package/dist/types/mcTypes.js +0 -9
- package/dist/utils/autoload.js +0 -160
- package/dist/utils/helpers.js +0 -532
- package/dist/utils/index.js +0 -12
- package/dist/utils/resolveComponentPath.js +0 -39
- package/dist/vite/index.d.ts +0 -7
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/index.js +0 -6
- package/dist/vite/loadAssets.d.ts +0 -36
- package/dist/vite/loadAssets.d.ts.map +0 -1
- package/dist/vite/loadAssets.js +0 -221
package/dist/core/index.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
# Overview
|
|
3
|
-
|
|
4
|
-
MicroComponents (MC) is a lightweight pattern for creating self-contained, event-driven, and composable UI components. It provides two core base classes: {@link MC} and {@link DC}, which differ primarily in their target attachment strategy and runtime environment requirements.
|
|
5
|
-
|
|
6
|
-
In general, your code extends from either of these classes depending on whether you need DOM interaction or pure logic encapsulation.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## MC vs DC: Server vs Client
|
|
11
|
-
|
|
12
|
-
The key distinction between the two base classes is their execution context:
|
|
13
|
-
|
|
14
|
-
- **MC (MicroComponent)** – A general-purpose component that works in **any JavaScript environment**, including server-side runtimes (Node.js, Deno, etc.). It does not require a DOM and can be used for business logic, state management, or as a lightweight container.
|
|
15
|
-
|
|
16
|
-
- **DC (DOM Component)** – A DOM-centric component that operates wherever a real ( browser ) or simulated ( jsdom, etc. ) DOM exists. It attaches itself to an HTMLElement and provides event handling, DOM traversal, and lifecycle management. The `target` parameter is required and must be an HTMLElement.
|
|
17
|
-
|
|
18
|
-
**HINT:** DCs can be attached to **any** DOM node ( HTMLElement ). That is you can connect them to <div>, <span> and the like. With a trick you can even define custom elements that convert themselves to e.g. a plain <div>.
|
|
19
|
-
|
|
20
|
-
### When to use which?
|
|
21
|
-
|
|
22
|
-
| Scenario | Recommended Class |
|
|
23
|
-
|----------|-------------------|
|
|
24
|
-
| Server-side rendering | DC |
|
|
25
|
-
| Interactive UI elements in the browser | DC |
|
|
26
|
-
| Event-driven components attached to HTML nodes | DC |
|
|
27
|
-
| Pure logic without DOM | MC |
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Backreference Concept
|
|
32
|
-
|
|
33
|
-
Both MC and DC store a reference to their **host object** via the internal `_target` property (exposed as the `target` getter). This is a *backreference* — it points back to the object that contains or hosts this component instance.
|
|
34
|
-
|
|
35
|
-
- For **DC instances**, `this.target` refers to the HTMLElement in the DOM (or testing DOM such as jsdom) to which the component is attached.
|
|
36
|
-
- For **MC instances**, `this.target` can be any arbitrary object, allowing flexible composition patterns.
|
|
37
|
-
|
|
38
|
-
The reference is held via a `WeakRef` ([MDN WeakRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef)) internally to avoid circular references and prevent memory leaks.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
# DC Class Specific
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## DOM Integration
|
|
45
|
-
|
|
46
|
-
When a DC is attached to an element via {@linkcode DC.add}, two things happen:
|
|
47
|
-
|
|
48
|
-
1. The element gains a `_mc` property (of type {@link McHost}) that holds references to all attached microcomponents, keyed by their unique identifiers.
|
|
49
|
-
2. The element receives a `data-mc` attribute, signaling that it hosts at least one microcomponent. This attribute is used internally for DOM queries and selectors.
|
|
50
|
-
|
|
51
|
-
This `_mc` property is the **only** interference with the native DOM structure, ensuring compatibility with other frameworks or libraries.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Lifecycle
|
|
56
|
-
|
|
57
|
-
- DC instances automatically trigger an `"Init"` lifecycle event asynchronously - using queueMicrotask() - after construction. A component with a 'oneInit()' method will catch that lifecycle event, and when it runs it is guaranteed that the constructor() has run.
|
|
58
|
-
- Methods prefixed with `on` or `one` (e.g., `onClick`, `oneUpdate`) are auto-attached as event listeners on the target element, supporting both native HTML events and custom microcomponent events.
|
|
59
|
-
- When using the static `assets` property to on-demand load shadow/target templates and css
|
|
60
|
-
- those are automatically attached to their respective target locations
|
|
61
|
-
- and all native event handlers in template code (<button click="...">) are automatically attached also
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
@module Core
|
|
65
|
-
@showGroups
|
|
66
|
-
*/
|
|
67
|
-
/** @group MicroComponents */
|
|
68
|
-
export { MC, DC, McBase } from './MC.js';
|
|
69
|
-
/** @group Elements */
|
|
70
|
-
export { defineCE, getCE, listCE } from './elements.js';
|
|
71
|
-
/** @group Helpers */
|
|
72
|
-
export { isMcHost } from './mcGuards.js';
|
package/dist/state/index.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@hidden
|
|
3
|
-
DOM placeholder binding: `{name}` templates in attributes and text nodes,
|
|
4
|
-
kept up to date from observable data.
|
|
5
|
-
|
|
6
|
-
Split from observables.ts so the pure observable machinery carries no DOM
|
|
7
|
-
scanning/decoration code. The two modules reference each other only inside
|
|
8
|
-
function bodies (`Placeholders` calls `observe(this)`; `bind()` calls
|
|
9
|
-
{@link makePlaceholders}), never during module evaluation, so the circular
|
|
10
|
-
import is benign under ESM live bindings.
|
|
11
|
-
*/
|
|
12
|
-
import { DC } from '../core/MC.js';
|
|
13
|
-
import { observe } from './observables.js';
|
|
14
|
-
/**
|
|
15
|
-
Extracts placeholder names from an HTML element's attributes and text content.
|
|
16
|
-
|
|
17
|
-
Placeholders are strings enclosed in curly braces `{}`. This function scans
|
|
18
|
-
all attributes and direct child text nodes of the target element for placeholders
|
|
19
|
-
and returns them as an array of unique names (without braces).
|
|
20
|
-
|
|
21
|
-
@param target - The HTMLElement to scan for placeholders.
|
|
22
|
-
@returns An array of placeholder names found within the element's attributes and text content.
|
|
23
|
-
*/
|
|
24
|
-
function getPlaceholders(target) {
|
|
25
|
-
const placeholders = new Set(), regEx = /\{[^\{\}]*\}/g;
|
|
26
|
-
// scan attributes
|
|
27
|
-
(target.getAttributeNames ? target.getAttributeNames() : [])
|
|
28
|
-
.filter((attr) => target.getAttribute(attr)?.match(regEx)) // extract placeholder(s)
|
|
29
|
-
.forEach((attr) => {
|
|
30
|
-
const attrPlaceholders = new Set(target.getAttribute(attr)?.match(regEx));
|
|
31
|
-
attrPlaceholders.forEach((attrPlaceholder) => placeholders.add(attrPlaceholder.replace(/[\{\}]/g, '')));
|
|
32
|
-
});
|
|
33
|
-
// scan children text nodes
|
|
34
|
-
[...target.childNodes]
|
|
35
|
-
.filter((childNode) => {
|
|
36
|
-
return (
|
|
37
|
-
// is text node and has placeholders
|
|
38
|
-
childNode.nodeType === 3 && (childNode.nodeValue || '').match(regEx));
|
|
39
|
-
})
|
|
40
|
-
.forEach((childNode) => {
|
|
41
|
-
/* v8 ignore next -- the filter above matched nodeValue against the placeholder
|
|
42
|
-
pattern, so it is non-null and non-empty by the time we get here */
|
|
43
|
-
const textContent = childNode?.nodeValue || '';
|
|
44
|
-
const contentPlaceholders = textContent.match(regEx);
|
|
45
|
-
contentPlaceholders.forEach((textNodePlaceholder) => placeholders.add(textNodePlaceholder.replace(/[\{\}]/g, '')));
|
|
46
|
-
});
|
|
47
|
-
return [...placeholders];
|
|
48
|
-
}
|
|
49
|
-
// Tag names instead of constructor references (HTMLInputElement etc.):
|
|
50
|
-
// evaluating those globals at module scope crashes any DOM-less import
|
|
51
|
-
// of the package (e.g. server-side use of MC).
|
|
52
|
-
const inputTags = ['INPUT', 'SELECT', 'TEXTAREA'];
|
|
53
|
-
/**
|
|
54
|
-
A class that manages placeholders within an HTML element and updates them dynamically.
|
|
55
|
-
|
|
56
|
-
This class extends `DC` (Data Controller) and processes the target element to identify
|
|
57
|
-
placeholder patterns in attributes and text nodes. It then observes changes on its internal
|
|
58
|
-
data object and updates the corresponding DOM locations when notified of changes.
|
|
59
|
-
*/
|
|
60
|
-
class Placeholders extends DC {
|
|
61
|
-
// observe(this) in the constructor swaps this for the live observable wrapper.
|
|
62
|
-
data = {};
|
|
63
|
-
oldDisplay = '';
|
|
64
|
-
/**
|
|
65
|
-
Creates a new `Placeholders` instance for the given HTML element.
|
|
66
|
-
|
|
67
|
-
Initializes the internal data with empty strings for each placeholder
|
|
68
|
-
found in the target and starts observing the data object. Attaches
|
|
69
|
-
callbacks to update placeholders as needed.
|
|
70
|
-
|
|
71
|
-
@param target - The HTMLElement to process for placeholders.
|
|
72
|
-
*/
|
|
73
|
-
constructor(target) {
|
|
74
|
-
super(target);
|
|
75
|
-
getPlaceholders(target).forEach((property) => (this.data[property] = ''));
|
|
76
|
-
observe(this);
|
|
77
|
-
this.#attachCallbacks();
|
|
78
|
-
}
|
|
79
|
-
#attachCallbacks() {
|
|
80
|
-
const target = this.target;
|
|
81
|
-
let hide = false;
|
|
82
|
-
// scan attributes
|
|
83
|
-
/* v8 ignore next -- Placeholders is only ever constructed with an HTMLElement
|
|
84
|
-
(DC's constructor reads target.tagName), so the fragment/shadow-root guard
|
|
85
|
-
that getPlaceholders needs is dead here. */
|
|
86
|
-
const attrNames = target.getAttributeNames ? target.getAttributeNames() : [];
|
|
87
|
-
attrNames
|
|
88
|
-
.filter((attr) => target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)) // extract placeholder(s)
|
|
89
|
-
.forEach((attr) => {
|
|
90
|
-
const placeholders = new Set(target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g));
|
|
91
|
-
/* v8 ignore next -- attr passed the filter above, so its value is a non-null string */
|
|
92
|
-
const attrTemplate = target.getAttribute(attr) ?? '';
|
|
93
|
-
// attach callback
|
|
94
|
-
this.data.subscribe(((template) => (data) => {
|
|
95
|
-
let result = template;
|
|
96
|
-
placeholders.forEach((placeholder) => {
|
|
97
|
-
result = result.replace(placeholder,
|
|
98
|
-
// JSON.stringify(data[placeholder.replace(/[\{\}]/g, '')])
|
|
99
|
-
String(data[placeholder.replace(/[\{\}]/g, '')]));
|
|
100
|
-
});
|
|
101
|
-
target.setAttribute(attr, result);
|
|
102
|
-
})(attrTemplate));
|
|
103
|
-
});
|
|
104
|
-
// scan children text nodes
|
|
105
|
-
[...target.childNodes]
|
|
106
|
-
.filter((childNode) => {
|
|
107
|
-
return (
|
|
108
|
-
// is text node and has placeholders
|
|
109
|
-
childNode.nodeType === 3 && (childNode.nodeValue || '').match(/\{[^\{\}]*\}/g));
|
|
110
|
-
})
|
|
111
|
-
.forEach((childNode) => {
|
|
112
|
-
hide = true;
|
|
113
|
-
/* v8 ignore next -- the filter above only admits text nodes with a non-empty
|
|
114
|
-
nodeValue, so the ?? fallback is unreachable */
|
|
115
|
-
const textTemplate = childNode.nodeValue ?? '';
|
|
116
|
-
const placeholders = new Set(textTemplate.match(/\{[^\{\}]*\}/g));
|
|
117
|
-
// attach callback
|
|
118
|
-
this.data.subscribe(((template) => (data) => {
|
|
119
|
-
let result = template;
|
|
120
|
-
placeholders.forEach((placeholder) => {
|
|
121
|
-
result = result.replace(placeholder,
|
|
122
|
-
// JSON.stringify(data[placeholder.replace(/[\{\}]/g, '')])
|
|
123
|
-
String(data[placeholder.replace(/[\{\}]/g, '')]));
|
|
124
|
-
});
|
|
125
|
-
if (this.oldDisplay) {
|
|
126
|
-
target.style = this.oldDisplay;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
target.removeAttribute('style');
|
|
130
|
-
}
|
|
131
|
-
delete this.oldDisplay;
|
|
132
|
-
childNode.nodeValue = result;
|
|
133
|
-
})(textTemplate));
|
|
134
|
-
// hide for now
|
|
135
|
-
this.oldDisplay = target.getAttribute('style') || ''; // TBD allow null to indicate there was no old style attribute
|
|
136
|
-
if (hide && !inputTags.includes(target.tagName) && target.tagName !== 'FIELDSET') {
|
|
137
|
-
target.style.display = 'none';
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
Handles a `data` custom event to update the internal placeholder data.
|
|
143
|
-
|
|
144
|
-
Extracts matching keys from the event's detail data and updates only those
|
|
145
|
-
properties in the internal `data` object that were previously defined as placeholders.
|
|
146
|
-
|
|
147
|
-
@param ev - The CustomEvent containing new data to merge into the placeholders.
|
|
148
|
-
*/
|
|
149
|
-
onData(ev) {
|
|
150
|
-
const data = {};
|
|
151
|
-
Object.keys(ev.detail.data).forEach((key) => {
|
|
152
|
-
if (this.data.hasOwnProperty(key))
|
|
153
|
-
data[key] = ev.detail.data[key];
|
|
154
|
-
});
|
|
155
|
-
if (JSON.stringify(data) !== JSON.stringify(this.data))
|
|
156
|
-
Object.assign(this.data, data); // will trigger callbacks
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
Finds all elements within a given scope that contain placeholders.
|
|
161
|
-
|
|
162
|
-
@param target - The HTMLElement, DocumentFragment, or ShadowRoot to search.
|
|
163
|
-
@returns An array of HTMLElements containing placeholder patterns.
|
|
164
|
-
*/
|
|
165
|
-
function getPlaceholderElements(target) {
|
|
166
|
-
const elements = [target]
|
|
167
|
-
.concat([...target.querySelectorAll('*')])
|
|
168
|
-
.filter((e) => !!getPlaceholders(e).length)
|
|
169
|
-
.filter((e) => e?.tagName !== 'STYLE')
|
|
170
|
-
.reverse(); // to start with inner elements
|
|
171
|
-
return elements;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
Applies placeholder support to an element and its descendants.
|
|
175
|
-
|
|
176
|
-
This function scans the provided target for elements containing placeholders
|
|
177
|
-
and attaches a `Placeholders` controller instance to each one if not already present.
|
|
178
|
-
|
|
179
|
-
@param element - The HTMLElement, DocumentFragment, or ShadowRoot to process.
|
|
180
|
-
*/
|
|
181
|
-
export function makePlaceholders(element) {
|
|
182
|
-
getPlaceholderElements(element).forEach((e) => {
|
|
183
|
-
if (e?._mc?._placeholders)
|
|
184
|
-
return;
|
|
185
|
-
DC.add(e, '_placeholders', new Placeholders(e));
|
|
186
|
-
});
|
|
187
|
-
}
|
package/dist/types/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/mcTypes.js
DELETED
package/dist/utils/autoload.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module Autoload
|
|
3
|
-
|
|
4
|
-
The {@linkcode autoload} function enables automatic detection and instantiation of microcomponents in the DOM via a `MutationObserver`. When enabled, newly added elements with undefined custom tags or `data-mc` references will trigger dynamic script loading and component attachment.
|
|
5
|
-
|
|
6
|
-
Kept in its own module so the core controller classes in MC.ts stay free of
|
|
7
|
-
side-effect-bearing DOM machinery (`MutationObserver`, script injection):
|
|
8
|
-
importing `DC`/`MC` never pulls a DOM observer into the bundle unless
|
|
9
|
-
{@link autoload} itself is imported.
|
|
10
|
-
*/
|
|
11
|
-
import { DC } from '../core/MC.js';
|
|
12
|
-
import { isMcHost } from '../core/mcGuards.js';
|
|
13
|
-
import { resolveComponentPath } from './resolveComponentPath.js';
|
|
14
|
-
const loadingDCs = new Set();
|
|
15
|
-
/**
|
|
16
|
-
Creates a closure that attaches DOM elements with the specified tag name to
|
|
17
|
-
their corresponding microcomponent instances.
|
|
18
|
-
|
|
19
|
-
Used internally by `makeLoadScript()` when dynamically loading external
|
|
20
|
-
custom element definitions. The returned function is invoked after the
|
|
21
|
-
element definition becomes available, and performs:
|
|
22
|
-
- Querying of all matching elements in the DOM
|
|
23
|
-
- Instantiation of new `DC` instances
|
|
24
|
-
- Attachment to each DOM node via `DC.add()`
|
|
25
|
-
|
|
26
|
-
@param element - The tag name (custom element name) of the microcomponent to instantiate.
|
|
27
|
-
@returns A closure that performs the attachment logic when invoked.
|
|
28
|
-
@group Autoload
|
|
29
|
-
*/
|
|
30
|
-
function attachDC(element) {
|
|
31
|
-
return () => {
|
|
32
|
-
const elements = Array.from(document.querySelectorAll(`[data-mc*="${element}"]`)).filter((e) => e instanceof HTMLElement);
|
|
33
|
-
if (elements.length === 0)
|
|
34
|
-
return;
|
|
35
|
-
const customElementClass = customElements.get(element);
|
|
36
|
-
for (let node of elements) {
|
|
37
|
-
try {
|
|
38
|
-
DC.add(node, customElementClass.name, new customElementClass(node));
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
/* v8 ignore start */
|
|
42
|
-
if ('create' in customElementClass && typeof customElementClass.create === 'function') {
|
|
43
|
-
const customElement = customElementClass.create(node);
|
|
44
|
-
if (!isMcHost(customElement))
|
|
45
|
-
throw `create() of Class ${customElementClass.name} did not return a McHost.`;
|
|
46
|
-
customElement.setAttribute('data-mc', node.getAttribute('data-mc') ?? '');
|
|
47
|
-
node.replaceWith(customElement);
|
|
48
|
-
node = customElement;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
throw `Class ${customElementClass.name} doesn't have a callable constructor AND class doesn't have a static .create() method.`;
|
|
52
|
-
}
|
|
53
|
-
/* v8 ignore end */
|
|
54
|
-
}
|
|
55
|
-
const newMcName = node
|
|
56
|
-
.getAttribute('data-mc')
|
|
57
|
-
.split(' ')
|
|
58
|
-
.filter((name) => name !== element && name !== '')
|
|
59
|
-
.join(' ')
|
|
60
|
-
.trim();
|
|
61
|
-
node.setAttribute('data-mc', newMcName);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
Dynamically loads and registers a custom element definition from an external JS file.
|
|
67
|
-
|
|
68
|
-
Constructs the script URL based on kebab-case naming (e.g., `my-element` → `./my/element.js`).
|
|
69
|
-
Registers a handler to be executed once the element is defined, then appends the
|
|
70
|
-
`<script>` tag to `<head>`. Handles both successful loading and error cases.
|
|
71
|
-
|
|
72
|
-
@param element - The custom element tag name (e.g., 'mc-button', 'my-widget').
|
|
73
|
-
*/
|
|
74
|
-
function makeLoadScript(element) {
|
|
75
|
-
/* v8 ignore next */
|
|
76
|
-
if (loadingDCs.has(element))
|
|
77
|
-
return; // already loading
|
|
78
|
-
loadingDCs.add(element);
|
|
79
|
-
const fileName = resolveComponentPath(element);
|
|
80
|
-
const scriptElement = document.createElement('script');
|
|
81
|
-
scriptElement.setAttribute('src', fileName);
|
|
82
|
-
scriptElement.setAttribute('blocking', 'render');
|
|
83
|
-
scriptElement.async = true;
|
|
84
|
-
scriptElement.setAttribute('type', 'module');
|
|
85
|
-
scriptElement.setAttribute('data-name', element);
|
|
86
|
-
scriptElement.setAttribute('loading', '');
|
|
87
|
-
const loadedPromise = customElements.whenDefined(element);
|
|
88
|
-
loadedPromise.then(attachDC(element));
|
|
89
|
-
scriptElement.addEventListener('load', (ev) => {
|
|
90
|
-
loadingDCs.delete(element);
|
|
91
|
-
document.head.querySelector(`script[data-name="${element}"]`)?.remove();
|
|
92
|
-
});
|
|
93
|
-
scriptElement.addEventListener('error', (ev) => {
|
|
94
|
-
console.error('could not load DC code from', fileName);
|
|
95
|
-
});
|
|
96
|
-
document.head.append(scriptElement);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
Scans the DOM for undefined custom elements and pending microcomponents,
|
|
100
|
-
then loads their definitions if necessary.
|
|
101
|
-
|
|
102
|
-
This is the main driver function for the autoload mechanism. It performs:
|
|
103
|
-
1. Finds all undefined custom elements in the document
|
|
104
|
-
2. Checks whether they are already loading or defined
|
|
105
|
-
3. Calls `makeLoadScript()` to fetch missing definitions
|
|
106
|
-
4. Invokes `attachDC()` where element definitions exist but haven't been attached yet
|
|
107
|
-
|
|
108
|
-
It is called both immediately after enabling autoload and on each DOM mutation.
|
|
109
|
-
*/
|
|
110
|
-
function loadUndefinedMCs() {
|
|
111
|
-
// load unresolved custom elements
|
|
112
|
-
[...document.querySelectorAll(':not(:defined)')]
|
|
113
|
-
.filter((element) => !!element &&
|
|
114
|
-
!customElements.get(element.tagName.toLowerCase()) && // not in CustomElementsRegistry
|
|
115
|
-
!loadingDCs.has(element.tagName.toLowerCase()) // not in loading list
|
|
116
|
-
)
|
|
117
|
-
.forEach((el) => {
|
|
118
|
-
makeLoadScript(el.tagName.toLowerCase()); // will automatically be instantiated
|
|
119
|
-
});
|
|
120
|
-
// if defined in _mc attribute: instantiate DCs, load if necessary
|
|
121
|
-
[...document.querySelectorAll('[data-mc]:not([data-mc=""]')]
|
|
122
|
-
.filter((element) => !!element)
|
|
123
|
-
.forEach((el) => {
|
|
124
|
-
const _mc = el
|
|
125
|
-
.getAttribute('data-mc')
|
|
126
|
-
?.split(' ')
|
|
127
|
-
.filter((name) => name !== '');
|
|
128
|
-
_mc?.forEach((element) => {
|
|
129
|
-
const isDefined = customElements.get(element);
|
|
130
|
-
if (!loadingDCs.has(element) && !isDefined) {
|
|
131
|
-
makeLoadScript(element);
|
|
132
|
-
}
|
|
133
|
-
else if (isDefined) {
|
|
134
|
-
attachDC(element)();
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
let observer = null;
|
|
140
|
-
export function autoload(enable = true) {
|
|
141
|
-
if (!enable) {
|
|
142
|
-
if (observer) {
|
|
143
|
-
observer.disconnect();
|
|
144
|
-
observer = null;
|
|
145
|
-
}
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
const wasEnabled = !!observer;
|
|
149
|
-
if (wasEnabled)
|
|
150
|
-
return true;
|
|
151
|
-
// Initialize observer only once
|
|
152
|
-
observer = new MutationObserver(loadUndefinedMCs);
|
|
153
|
-
setTimeout(() => {
|
|
154
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
155
|
-
loadUndefinedMCs();
|
|
156
|
-
}, 5);
|
|
157
|
-
window.autoload ??= autoload;
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
autoload.state = loadingDCs;
|