@devera_se/bedrockjs 0.1.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 +519 -0
- package/package.json +65 -0
- package/src/component.d.ts +36 -0
- package/src/component.js +357 -0
- package/src/html.d.ts +12 -0
- package/src/html.js +148 -0
- package/src/index.d.ts +12 -0
- package/src/index.js +50 -0
- package/src/reactive.d.ts +20 -0
- package/src/reactive.js +277 -0
- package/src/render.d.ts +5 -0
- package/src/render.js +326 -0
- package/src/router.d.ts +52 -0
- package/src/router.js +356 -0
package/src/component.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Component class for creating web components
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { render } from './render.js';
|
|
6
|
+
import { watch } from './reactive.js';
|
|
7
|
+
|
|
8
|
+
// Registry of defined components
|
|
9
|
+
const componentRegistry = new Map();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base class for creating reactive web components
|
|
13
|
+
*/
|
|
14
|
+
export class Component extends HTMLElement {
|
|
15
|
+
// Override in subclass to set custom tag name
|
|
16
|
+
static tag = null;
|
|
17
|
+
|
|
18
|
+
// Set to true to use Shadow DOM
|
|
19
|
+
static shadow = false;
|
|
20
|
+
|
|
21
|
+
// Define reactive properties
|
|
22
|
+
static properties = {};
|
|
23
|
+
|
|
24
|
+
// Set to false to disable auto-registration
|
|
25
|
+
static autoRegister = true;
|
|
26
|
+
|
|
27
|
+
// Store for property values
|
|
28
|
+
#props = {};
|
|
29
|
+
|
|
30
|
+
// Watcher cleanup function
|
|
31
|
+
#stopWatch = null;
|
|
32
|
+
|
|
33
|
+
// Render root (shadow or light DOM)
|
|
34
|
+
#renderRoot = null;
|
|
35
|
+
|
|
36
|
+
// Whether component is connected
|
|
37
|
+
#connected = false;
|
|
38
|
+
|
|
39
|
+
// Pending render flag
|
|
40
|
+
#pendingRender = false;
|
|
41
|
+
|
|
42
|
+
// Route data from router
|
|
43
|
+
#routeData = null;
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
super();
|
|
47
|
+
|
|
48
|
+
// Initialize shadow DOM if enabled
|
|
49
|
+
if (this.constructor.shadow) {
|
|
50
|
+
this.#renderRoot = this.attachShadow({ mode: 'open' });
|
|
51
|
+
} else {
|
|
52
|
+
this.#renderRoot = this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Initialize reactive properties
|
|
56
|
+
this.#initializeProperties();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize reactive properties from static definition
|
|
61
|
+
*/
|
|
62
|
+
#initializeProperties() {
|
|
63
|
+
const properties = this.constructor.properties;
|
|
64
|
+
|
|
65
|
+
for (const [name, config] of Object.entries(properties)) {
|
|
66
|
+
const normalizedConfig = typeof config === 'function'
|
|
67
|
+
? { type: config }
|
|
68
|
+
: config;
|
|
69
|
+
|
|
70
|
+
// Set default value
|
|
71
|
+
if (normalizedConfig.default !== undefined) {
|
|
72
|
+
this.#props[name] = typeof normalizedConfig.default === 'function'
|
|
73
|
+
? normalizedConfig.default()
|
|
74
|
+
: normalizedConfig.default;
|
|
75
|
+
} else {
|
|
76
|
+
this.#props[name] = undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Define getter/setter
|
|
80
|
+
Object.defineProperty(this, name, {
|
|
81
|
+
get: () => this.#props[name],
|
|
82
|
+
set: (value) => {
|
|
83
|
+
const oldValue = this.#props[name];
|
|
84
|
+
const coerced = this.#coerceValue(value, normalizedConfig.type);
|
|
85
|
+
|
|
86
|
+
if (oldValue !== coerced) {
|
|
87
|
+
this.#props[name] = coerced;
|
|
88
|
+
this.#scheduleRender();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
enumerable: true,
|
|
92
|
+
configurable: true
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Coerce value to the specified type
|
|
99
|
+
*/
|
|
100
|
+
#coerceValue(value, type) {
|
|
101
|
+
if (value === null || value === undefined) {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!type) return value;
|
|
106
|
+
|
|
107
|
+
switch (type) {
|
|
108
|
+
case String:
|
|
109
|
+
return String(value);
|
|
110
|
+
case Number:
|
|
111
|
+
return Number(value);
|
|
112
|
+
case Boolean:
|
|
113
|
+
return Boolean(value);
|
|
114
|
+
case Array:
|
|
115
|
+
return Array.isArray(value) ? value : [value];
|
|
116
|
+
case Object:
|
|
117
|
+
return typeof value === 'object' ? value : { value };
|
|
118
|
+
default:
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the render root (shadow DOM or this element)
|
|
125
|
+
*/
|
|
126
|
+
get renderRoot() {
|
|
127
|
+
return this.#renderRoot;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get route data
|
|
132
|
+
*/
|
|
133
|
+
get routeData() {
|
|
134
|
+
return this.#routeData;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Set route data (called by router)
|
|
139
|
+
*/
|
|
140
|
+
set routeData(data) {
|
|
141
|
+
this.#routeData = data;
|
|
142
|
+
this.#scheduleRender();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Called when element is connected to DOM
|
|
147
|
+
*/
|
|
148
|
+
connectedCallback() {
|
|
149
|
+
this.#connected = true;
|
|
150
|
+
|
|
151
|
+
// Read attributes into properties
|
|
152
|
+
this.#readAttributes();
|
|
153
|
+
|
|
154
|
+
// Initial render
|
|
155
|
+
this.#doRender();
|
|
156
|
+
|
|
157
|
+
// Set up reactive watching
|
|
158
|
+
this.#stopWatch = watch(() => {
|
|
159
|
+
this.#doRender();
|
|
160
|
+
}, { immediate: false });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Called when element is disconnected from DOM
|
|
165
|
+
*/
|
|
166
|
+
disconnectedCallback() {
|
|
167
|
+
this.#connected = false;
|
|
168
|
+
|
|
169
|
+
if (this.#stopWatch) {
|
|
170
|
+
this.#stopWatch();
|
|
171
|
+
this.#stopWatch = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Read attributes into properties
|
|
177
|
+
*/
|
|
178
|
+
#readAttributes() {
|
|
179
|
+
const properties = this.constructor.properties;
|
|
180
|
+
|
|
181
|
+
for (const [name, config] of Object.entries(properties)) {
|
|
182
|
+
const normalizedConfig = typeof config === 'function'
|
|
183
|
+
? { type: config }
|
|
184
|
+
: config;
|
|
185
|
+
|
|
186
|
+
// Convert property name to attribute name (camelCase -> kebab-case)
|
|
187
|
+
const attrName = name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
188
|
+
|
|
189
|
+
if (this.hasAttribute(attrName)) {
|
|
190
|
+
const attrValue = this.getAttribute(attrName);
|
|
191
|
+
this[name] = this.#parseAttributeValue(attrValue, normalizedConfig.type);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parse attribute string value based on type
|
|
198
|
+
*/
|
|
199
|
+
#parseAttributeValue(value, type) {
|
|
200
|
+
if (!type) return value;
|
|
201
|
+
|
|
202
|
+
switch (type) {
|
|
203
|
+
case Boolean:
|
|
204
|
+
return value !== null && value !== 'false';
|
|
205
|
+
case Number:
|
|
206
|
+
return Number(value);
|
|
207
|
+
case Array:
|
|
208
|
+
case Object:
|
|
209
|
+
try {
|
|
210
|
+
return JSON.parse(value);
|
|
211
|
+
} catch {
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Observe attribute changes
|
|
221
|
+
*/
|
|
222
|
+
static get observedAttributes() {
|
|
223
|
+
const properties = this.properties || {};
|
|
224
|
+
return Object.keys(properties).map(name =>
|
|
225
|
+
name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Called when an observed attribute changes
|
|
231
|
+
*/
|
|
232
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
233
|
+
if (oldValue === newValue) return;
|
|
234
|
+
|
|
235
|
+
// Convert attribute name to property name (kebab-case -> camelCase)
|
|
236
|
+
const propName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
237
|
+
|
|
238
|
+
if (propName in this.constructor.properties) {
|
|
239
|
+
const config = this.constructor.properties[propName];
|
|
240
|
+
const normalizedConfig = typeof config === 'function'
|
|
241
|
+
? { type: config }
|
|
242
|
+
: config;
|
|
243
|
+
|
|
244
|
+
this[propName] = this.#parseAttributeValue(newValue, normalizedConfig.type);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Schedule a render on next microtask
|
|
250
|
+
*/
|
|
251
|
+
#scheduleRender() {
|
|
252
|
+
if (!this.#connected || this.#pendingRender) return;
|
|
253
|
+
|
|
254
|
+
this.#pendingRender = true;
|
|
255
|
+
queueMicrotask(() => {
|
|
256
|
+
this.#pendingRender = false;
|
|
257
|
+
if (this.#connected) {
|
|
258
|
+
this.#doRender();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Perform the actual render
|
|
265
|
+
*/
|
|
266
|
+
#doRender() {
|
|
267
|
+
const result = this.render();
|
|
268
|
+
if (result) {
|
|
269
|
+
render(result, this.#renderRoot);
|
|
270
|
+
}
|
|
271
|
+
this.updated();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Override to return template result
|
|
276
|
+
* @returns {TemplateResult|null}
|
|
277
|
+
*/
|
|
278
|
+
render() {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Called after each render
|
|
284
|
+
*/
|
|
285
|
+
updated() {
|
|
286
|
+
// Override in subclass
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Manually trigger a re-render
|
|
291
|
+
*/
|
|
292
|
+
requestUpdate() {
|
|
293
|
+
this.#scheduleRender();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Register this component with the custom elements registry
|
|
298
|
+
* @param {string} [tagName] - Optional tag name override
|
|
299
|
+
*/
|
|
300
|
+
static register(tagName) {
|
|
301
|
+
const tag = tagName || this.tag;
|
|
302
|
+
|
|
303
|
+
if (!tag) {
|
|
304
|
+
throw new Error('Component must have a tag name');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!customElements.get(tag)) {
|
|
308
|
+
customElements.define(tag, this);
|
|
309
|
+
componentRegistry.set(tag, this);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Decorator/helper to define a component
|
|
318
|
+
* @param {string} tag - Tag name
|
|
319
|
+
* @param {Object} options - Component options
|
|
320
|
+
*/
|
|
321
|
+
export function defineComponent(tag, options = {}) {
|
|
322
|
+
return (ComponentClass) => {
|
|
323
|
+
ComponentClass.tag = tag;
|
|
324
|
+
|
|
325
|
+
if (options.shadow !== undefined) {
|
|
326
|
+
ComponentClass.shadow = options.shadow;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (options.properties) {
|
|
330
|
+
ComponentClass.properties = {
|
|
331
|
+
...ComponentClass.properties,
|
|
332
|
+
...options.properties
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (options.autoRegister !== false) {
|
|
337
|
+
ComponentClass.register();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return ComponentClass;
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Auto-register components when the class is defined
|
|
346
|
+
* Call this after defining your component class
|
|
347
|
+
*/
|
|
348
|
+
export function autoRegister(ComponentClass) {
|
|
349
|
+
if (ComponentClass.autoRegister && ComponentClass.tag) {
|
|
350
|
+
ComponentClass.register();
|
|
351
|
+
}
|
|
352
|
+
return ComponentClass;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Hook into class definition to auto-register
|
|
356
|
+
// This runs when the module is imported
|
|
357
|
+
const originalDefine = Object.defineProperty;
|
package/src/html.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class TemplateResult {
|
|
2
|
+
strings: TemplateStringsArray;
|
|
3
|
+
values: any[];
|
|
4
|
+
|
|
5
|
+
constructor(strings: TemplateStringsArray, values: any[]);
|
|
6
|
+
|
|
7
|
+
getTemplate(): any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function html(strings: TemplateStringsArray, ...values: any[]): TemplateResult;
|
|
11
|
+
|
|
12
|
+
export function isTemplateResult(value: any): value is TemplateResult;
|
package/src/html.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template literal parser for creating efficient DOM templates
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Unique marker for template parts
|
|
6
|
+
const MARKER = `bedrock-${Math.random().toString(36).slice(2)}`;
|
|
7
|
+
const COMMENT_MARKER = `<!--${MARKER}-`;
|
|
8
|
+
const ATTR_MARKER = `${MARKER}-`;
|
|
9
|
+
|
|
10
|
+
// Template cache for reusing parsed templates
|
|
11
|
+
const templateCache = new WeakMap();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a parsed template with static parts and dynamic values
|
|
15
|
+
*/
|
|
16
|
+
export class TemplateResult {
|
|
17
|
+
constructor(strings, values) {
|
|
18
|
+
this.strings = strings;
|
|
19
|
+
this.values = values;
|
|
20
|
+
this._type = 'template-result';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get cached template or create new one
|
|
25
|
+
*/
|
|
26
|
+
getTemplate() {
|
|
27
|
+
let template = templateCache.get(this.strings);
|
|
28
|
+
if (!template) {
|
|
29
|
+
template = parseTemplate(this.strings);
|
|
30
|
+
templateCache.set(this.strings, template);
|
|
31
|
+
}
|
|
32
|
+
return template;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Tagged template literal function for creating templates
|
|
38
|
+
* @param {TemplateStringsArray} strings - Static parts
|
|
39
|
+
* @param {...any} values - Dynamic values
|
|
40
|
+
* @returns {TemplateResult}
|
|
41
|
+
*/
|
|
42
|
+
export function html(strings, ...values) {
|
|
43
|
+
return new TemplateResult(strings, values);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if we're inside an HTML tag (for attribute vs node detection)
|
|
48
|
+
*/
|
|
49
|
+
function isInsideTag(str) {
|
|
50
|
+
let inTag = false;
|
|
51
|
+
for (let i = str.length - 1; i >= 0; i--) {
|
|
52
|
+
if (str[i] === '>') return false;
|
|
53
|
+
if (str[i] === '<') return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse template strings into a reusable template structure
|
|
60
|
+
*/
|
|
61
|
+
function parseTemplate(strings) {
|
|
62
|
+
const parts = [];
|
|
63
|
+
let htmlStr = '';
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < strings.length; i++) {
|
|
66
|
+
htmlStr += strings[i];
|
|
67
|
+
|
|
68
|
+
if (i < strings.length - 1) {
|
|
69
|
+
// Check if this expression is inside a tag (attribute) or outside (node)
|
|
70
|
+
if (isInsideTag(htmlStr)) {
|
|
71
|
+
// Attribute position - use attribute marker
|
|
72
|
+
htmlStr += `${ATTR_MARKER}${i}`;
|
|
73
|
+
parts.push({ type: 'attr-pending', index: i });
|
|
74
|
+
} else {
|
|
75
|
+
// Node position - use comment marker
|
|
76
|
+
htmlStr += `${COMMENT_MARKER}${i}-->`;
|
|
77
|
+
parts.push({ type: 'node', index: i });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Parse into template element
|
|
83
|
+
const template = document.createElement('template');
|
|
84
|
+
template.innerHTML = htmlStr;
|
|
85
|
+
|
|
86
|
+
// Walk the template to resolve part locations
|
|
87
|
+
const resolvedParts = new Array(strings.length - 1).fill(null);
|
|
88
|
+
walkTemplate(template.content, resolvedParts, []);
|
|
89
|
+
|
|
90
|
+
return { element: template, parts: resolvedParts };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walk the template DOM to find marker positions
|
|
95
|
+
*/
|
|
96
|
+
function walkTemplate(node, parts, path) {
|
|
97
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
98
|
+
// Check attributes for markers
|
|
99
|
+
const attrsToRemove = [];
|
|
100
|
+
for (const attr of node.attributes) {
|
|
101
|
+
if (attr.value.includes(ATTR_MARKER) || attr.name.includes(ATTR_MARKER)) {
|
|
102
|
+
const match = (attr.value + attr.name).match(new RegExp(`${ATTR_MARKER}(\\d+)`));
|
|
103
|
+
if (match) {
|
|
104
|
+
const index = parseInt(match[1], 10);
|
|
105
|
+
const name = attr.name.replace(new RegExp(`${ATTR_MARKER}\\d+`), '');
|
|
106
|
+
const isEvent = name.startsWith('on-');
|
|
107
|
+
const isProperty = name.startsWith('.');
|
|
108
|
+
|
|
109
|
+
parts[index] = {
|
|
110
|
+
type: isEvent ? 'event' : isProperty ? 'property' : 'attribute',
|
|
111
|
+
path: [...path],
|
|
112
|
+
name: isEvent ? name.slice(3) : isProperty ? name.slice(1) : name,
|
|
113
|
+
};
|
|
114
|
+
attrsToRemove.push(attr.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Remove marker attributes
|
|
119
|
+
for (const name of attrsToRemove) {
|
|
120
|
+
node.removeAttribute(name);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check comment nodes for markers
|
|
125
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
126
|
+
const text = node.textContent;
|
|
127
|
+
if (text.startsWith(MARKER + '-')) {
|
|
128
|
+
const index = parseInt(text.slice(MARKER.length + 1), 10);
|
|
129
|
+
parts[index] = {
|
|
130
|
+
type: 'node',
|
|
131
|
+
path: [...path]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Recurse into children
|
|
137
|
+
const children = Array.from(node.childNodes);
|
|
138
|
+
for (let i = 0; i < children.length; i++) {
|
|
139
|
+
walkTemplate(children[i], parts, [...path, i]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if a value is a TemplateResult
|
|
145
|
+
*/
|
|
146
|
+
export function isTemplateResult(value) {
|
|
147
|
+
return value && value._type === 'template-result';
|
|
148
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { html, TemplateResult, isTemplateResult } from './html.js';
|
|
2
|
+
export { render, keyed } from './render.js';
|
|
3
|
+
export { Component, defineComponent, autoRegister } from './component.js';
|
|
4
|
+
export { reactive, watch, computed, signal, batch } from './reactive.js';
|
|
5
|
+
export {
|
|
6
|
+
Router,
|
|
7
|
+
RouterOutlet,
|
|
8
|
+
RouterLink,
|
|
9
|
+
createRouter,
|
|
10
|
+
navigate,
|
|
11
|
+
getParams
|
|
12
|
+
} from './router.js';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BedrockJS - A lightweight web framework built on web components
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import { html, Component, reactive, Router } from 'bedrockjs';
|
|
6
|
+
*
|
|
7
|
+
* class MyCounter extends Component {
|
|
8
|
+
* static tag = 'my-counter';
|
|
9
|
+
* static properties = {
|
|
10
|
+
* count: { type: Number, default: 0 }
|
|
11
|
+
* };
|
|
12
|
+
*
|
|
13
|
+
* render() {
|
|
14
|
+
* return html`
|
|
15
|
+
* <button on-click=${() => this.count++}>
|
|
16
|
+
* Count: ${this.count}
|
|
17
|
+
* </button>
|
|
18
|
+
* `;
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* MyCounter.register();
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Template system
|
|
25
|
+
export { html, TemplateResult, isTemplateResult } from './html.js';
|
|
26
|
+
|
|
27
|
+
// Rendering
|
|
28
|
+
export { render, keyed } from './render.js';
|
|
29
|
+
|
|
30
|
+
// Component system
|
|
31
|
+
export { Component, defineComponent, autoRegister } from './component.js';
|
|
32
|
+
|
|
33
|
+
// Reactive state
|
|
34
|
+
export {
|
|
35
|
+
reactive,
|
|
36
|
+
watch,
|
|
37
|
+
computed,
|
|
38
|
+
signal,
|
|
39
|
+
batch
|
|
40
|
+
} from './reactive.js';
|
|
41
|
+
|
|
42
|
+
// Router
|
|
43
|
+
export {
|
|
44
|
+
Router,
|
|
45
|
+
RouterOutlet,
|
|
46
|
+
RouterLink,
|
|
47
|
+
createRouter,
|
|
48
|
+
navigate,
|
|
49
|
+
getParams
|
|
50
|
+
} from './router.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function reactive<T extends object>(target: T): T;
|
|
2
|
+
|
|
3
|
+
export interface WatchOptions {
|
|
4
|
+
immediate?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function watch(fn: () => void, options?: WatchOptions): () => void;
|
|
8
|
+
|
|
9
|
+
export interface Computed<T> {
|
|
10
|
+
readonly value: T;
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function computed<T>(getter: () => T): Computed<T>;
|
|
15
|
+
|
|
16
|
+
export type Signal<T> = [() => T, (value: T) => void];
|
|
17
|
+
|
|
18
|
+
export function signal<T>(initialValue: T): Signal<T>;
|
|
19
|
+
|
|
20
|
+
export function batch(fn: () => void): void;
|