@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/utils/helpers.js
DELETED
|
@@ -1,532 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module Helpers
|
|
3
|
-
|
|
4
|
-
Small runtime-agnostic utilities shared across the library: environment
|
|
5
|
-
detection, object flattening/comparison, debouncing, accessor copying, and
|
|
6
|
-
kebab-to-PascalCase conversion.
|
|
7
|
-
*/
|
|
8
|
-
import { on, trigger } from './events.js';
|
|
9
|
-
/**
|
|
10
|
-
* Determines whether the current runtime environment is a web browser.
|
|
11
|
-
*
|
|
12
|
-
* This function works by attempting to access `this` within an anonymous function
|
|
13
|
-
* and checking if it refers to the global `window` object. It returns `true`
|
|
14
|
-
* when executed in a browser context, and `false` otherwise (e.g., Node.js).
|
|
15
|
-
*
|
|
16
|
-
* Note: While modern environments typically provide more reliable detection mechanisms,
|
|
17
|
-
* this fallback remains compatible across older runtimes where other detection techniques
|
|
18
|
-
* may be unreliable.
|
|
19
|
-
*
|
|
20
|
-
* @returns `true` if the code is running in a browser environment, `false` otherwise.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* if (isBrowser()) {
|
|
25
|
-
* console.log('Running in a browser');
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export function isBrowser() {
|
|
30
|
-
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Checks if a value is a native JavaScript object (created via {} or new Object()).
|
|
34
|
-
* @param value - The value to check.
|
|
35
|
-
* @returns True if the value is a plain object, false otherwise.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* console.log(isNativeObject({}));
|
|
40
|
-
* console.log(isNativeObject(new Object()));
|
|
41
|
-
* console.log(isNativeObject([]));
|
|
42
|
-
* console.log(isNativeObject(null));
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
export function isNativeObject(value) {
|
|
46
|
-
return typeof value === 'object' && value !== null && value.constructor === Object;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Recursively sorts all keys in an object alphabetically, preserving nested structure.
|
|
50
|
-
* @param source - The object to sort.
|
|
51
|
-
* @returns A new object with sorted keys at all levels.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```ts
|
|
55
|
-
* const obj = { z: 1, a: { y: 2, b: 3 } };
|
|
56
|
-
* console.log(JSON.stringify(sortObject(obj)));
|
|
57
|
-
* ```
|
|
58
|
-
*/
|
|
59
|
-
export function sortObject(source) {
|
|
60
|
-
const result = {}, keys = Object.keys(source).sort();
|
|
61
|
-
// Indexed for: no per-key callback/iterator overhead. ~15x faster than .forEach,
|
|
62
|
-
// ~10x faster than for-of over the loop body itself (V8, 5M elems), and ~15x faster than .forEach()
|
|
63
|
-
for (let i = 0; i < keys.length; i++) {
|
|
64
|
-
const key = keys[i], value = source[key];
|
|
65
|
-
if (isNativeObject(value)) {
|
|
66
|
-
result[key] = sortObject(value);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
result[key] = value;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Flattens a nested object into a single-level object with dot-separated keys.
|
|
76
|
-
* Optionally sorts the resulting flat keys alphabetically (default: true).
|
|
77
|
-
* @param source - The object to flatten.
|
|
78
|
-
* @param doSort - Whether to sort the resulting keys (default: true).
|
|
79
|
-
* @returns A flattened object with dot-notation keys.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* const obj = { user: { name: 'Alice', age: 30 } };
|
|
84
|
-
* console.log(JSON.stringify(flattenObject(obj)));
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export function flattenObject(source, doSort = true) {
|
|
88
|
-
const result = {};
|
|
89
|
-
const record = source;
|
|
90
|
-
const names = Object.getOwnPropertyNames(source);
|
|
91
|
-
// Indexed for: no per-key callback overhead, and no extra copy from spreading
|
|
92
|
-
// ~10x faster than for-of over the loop body itself (V8, 5M elems), and ~15x faster than .forEach()
|
|
93
|
-
for (let i = 0; i < names.length; i++) {
|
|
94
|
-
const propName = names[i], value = record[propName];
|
|
95
|
-
if (value instanceof Object) {
|
|
96
|
-
const temp = flattenObject(value, false);
|
|
97
|
-
for (const j in temp) {
|
|
98
|
-
result[propName + '.' + j] = temp[j];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
result[propName] = value;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return doSort ? sortObject(result) : result;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Structural deep-equality using SameValueZero for primitives.
|
|
109
|
-
*
|
|
110
|
-
* See {@link https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero}.
|
|
111
|
-
*
|
|
112
|
-
* Note: `Map`/`Set` are not structurally compared.
|
|
113
|
-
*
|
|
114
|
-
* @param a - The first value to compare.
|
|
115
|
-
* @param b - The second value to compare.
|
|
116
|
-
* @returns True if the values are deeply equal, false otherwise.
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* ```ts
|
|
120
|
-
* console.log(deepEqual({ a: 1 }, { a: 1 }));
|
|
121
|
-
* console.log(deepEqual({ a: { b: 2 } }, { a: {} }));
|
|
122
|
-
* console.log(deepEqual({ a: NaN }, { a: NaN }));
|
|
123
|
-
* console.log(deepEqual({ a: NaN }, { a: null }));
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
export function deepEqual(a, b) {
|
|
127
|
-
// SameValueZero: identical primitives/refs, plus NaN === NaN.
|
|
128
|
-
if (a === b || (a !== a && b !== b))
|
|
129
|
-
return true;
|
|
130
|
-
/* v8 ignore start */
|
|
131
|
-
// Beyond SameValueZero, equality needs two non-null objects of the same kind.
|
|
132
|
-
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null)
|
|
133
|
-
return false;
|
|
134
|
-
if (a.constructor !== b.constructor)
|
|
135
|
-
return false;
|
|
136
|
-
if (a instanceof Date)
|
|
137
|
-
return a.getTime() === b.getTime();
|
|
138
|
-
if (a instanceof RegExp)
|
|
139
|
-
return a.toString() === b.toString();
|
|
140
|
-
/* v8 ignore end */
|
|
141
|
-
if (Array.isArray(a)) {
|
|
142
|
-
const bArr = b;
|
|
143
|
-
/* v8 ignore next */
|
|
144
|
-
if (a.length !== bArr.length)
|
|
145
|
-
return false;
|
|
146
|
-
for (let i = 0; i < a.length; i++) {
|
|
147
|
-
if (!deepEqual(a[i], bArr[i]))
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
const aObj = a, bObj = b, keys = Object.keys(aObj);
|
|
153
|
-
if (keys.length !== Object.keys(bObj).length)
|
|
154
|
-
return false;
|
|
155
|
-
for (let i = 0; i < keys.length; i++) {
|
|
156
|
-
const key = keys[i];
|
|
157
|
-
if (!Object.prototype.hasOwnProperty.call(bObj, key) || !deepEqual(aObj[key], bObj[key]))
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Retrieves a nested value from an object using a namespace path (dot-separated).
|
|
164
|
-
* @param ns - The namespace string or array of keys representing the path.
|
|
165
|
-
* @param obj - The object to traverse.
|
|
166
|
-
* @returns The value at the specified namespace path, or undefined if not found.
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* const data = { user: { profile: { name: 'Alice' } } };
|
|
171
|
-
* console.log(nameSpace('user.profile.name', data));
|
|
172
|
-
* console.log(nameSpace(['user', 'profile'], data));
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
export function nameSpace(ns, obj = {}) {
|
|
176
|
-
const source = obj;
|
|
177
|
-
const nsa = typeof ns === 'string' ? ns.split('.') : ns;
|
|
178
|
-
if (!source || !nsa[0] || !source[nsa[0]]) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
if (nsa.length > 1) {
|
|
182
|
-
const next = source[nsa[0]];
|
|
183
|
-
if (isNativeObject(next)) {
|
|
184
|
-
return nameSpace(nsa.slice(1), next);
|
|
185
|
-
}
|
|
186
|
-
return undefined;
|
|
187
|
-
}
|
|
188
|
-
return source[nsa[0]];
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Parses a template string or object, replacing placeholders in the form of {key.path} with
|
|
192
|
-
* corresponding values from the provided data object using namespace resolution.
|
|
193
|
-
* Also recursively processes objects and arrays within the input.
|
|
194
|
-
* @param template - The string or object to parse (can contain {{placeholder}} expressions).
|
|
195
|
-
* @param parseThis - The object containing values for placeholder substitution.
|
|
196
|
-
* @returns The parsed result, with placeholders replaced by actual values.
|
|
197
|
-
*
|
|
198
|
-
* @example
|
|
199
|
-
* ```ts
|
|
200
|
-
* const data = { user: { name: 'Alice' } };
|
|
201
|
-
* console.log(parse('Hello {user.name}!', data));
|
|
202
|
-
* console.log(parse(['Hi {user.name}', 'Bye {user.name}'], data));
|
|
203
|
-
* ```
|
|
204
|
-
*/
|
|
205
|
-
export function parse(template, parseThis, ...rest) {
|
|
206
|
-
const args = [template, parseThis, ...rest];
|
|
207
|
-
if (args.length > 2) {
|
|
208
|
-
while (args.length > 1) {
|
|
209
|
-
args[0] = parse(args[0], args[1]);
|
|
210
|
-
args.splice(1, 1);
|
|
211
|
-
}
|
|
212
|
-
return args[0];
|
|
213
|
-
}
|
|
214
|
-
if (typeof template === 'string') {
|
|
215
|
-
let result = template;
|
|
216
|
-
template.match(/\{[^\{\}]*\}/g)?.forEach((pPropname) => {
|
|
217
|
-
const propname = pPropname.substring(1, pPropname.length - 1), value = nameSpace(propname, parseThis);
|
|
218
|
-
if (typeof value !== 'undefined') {
|
|
219
|
-
result = result.replace(pPropname, String(value));
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
else if (isNativeObject(template)) {
|
|
225
|
-
const record = template;
|
|
226
|
-
Object.keys(record).forEach((pKey) => {
|
|
227
|
-
/* v8 ignore next */
|
|
228
|
-
if (Object.prototype.hasOwnProperty.call(record, pKey)) {
|
|
229
|
-
record[pKey] = parse(record[pKey], parseThis);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
return template;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Converts a kebab-case string to PascalCase.
|
|
237
|
-
* Example: 'my-component-name' -> 'MyComponentName'
|
|
238
|
-
* @param str - The kebab-case string to convert.
|
|
239
|
-
* @returns The converted PascalCase string.
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ```ts
|
|
243
|
-
* console.log(kebabToPascal('my-element'));
|
|
244
|
-
* ```
|
|
245
|
-
*/
|
|
246
|
-
export function kebabToPascal(str) {
|
|
247
|
-
return str
|
|
248
|
-
.split('-') // Split by hyphens
|
|
249
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize each part
|
|
250
|
-
.join(''); // Join without spaces
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Creates a debounced version of a function that delays execution until after the specified
|
|
254
|
-
* number of milliseconds have elapsed since the last time this function was invoked.
|
|
255
|
-
* Useful for rate-limiting expensive operations like event handlers or API calls.
|
|
256
|
-
* @param func - The function to debounce.
|
|
257
|
-
* @param milliseconds - The number of milliseconds to delay execution.
|
|
258
|
-
* @returns A debounced version of the input function.
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```ts
|
|
262
|
-
* const log = debounce(() => console.log('called'), 300);
|
|
263
|
-
* log();
|
|
264
|
-
* log();
|
|
265
|
-
* ```
|
|
266
|
-
*/
|
|
267
|
-
export function debounce(func, milliseconds) {
|
|
268
|
-
let timeout;
|
|
269
|
-
return (...args) => {
|
|
270
|
-
clearTimeout(timeout);
|
|
271
|
-
timeout = setTimeout(() => {
|
|
272
|
-
func(...args);
|
|
273
|
-
}, milliseconds);
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Parses an HTML string and returns an array of DOM elements.
|
|
278
|
-
* Handles self-closing tags (XHTML style) by converting them to full closing tags first.
|
|
279
|
-
* @hidden
|
|
280
|
-
* @param html - The HTML string to parse.
|
|
281
|
-
* @returns An array of HTMLElement objects representing the parsed HTML nodes.
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* ```ts
|
|
285
|
-
* const elems = htmlToElements('<div>Hello</div><span>World</span>');
|
|
286
|
-
* console.log(elems.map((el) => el.tagName));
|
|
287
|
-
* ```
|
|
288
|
-
*/
|
|
289
|
-
export function htmlToElements(html) {
|
|
290
|
-
const template = document.createElement('template');
|
|
291
|
-
html = html.replace(/<([A-Za-z0-9\-]*)([^>\/]*)(\/>)/gi, '<$1$2></$1>'); // replace XHTML closing tags by full closing tags
|
|
292
|
-
template.innerHTML = html;
|
|
293
|
-
template.content.normalize();
|
|
294
|
-
return Array.from(template.content.childNodes);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Copies all getter and setter properties from a source object to a target object.
|
|
298
|
-
* Does not copy methods or regular data properties—only accessor properties (get/set).
|
|
299
|
-
* @hidden
|
|
300
|
-
* @param source - The source object with getters and/or setters to copy.
|
|
301
|
-
* @param target - The target object to which the accessors will be copied.
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```ts
|
|
305
|
-
* const src = { get name() { return 'Alice'; } };
|
|
306
|
-
* const tgt = {};
|
|
307
|
-
* copyGettersSetters(src, tgt);
|
|
308
|
-
* console.log(tgt.name);
|
|
309
|
-
* ```
|
|
310
|
-
*/
|
|
311
|
-
export function copyGettersSetters(source, target) {
|
|
312
|
-
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
313
|
-
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
314
|
-
if ('get' in descriptor || 'set' in descriptor) {
|
|
315
|
-
Object.defineProperty(target, key, descriptor);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Injects a CSS string into the given scope (document or shadow root) as an adopted stylesheet.
|
|
321
|
-
* Enables css-in-js so that every custom element is self-contained and importable.
|
|
322
|
-
*
|
|
323
|
-
* @hidden
|
|
324
|
-
* @param target - The scope to inject styles into: `document` for global styles, or a `ShadowRoot` for encapsulated component internals.
|
|
325
|
-
* @param css - The CSS string to inject.
|
|
326
|
-
* @returns The created `CSSStyleSheet` instance that was adopted into the target scope.
|
|
327
|
-
*
|
|
328
|
-
* When targeting **document**: selectors like `:host`, `::slotted()`, and shadow-internal classes are **not allowed** — they have no effect outside a shadow root. Use only global selectors, element tags, or layout presets.
|
|
329
|
-
* When targeting **shadowRoot**: use `:host` for the custom element itself, standard selectors (`.inner`, `#id`) for internal structure, and `::slotted()` for projected light-DOM content.
|
|
330
|
-
*
|
|
331
|
-
* @example Inject global styles into the document:
|
|
332
|
-
* ```ts
|
|
333
|
-
* import { injectCSS } from '@twobirds/mc/utils/helpers';
|
|
334
|
-
* injectCSS(document, 'body { display: block; }');
|
|
335
|
-
* ```
|
|
336
|
-
*
|
|
337
|
-
* @example Inject global styles for a micro component into the document:
|
|
338
|
-
* ```ts
|
|
339
|
-
* import { injectCSS } from '@twobirds/mc/utils/helpers';
|
|
340
|
-
* injectCSS(document, 'my-microcomponent { display: block; }');
|
|
341
|
-
* ```
|
|
342
|
-
*
|
|
343
|
-
* @example Inject scoped styles into a shadow root:
|
|
344
|
-
* ```ts
|
|
345
|
-
* const shadow = element.attachShadow({ mode: 'open' });
|
|
346
|
-
* injectCSS(shadow, ':host { color: var(--text); } .inner { padding: 8px; }');
|
|
347
|
-
* ```
|
|
348
|
-
* Styles can also be added using a compound innerHTML statement: `shadow.innerHTML = \`<style>${style}</style>${template}\``
|
|
349
|
-
*/
|
|
350
|
-
const _injectedCSS = new WeakMap();
|
|
351
|
-
export function injectCSS(target, css) {
|
|
352
|
-
const injected = _injectedCSS.get(target) ?? new Set();
|
|
353
|
-
if (injected.has(css))
|
|
354
|
-
return target.adoptedStyleSheets[0];
|
|
355
|
-
injected.add(css);
|
|
356
|
-
_injectedCSS.set(target, injected);
|
|
357
|
-
const sheet = new CSSStyleSheet();
|
|
358
|
-
sheet.replaceSync(css);
|
|
359
|
-
target.adoptedStyleSheets.push(sheet);
|
|
360
|
-
return sheet;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Processes a record of asset strings and applies them to the given target element.
|
|
364
|
-
* Supports injecting CSS into document or shadow root, and setting innerHTML for
|
|
365
|
-
* both target and shadow DOM. Automatically attaches a shadow root if any key starts
|
|
366
|
-
* with `'shadow'`.
|
|
367
|
-
*
|
|
368
|
-
* Recognized keys: `'css'`, `'targetCss'` (document-level styles), `'shadowCss'`
|
|
369
|
-
* (shadow-root styles), `'shadowHtml'` (shadow innerHTML), `'targetHtml'` (target innerHTML).
|
|
370
|
-
*
|
|
371
|
-
* @param instance - The micro component to apply assets onto.
|
|
372
|
-
* @param assets - A record mapping asset type keys to their string content.
|
|
373
|
-
*
|
|
374
|
-
* @example
|
|
375
|
-
* ```ts
|
|
376
|
-
* processAssets(this, {
|
|
377
|
-
* css: 'body { margin: 0; }',
|
|
378
|
-
* shadowCss: ':host { display: block; }',
|
|
379
|
-
* shadowHtml: '<slot></slot>'
|
|
380
|
-
* });
|
|
381
|
-
* ```
|
|
382
|
-
*/
|
|
383
|
-
export function processAssets(instance, assets) {
|
|
384
|
-
const target = instance.target;
|
|
385
|
-
let shadow = target.shadowRoot;
|
|
386
|
-
if (!shadow && Object.keys(assets).some((key) => key.indexOf('shadow') === 0)) {
|
|
387
|
-
shadow = target.attachShadow({ mode: 'open' });
|
|
388
|
-
}
|
|
389
|
-
Object.keys(assets).forEach((key) => {
|
|
390
|
-
switch (key) {
|
|
391
|
-
case 'css':
|
|
392
|
-
injectCSS(document, assets[key]);
|
|
393
|
-
break;
|
|
394
|
-
case 'targetCss':
|
|
395
|
-
injectCSS(document, assets[key]);
|
|
396
|
-
break;
|
|
397
|
-
case 'shadowCss':
|
|
398
|
-
injectCSS(shadow, assets[key]);
|
|
399
|
-
break;
|
|
400
|
-
case 'shadowHtml':
|
|
401
|
-
shadow.innerHTML = assets[key];
|
|
402
|
-
bindTemplateEvents(shadow, instance);
|
|
403
|
-
break;
|
|
404
|
-
case 'targetHtml':
|
|
405
|
-
target.innerHTML = assets[key];
|
|
406
|
-
bindTemplateEvents(target, instance);
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Loads companion asset files (CSS, HTML) for a given component basename from the server.
|
|
413
|
-
*
|
|
414
|
-
* Each entry in `assets` encodes an optional variant and extension in camelCase, e.g. `'shadowCss'` → `{name}.shadow.css`,
|
|
415
|
-
* `'targetHtml'` → `{name}.target.html`. Plain entries like `'css'` or `'html'` resolve to
|
|
416
|
-
* `{name}.css` / `{name}.html`.
|
|
417
|
-
*
|
|
418
|
-
* In bundled mode the Vite plugin replaces this call with an inline array literal, so `.then()`
|
|
419
|
-
* fires synchronously — no async overhead.
|
|
420
|
-
*
|
|
421
|
-
* @hidden
|
|
422
|
-
* @param name - The component basename (e.g. `'shop-catalog'`).
|
|
423
|
-
* @param assets - Array of asset descriptors such as `'shadowCss'`, `'targetHtml'`, `'shadowHtml'`, `'css'`, `'html'`.
|
|
424
|
-
* @returns A promise resolving to an array of file contents in the same order as `assets`. If a file is not found, the corresponding entry is `'file not found: ' + filename`.
|
|
425
|
-
*
|
|
426
|
-
* @example
|
|
427
|
-
* ```ts
|
|
428
|
-
* export const MyComponent = loadAssets('my-component', ['shadowCss', 'shadowHtml']).then(([shadowCss, shadowHtml]) => {
|
|
429
|
-
* class _MyComponent extends DC { ... }
|
|
430
|
-
* defineCE('my-component', _MyComponent);
|
|
431
|
-
* return _MyComponent;
|
|
432
|
-
* });
|
|
433
|
-
* ```
|
|
434
|
-
*/
|
|
435
|
-
export function loadAssets(name, assets) {
|
|
436
|
-
const parseAsset = (asset) => {
|
|
437
|
-
// Match camelCase variant+ext, e.g. 'shadowCss' → ['shadow','css']
|
|
438
|
-
const m = asset.match(/^([a-z]+)([A-Z][a-z]*)$/);
|
|
439
|
-
if (m) {
|
|
440
|
-
return `./${name.replace(/-/g, '/')}.${m[1]}.${m[2].toLowerCase()}`;
|
|
441
|
-
}
|
|
442
|
-
// Plain extension, e.g. 'css' → 'catalog.css'
|
|
443
|
-
return `./${name.replace(/-/g, '/')}.${asset.toLowerCase()}`;
|
|
444
|
-
};
|
|
445
|
-
return Promise.all(assets.map(async (asset) => {
|
|
446
|
-
const filename = parseAsset(asset);
|
|
447
|
-
//console.log(filename);
|
|
448
|
-
try {
|
|
449
|
-
const resp = await fetch(filename);
|
|
450
|
-
if (!resp.ok) {
|
|
451
|
-
return `file not found: ${filename}`;
|
|
452
|
-
}
|
|
453
|
-
if (filename.endsWith('json')) {
|
|
454
|
-
const text = await resp.text();
|
|
455
|
-
try {
|
|
456
|
-
return JSON.parse(text);
|
|
457
|
-
}
|
|
458
|
-
catch {
|
|
459
|
-
console.error(`could not JSON.parse() asset '${asset}' from ${filename}:\n`, text);
|
|
460
|
-
return null;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return resp.text();
|
|
464
|
-
}
|
|
465
|
-
catch {
|
|
466
|
-
return `file not found: ${filename}`;
|
|
467
|
-
}
|
|
468
|
-
}));
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Binds declarative event attributes in a template fragment to microcomponent triggers.
|
|
472
|
-
*
|
|
473
|
-
* Scans all elements under `root` for attributes whose names correspond to native DOM
|
|
474
|
-
* events (e.g. `click`, `submit`, `change`). The attribute value encodes the target and
|
|
475
|
-
* handler method in the form `target|methodName`:
|
|
476
|
-
*
|
|
477
|
-
* - `this|myHandler` — triggers locally on the component attached to this element.
|
|
478
|
-
* - `other-comp|submit` — resolves elements via `querySelectorAll('other-comp')` and
|
|
479
|
-
* triggers the event on each matched host's `_mc`.
|
|
480
|
-
*
|
|
481
|
-
* An optional sibling `payload` attribute supplies the data argument for `trigger()`.
|
|
482
|
-
* After binding, both the event attribute and the payload attribute are removed from the DOM.
|
|
483
|
-
*
|
|
484
|
-
* @param rootElement - The container element whose descendants should be scanned for bindings.
|
|
485
|
-
* @param instance - The instance to trigger events on.
|
|
486
|
-
*
|
|
487
|
-
* @example
|
|
488
|
-
* ```html
|
|
489
|
-
* <button click='this|onSubmit|{"action":"go"}'>Submit</button>
|
|
490
|
-
* <input change="|cart-form|refresh">
|
|
491
|
-
* ```
|
|
492
|
-
*/
|
|
493
|
-
export function bindTemplateEvents(rootElement, instance) {
|
|
494
|
-
// Collect all elements that carry at least one attribute matching a native event name.
|
|
495
|
-
const allElements = [...rootElement.querySelectorAll('*')];
|
|
496
|
-
for (const el of allElements) {
|
|
497
|
-
if (!(el instanceof HTMLElement))
|
|
498
|
-
continue;
|
|
499
|
-
const attrs = [...el.attributes];
|
|
500
|
-
for (const attr of attrs) {
|
|
501
|
-
const lowerName = attr.name.toLowerCase();
|
|
502
|
-
// Check if this attribute name maps to a native event handler property.
|
|
503
|
-
if (!('on' + lowerName in el))
|
|
504
|
-
continue;
|
|
505
|
-
const parts = attr.value.trim().split('|');
|
|
506
|
-
if (parts.length < 2)
|
|
507
|
-
continue; // need at least selector and eventname
|
|
508
|
-
const [selector, event, payload] = parts;
|
|
509
|
-
// Clean up binding attributes from the DOM.
|
|
510
|
-
el.removeAttribute(attr.name);
|
|
511
|
-
// Resolve payload from sibling attribute if present.
|
|
512
|
-
let payloadData; // intentional
|
|
513
|
-
if (payload) {
|
|
514
|
-
try {
|
|
515
|
-
payloadData = JSON.parse(payload);
|
|
516
|
-
}
|
|
517
|
-
catch {
|
|
518
|
-
payloadData = payload;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// Wire the native event listener.
|
|
522
|
-
if (selector === 'this' || selector === '') {
|
|
523
|
-
// Local trigger — fire on the _mc attached to this element.
|
|
524
|
-
on(el, lowerName, () => instance.trigger(event, payloadData));
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
// Selector-based trigger — query and fire on every match.
|
|
528
|
-
on(el, lowerName, () => trigger(selector, event, payloadData));
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
package/dist/utils/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Utils module — events, autoload, and helper utilities.
|
|
3
|
-
|
|
4
|
-
@module Utilities
|
|
5
|
-
@showGroups
|
|
6
|
-
*/
|
|
7
|
-
/** @group Events */
|
|
8
|
-
export { getMcEventName, trueEventName, off, on, one, trigger, BubbleController, } from './events.js';
|
|
9
|
-
/** @group Autoload */
|
|
10
|
-
export { autoload } from './autoload.js';
|
|
11
|
-
/** @group Helpers */
|
|
12
|
-
export { isBrowser, isNativeObject, sortObject, flattenObject, deepEqual, nameSpace, parse, kebabToPascal, debounce, htmlToElements, copyGettersSetters, injectCSS, processAssets, loadAssets, bindTemplateEvents, } from './helpers.js';
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module Autoload
|
|
3
|
-
|
|
4
|
-
Single source of truth for the tag-to-file convention: hyphens become path
|
|
5
|
-
separators, so `my-card` resolves to `./my/card.js`. Imported by the runtime
|
|
6
|
-
autoload loader and by build-time tooling so both agree on the path without
|
|
7
|
-
either duplicating the rule.
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
The tag-to-path primitive: hyphens become path separators, with no prefix or
|
|
11
|
-
extension. `my-card` → `my/card`. The single source of truth for the
|
|
12
|
-
convention; {@link resolveComponentPath} and build-time tooling both derive
|
|
13
|
-
their paths from it so neither duplicates the rule.
|
|
14
|
-
|
|
15
|
-
@example Returns `'my/card'` for `my-card`, and `'app/header'` for `app-header`.
|
|
16
|
-
```ts
|
|
17
|
-
tagPath('my-card');
|
|
18
|
-
tagPath('app-header');
|
|
19
|
-
```
|
|
20
|
-
@hidden
|
|
21
|
-
@group Autoload
|
|
22
|
-
*/
|
|
23
|
-
export function tagPath(tag) {
|
|
24
|
-
return tag.split('-').join('/');
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
Resolves a microcomponent tag name to its autoload module path by prefixing and
|
|
28
|
-
suffixing {@link tagPath}: `my-card` → `./my/card.js`.
|
|
29
|
-
|
|
30
|
-
@example Returns `'./my/card.js'` for `my-card`, and `'./app/header.js'` for `app-header`.
|
|
31
|
-
```ts
|
|
32
|
-
resolveComponentPath('my-card');
|
|
33
|
-
resolveComponentPath('app-header');
|
|
34
|
-
```
|
|
35
|
-
@group Autoload
|
|
36
|
-
*/
|
|
37
|
-
export function resolveComponentPath(tag) {
|
|
38
|
-
return './' + tagPath(tag) + '.js';
|
|
39
|
-
}
|
package/dist/vite/index.d.ts
DELETED
package/dist/vite/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA;;;;EAIE;AAEF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/vite/index.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Inlines `static assets = [...]` declarations on DC classes for a bundled build: companion
|
|
3
|
-
`.css` / `.html` files are read at build time and embedded as a `static _assets = { ... }`
|
|
4
|
-
property inserted as the first line inside the class body, so `loadAssets()` never fetches
|
|
5
|
-
them at runtime. The original `static assets` line is preserved for debugging purposes.
|
|
6
|
-
|
|
7
|
-
The plugin ships as its own entry, `@twobirds/microcomponents/vite`, so a project that
|
|
8
|
-
bundles to `public/` imports it without copying the source.
|
|
9
|
-
|
|
10
|
-
@example
|
|
11
|
-
```ts
|
|
12
|
-
import { loadAssetsPlugin } from '@twobirds/microcomponents/vite';
|
|
13
|
-
|
|
14
|
-
export default defineConfig({
|
|
15
|
-
plugins: [loadAssetsPlugin(sourceRoot)],
|
|
16
|
-
});
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
@module Vite
|
|
20
|
-
*/
|
|
21
|
-
import type { PluginOption } from 'vite';
|
|
22
|
-
/**
|
|
23
|
-
* Vite plugin factory that inlines companion `.css` / `.html` assets into the
|
|
24
|
-
* DC classes declaring them through `static assets = [...]`.
|
|
25
|
-
*
|
|
26
|
-
* @example Register the plugin in a Vite config, pointing at the source root.
|
|
27
|
-
* ```ts
|
|
28
|
-
* import { loadAssetsPlugin } from './vite-plugin-load-assets';
|
|
29
|
-
* export default defineConfig({ plugins: [loadAssetsPlugin(__dirname)] });
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @param rootDir Directory holding the component source files.
|
|
33
|
-
* @returns A Vite plugin option.
|
|
34
|
-
*/
|
|
35
|
-
export declare function loadAssetsPlugin(rootDir: string): PluginOption;
|
|
36
|
-
//# sourceMappingURL=loadAssets.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loadAssets.d.ts","sourceRoot":"","sources":["../../src/vite/loadAssets.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;EAmBE;AAIF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAgJzC;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAiE9D"}
|