@geektech/tsone 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2453 +0,0 @@
1
- import {
2
- StyleManager,
3
- renderStyleSheet
4
- } from "./index-ffzday7h.js";
5
-
6
- // lib/core/reactive/types.ts
7
- var IS_REACTIVE = Symbol("is_reactive");
8
- var IS_READONLY = Symbol("is_readonly");
9
- var IS_REF = Symbol("is_ref");
10
- var MUTATING_ARRAY_METHODS = [
11
- "push",
12
- "pop",
13
- "shift",
14
- "unshift",
15
- "splice",
16
- "sort",
17
- "reverse"
18
- ];
19
- function hasReactiveFlag(value, flag) {
20
- return Boolean(Reflect.get(value, flag));
21
- }
22
- function isObject(value) {
23
- return value !== null && typeof value === "object";
24
- }
25
-
26
- // lib/core/reactive.ts
27
- var effectId = 0;
28
-
29
- class ReactiveSystem {
30
- static instance;
31
- activeEffect = null;
32
- effectStack = [];
33
- targetMap = new WeakMap;
34
- reactiveMap = new WeakMap;
35
- readonlyMap = new WeakMap;
36
- constructor() {}
37
- static getInstance() {
38
- if (!ReactiveSystem.instance) {
39
- ReactiveSystem.instance = new ReactiveSystem;
40
- }
41
- return ReactiveSystem.instance;
42
- }
43
- reactive(target) {
44
- if (!isObject(target)) {
45
- console.warn("reactive: target must be an object");
46
- return target;
47
- }
48
- if (isReactive(target)) {
49
- return target;
50
- }
51
- if (this.reactiveMap.has(target)) {
52
- return this.reactiveMap.get(target);
53
- }
54
- if (Array.isArray(target)) {
55
- return this.createReactiveArray(target);
56
- }
57
- const proxy = new Proxy(target, {
58
- get: (target2, key) => {
59
- if (key === IS_REACTIVE) {
60
- return true;
61
- }
62
- if (key === IS_READONLY) {
63
- return false;
64
- }
65
- this.track(target2, key);
66
- const value = Reflect.get(target2, key);
67
- if (isObject(value) && !hasReactiveFlag(value, IS_READONLY)) {
68
- return this.reactive(value);
69
- }
70
- return value;
71
- },
72
- set: (target2, key, value) => {
73
- if (hasReactiveFlag(target2, IS_READONLY)) {
74
- console.warn(`Cannot set property ${String(key)} on readonly object`);
75
- return false;
76
- }
77
- const oldValue = Reflect.get(target2, key);
78
- if (isObject(value) && !hasReactiveFlag(value, IS_REACTIVE) && !hasReactiveFlag(value, IS_READONLY)) {
79
- value = this.reactive(value);
80
- }
81
- const result = Reflect.set(target2, key, value);
82
- if (oldValue !== value) {
83
- this.trigger(target2, key);
84
- }
85
- return result;
86
- },
87
- deleteProperty: (target2, key) => {
88
- if (hasReactiveFlag(target2, IS_READONLY)) {
89
- console.warn(`Cannot delete property ${String(key)} on readonly object`);
90
- return false;
91
- }
92
- const hadKey = key in target2;
93
- const result = Reflect.deleteProperty(target2, key);
94
- if (hadKey) {
95
- this.trigger(target2, key);
96
- }
97
- return result;
98
- }
99
- });
100
- this.reactiveMap.set(target, proxy);
101
- return proxy;
102
- }
103
- createReactiveArray(target) {
104
- if (this.reactiveMap.has(target)) {
105
- return this.reactiveMap.get(target);
106
- }
107
- const proxy = new Proxy(target, {
108
- get: (target2, key) => {
109
- if (key === IS_REACTIVE) {
110
- return true;
111
- }
112
- if (key === IS_READONLY) {
113
- return false;
114
- }
115
- this.track(target2, key);
116
- const value = Reflect.get(target2, key);
117
- if (typeof key === "string" && MUTATING_ARRAY_METHODS.includes(key)) {
118
- return (...args) => {
119
- const arrayMethod = value;
120
- const result = arrayMethod.apply(target2, args);
121
- this.trigger(target2, "length");
122
- this.trigger(target2, key);
123
- return result;
124
- };
125
- }
126
- if (isObject(value) && !hasReactiveFlag(value, IS_READONLY)) {
127
- return this.reactive(value);
128
- }
129
- return value;
130
- },
131
- set: (target2, key, value) => {
132
- if (hasReactiveFlag(target2, IS_READONLY)) {
133
- console.warn(`Cannot set property ${String(key)} on readonly object`);
134
- return false;
135
- }
136
- const oldValue = Reflect.get(target2, key);
137
- if (isObject(value) && !hasReactiveFlag(value, IS_REACTIVE) && !hasReactiveFlag(value, IS_READONLY)) {
138
- value = this.reactive(value);
139
- }
140
- const result = Reflect.set(target2, key, value);
141
- if (oldValue !== value) {
142
- this.trigger(target2, key);
143
- if (typeof key === "string" && !isNaN(Number(key))) {
144
- this.trigger(target2, "length");
145
- }
146
- }
147
- return result;
148
- },
149
- deleteProperty: (target2, key) => {
150
- if (hasReactiveFlag(target2, IS_READONLY)) {
151
- console.warn(`Cannot delete property ${String(key)} on readonly object`);
152
- return false;
153
- }
154
- const hadKey = key in target2;
155
- const result = Reflect.deleteProperty(target2, key);
156
- if (hadKey) {
157
- this.trigger(target2, key);
158
- this.trigger(target2, "length");
159
- }
160
- return result;
161
- }
162
- });
163
- this.reactiveMap.set(target, proxy);
164
- return proxy;
165
- }
166
- readonly(target) {
167
- if (!isObject(target)) {
168
- console.warn("readonly: target must be an object");
169
- return target;
170
- }
171
- if (hasReactiveFlag(target, IS_READONLY)) {
172
- return target;
173
- }
174
- if (this.readonlyMap.has(target)) {
175
- return this.readonlyMap.get(target);
176
- }
177
- const proxy = new Proxy(target, {
178
- get: (target2, key) => {
179
- if (key === IS_REACTIVE) {
180
- return false;
181
- }
182
- if (key === IS_READONLY) {
183
- return true;
184
- }
185
- const value = Reflect.get(target2, key);
186
- if (isObject(value)) {
187
- return this.readonly(value);
188
- }
189
- return value;
190
- },
191
- set: () => {
192
- console.warn("Cannot set property on readonly object");
193
- return false;
194
- },
195
- deleteProperty: () => {
196
- console.warn("Cannot delete property on readonly object");
197
- return false;
198
- }
199
- });
200
- this.readonlyMap.set(target, proxy);
201
- return proxy;
202
- }
203
- effect(fn, options) {
204
- const { lazy = false, scheduler, throwOnError = false } = options || {};
205
- const effectFn = () => {
206
- if (!effectFn.active) {
207
- return fn();
208
- }
209
- try {
210
- this.cleanup(effectFn);
211
- this.effectStack.push(effectFn);
212
- this.activeEffect = effectFn;
213
- return fn();
214
- } catch (error) {
215
- if (throwOnError) {
216
- throw error;
217
- }
218
- console.error("Effect error:", error);
219
- return;
220
- } finally {
221
- this.effectStack.pop();
222
- this.activeEffect = this.effectStack[this.effectStack.length - 1] ?? null;
223
- }
224
- };
225
- effectFn.id = effectId++;
226
- effectFn.deps = [];
227
- effectFn.active = true;
228
- effectFn.scheduler = scheduler;
229
- if (!lazy) {
230
- effectFn();
231
- }
232
- return effectFn;
233
- }
234
- computed(getter) {
235
- let dirty = true;
236
- let value;
237
- const computedTarget = {};
238
- const trackComputedValue = () => {
239
- this.track(computedTarget, "value");
240
- };
241
- const runner = this.effect(() => {
242
- value = getter();
243
- dirty = false;
244
- }, {
245
- lazy: true,
246
- scheduler: () => {
247
- if (!dirty) {
248
- dirty = true;
249
- this.trigger(computedTarget, "value");
250
- }
251
- }
252
- });
253
- return {
254
- get value() {
255
- if (dirty) {
256
- runner();
257
- }
258
- trackComputedValue();
259
- return value;
260
- }
261
- };
262
- }
263
- cleanup(effect) {
264
- effect.deps.forEach((dep) => {
265
- dep.delete(effect);
266
- });
267
- effect.deps.length = 0;
268
- }
269
- track(target, key) {
270
- if (!this.activeEffect || !this.activeEffect.active)
271
- return;
272
- let depsMap = this.targetMap.get(target);
273
- if (!depsMap) {
274
- depsMap = new Map;
275
- this.targetMap.set(target, depsMap);
276
- }
277
- let dep = depsMap.get(key);
278
- if (!dep) {
279
- dep = new Set;
280
- depsMap.set(key, dep);
281
- }
282
- if (!dep.has(this.activeEffect)) {
283
- dep.add(this.activeEffect);
284
- this.activeEffect.deps.push(dep);
285
- }
286
- }
287
- trigger(target, key) {
288
- const depsMap = this.targetMap.get(target);
289
- if (!depsMap)
290
- return;
291
- const dep = depsMap.get(key);
292
- if (!dep)
293
- return;
294
- const effects = new Set(dep);
295
- effects.forEach((effect) => {
296
- if (effect.active) {
297
- if (effect.scheduler) {
298
- effect.scheduler(effect);
299
- } else {
300
- effect();
301
- }
302
- }
303
- });
304
- }
305
- stop(effect) {
306
- if (effect.active) {
307
- this.cleanup(effect);
308
- effect.active = false;
309
- }
310
- }
311
- }
312
- function reactive(target) {
313
- return ReactiveSystem.getInstance().reactive(target);
314
- }
315
- function readonly(target) {
316
- return ReactiveSystem.getInstance().readonly(target);
317
- }
318
- function effect(fn, options) {
319
- return ReactiveSystem.getInstance().effect(fn, options);
320
- }
321
- function computed(getter) {
322
- return ReactiveSystem.getInstance().computed(getter);
323
- }
324
- function ref(value) {
325
- const wrapper = { value };
326
- Object.defineProperty(wrapper, IS_REF, {
327
- configurable: false,
328
- enumerable: false,
329
- value: true
330
- });
331
- return reactive(wrapper);
332
- }
333
- function isRef(value) {
334
- return isObject(value) && Boolean(Reflect.get(value, IS_REF));
335
- }
336
- function unref(value) {
337
- return isRef(value) ? value.value : value;
338
- }
339
- function stop(effect2) {
340
- ReactiveSystem.getInstance().stop(effect2);
341
- }
342
- function isReactive(value) {
343
- return isObject(value) && hasReactiveFlag(value, IS_REACTIVE);
344
- }
345
- function isReadonly(value) {
346
- return isObject(value) && hasReactiveFlag(value, IS_READONLY);
347
- }
348
-
349
- // lib/core/vnode.ts
350
- function isComponentNode(vnode) {
351
- return typeof vnode === "object" && vnode !== null && "component" in vnode;
352
- }
353
- function isHTMLNode(vnode) {
354
- return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag !== "slot";
355
- }
356
- function isSlotProvider(vnode) {
357
- return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag === "slot";
358
- }
359
- function h(tag, props, children, listeners, key, directions) {
360
- return {
361
- tag,
362
- props,
363
- children,
364
- listeners,
365
- key,
366
- directions
367
- };
368
- }
369
- function Tag(tag, options = {}) {
370
- return {
371
- tag,
372
- ...options
373
- };
374
- }
375
- function createElementFactory(tag) {
376
- return (options = {}) => Tag(tag, options);
377
- }
378
- var Div = createElementFactory("div");
379
- var Span = createElementFactory("span");
380
- var P = createElementFactory("p");
381
- var Button = createElementFactory("button");
382
- var Input = createElementFactory("input");
383
- var Section = createElementFactory("section");
384
- var Main = createElementFactory("main");
385
- var Header = createElementFactory("header");
386
- var Footer = createElementFactory("footer");
387
- var Nav = createElementFactory("nav");
388
- var Article = createElementFactory("article");
389
- var Aside = createElementFactory("aside");
390
- var H1 = createElementFactory("h1");
391
- var H2 = createElementFactory("h2");
392
- var H3 = createElementFactory("h3");
393
- var H4 = createElementFactory("h4");
394
- var H5 = createElementFactory("h5");
395
- var H6 = createElementFactory("h6");
396
- var Strong = createElementFactory("strong");
397
- var Em = createElementFactory("em");
398
- var Small = createElementFactory("small");
399
- var Pre = createElementFactory("pre");
400
- var Code = createElementFactory("code");
401
- var Blockquote = createElementFactory("blockquote");
402
- var Ul = createElementFactory("ul");
403
- var Ol = createElementFactory("ol");
404
- var Li = createElementFactory("li");
405
- var A = createElementFactory("a");
406
- var Img = createElementFactory("img");
407
- var Form = createElementFactory("form");
408
- var Label = createElementFactory("label");
409
- var Textarea = createElementFactory("textarea");
410
- var Select = createElementFactory("select");
411
- var Option = createElementFactory("option");
412
- var Table = createElementFactory("table");
413
- var Thead = createElementFactory("thead");
414
- var Tbody = createElementFactory("tbody");
415
- var Tr = createElementFactory("tr");
416
- var Th = createElementFactory("th");
417
- var Td = createElementFactory("td");
418
- function createComponent(componentClass, props, children, key, directions) {
419
- return {
420
- component: componentClass,
421
- props,
422
- children,
423
- key,
424
- directions
425
- };
426
- }
427
- function slot(name, key, directions) {
428
- return {
429
- tag: "slot",
430
- props: { name },
431
- key,
432
- directions
433
- };
434
- }
435
- function each(items, render, key) {
436
- return items.map((item, index) => {
437
- const vnode = render(item, index);
438
- if (typeof vnode === "string") {
439
- throw new Error("each render callback must return a VNode");
440
- }
441
- return { ...vnode, key: key(item, index) };
442
- });
443
- }
444
-
445
- // lib/core/model.ts
446
- function pathSegments(path) {
447
- const segments = path.split(".");
448
- if (path.length === 0 || segments.some((segment) => segment.length === 0 || segment === "__proto__" || segment === "prototype" || segment === "constructor")) {
449
- throw new Error(`Invalid model path "${path}"`);
450
- }
451
- return segments;
452
- }
453
- function isRecord(value) {
454
- return typeof value === "object" && value !== null && !Array.isArray(value);
455
- }
456
- function hasOwn(value, key) {
457
- return Object.prototype.hasOwnProperty.call(value, key);
458
- }
459
- function modelPath(binding) {
460
- return typeof binding === "string" ? binding : binding.path;
461
- }
462
- function getModelValue(state, path) {
463
- let value = state;
464
- for (const segment of pathSegments(path)) {
465
- if (!isRecord(value)) {
466
- throw new Error(`Invalid model path "${path}"`);
467
- }
468
- if (!hasOwn(value, segment)) {
469
- if (segment in value) {
470
- throw new Error(`Invalid model path "${path}"`);
471
- }
472
- return;
473
- }
474
- value = value[segment];
475
- }
476
- return value;
477
- }
478
- function setModelValue(state, path, value) {
479
- const segments = pathSegments(path);
480
- let target = state;
481
- for (const segment of segments.slice(0, -1)) {
482
- if (!hasOwn(target, segment)) {
483
- if (segment in target) {
484
- throw new Error(`Invalid model path "${path}"`);
485
- }
486
- target[segment] = {};
487
- } else if (!isRecord(target[segment])) {
488
- throw new Error(`Invalid model path "${path}"`);
489
- }
490
- const nextTarget = target[segment];
491
- if (!isRecord(nextTarget)) {
492
- throw new Error(`Invalid model path "${path}"`);
493
- }
494
- target = nextTarget;
495
- }
496
- const lastSegment = segments[segments.length - 1];
497
- target[lastSegment] = value;
498
- }
499
- function displayValue(binding, value) {
500
- if (typeof binding !== "string" && binding.format) {
501
- return binding.format(value);
502
- }
503
- return value === undefined || value === null ? "" : String(value);
504
- }
505
- function toModelValue(binding, value) {
506
- if (typeof binding !== "string" && binding.parse) {
507
- return binding.parse(value);
508
- }
509
- return value;
510
- }
511
- function syncControl(element, binding, value) {
512
- if (element instanceof HTMLInputElement) {
513
- if (element.type === "checkbox") {
514
- element.checked = Array.isArray(value) ? value.some((item) => String(item) === element.value) : Boolean(value);
515
- return;
516
- }
517
- if (element.type === "radio") {
518
- element.checked = value === element.value;
519
- return;
520
- }
521
- element.value = displayValue(binding, value);
522
- return;
523
- }
524
- if (element instanceof HTMLTextAreaElement) {
525
- element.value = displayValue(binding, value);
526
- return;
527
- }
528
- if (element instanceof HTMLSelectElement) {
529
- if (element.multiple) {
530
- const selected = Array.isArray(value) ? new Set(value.map(String)) : new Set;
531
- for (let index = 0;index < element.options.length; index += 1) {
532
- const option = element.options.item(index);
533
- if (!option) {
534
- continue;
535
- }
536
- option.selected = selected.has(option.value);
537
- }
538
- return;
539
- }
540
- element.value = displayValue(binding, value);
541
- }
542
- }
543
- function controlValue(element, currentValue) {
544
- if (element instanceof HTMLInputElement) {
545
- if (element.type === "checkbox") {
546
- if (Array.isArray(currentValue)) {
547
- const values = currentValue.filter((value) => String(value) !== element.value);
548
- return element.checked ? [...values, element.value] : values;
549
- }
550
- return element.checked;
551
- }
552
- if (element.type === "radio") {
553
- return element.checked ? element.value : currentValue;
554
- }
555
- return element.value;
556
- }
557
- if (element instanceof HTMLTextAreaElement) {
558
- return element.value;
559
- }
560
- if (element instanceof HTMLSelectElement) {
561
- if (!element.multiple) {
562
- return element.value;
563
- }
564
- const values = [];
565
- for (let index = 0;index < element.selectedOptions.length; index += 1) {
566
- const option = element.selectedOptions.item(index);
567
- if (option) {
568
- values.push(option.value);
569
- }
570
- }
571
- return values;
572
- }
573
- return;
574
- }
575
-
576
- class ModelBindingController {
577
- bindings = new WeakMap;
578
- bind(element, binding, state) {
579
- if (!this.isSupportedControl(element)) {
580
- return;
581
- }
582
- const existing = this.bindings.get(element);
583
- if (existing && this.sameBinding(existing, binding)) {
584
- return;
585
- }
586
- this.cleanup(element);
587
- const path = modelPath(binding);
588
- const sync = () => syncControl(element, binding, getModelValue(state, path));
589
- const eventName = element instanceof HTMLTextAreaElement || element instanceof HTMLInputElement && !["checkbox", "radio"].includes(element.type) ? "input" : "change";
590
- const listener = () => {
591
- const currentValue = getModelValue(state, path);
592
- setModelValue(state, path, toModelValue(binding, controlValue(element, currentValue)));
593
- };
594
- element.addEventListener(eventName, listener);
595
- const effectRef = effect(sync);
596
- this.bindings.set(element, {
597
- binding,
598
- path,
599
- parse: typeof binding === "string" ? undefined : binding.parse,
600
- format: typeof binding === "string" ? undefined : binding.format,
601
- eventName,
602
- listener,
603
- effect: effectRef
604
- });
605
- }
606
- cleanup(element) {
607
- const existing = this.bindings.get(element);
608
- if (!existing) {
609
- return;
610
- }
611
- element.removeEventListener(existing.eventName, existing.listener);
612
- stop(existing.effect);
613
- this.bindings.delete(element);
614
- }
615
- isSupportedControl(element) {
616
- return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
617
- }
618
- sameBinding(record, binding) {
619
- if (typeof record.binding === "string" || typeof binding === "string") {
620
- return record.binding === binding;
621
- }
622
- return record.path === binding.path && record.parse === binding.parse && record.format === binding.format;
623
- }
624
- }
625
-
626
- // lib/core/renderer/props.ts
627
- function isEventProp(key) {
628
- return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
629
- }
630
- function eventNameFromProp(key) {
631
- return key.slice(2).toLowerCase();
632
- }
633
- function parseEventName(event) {
634
- const [eventName, ...modifiers] = event.split(".");
635
- return { eventName, modifiers: new Set(modifiers) };
636
- }
637
- function wrapEventHandler(handler, modifiers) {
638
- const eventHandler = (event) => {
639
- if (modifiers.has("stop")) {
640
- event.stopPropagation();
641
- }
642
- if (modifiers.has("prevent")) {
643
- event.preventDefault();
644
- }
645
- if (modifiers.has("self") && event.currentTarget !== event.target) {
646
- return;
647
- }
648
- if (modifiers.has("once")) {
649
- event.currentTarget.removeEventListener(event.type, eventHandler);
650
- }
651
- handler(event);
652
- };
653
- return eventHandler;
654
- }
655
- function setStyleValue(style, property, value) {
656
- const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
657
- style.setProperty(cssProperty, String(value));
658
- }
659
-
660
- // lib/core/renderer/element-strategy.ts
661
- class ElementRenderStrategy {
662
- listeners = new WeakMap;
663
- effects = new WeakMap;
664
- modelBindings = new ModelBindingController;
665
- matches(vnode) {
666
- return typeof vnode === "object" && vnode !== null && isHTMLNode(vnode);
667
- }
668
- mount(vnode, context) {
669
- if (vnode.directions?.if === false) {
670
- return document.createComment("if");
671
- }
672
- const element = document.createElement(vnode.tag);
673
- this.applyProps(element, {}, vnode.props ?? {}, context);
674
- this.updateListeners(element, {}, this.collectListeners(vnode));
675
- this.mountChildren(element, vnode, context);
676
- this.applyDirections(element, undefined, vnode.directions, context);
677
- return element;
678
- }
679
- patch(oldVNode, newVNode, currentNode, context) {
680
- if (oldVNode.tag !== newVNode.tag || currentNode.nodeType === Node.COMMENT_NODE) {
681
- const nextNode = this.mount(newVNode, context);
682
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
683
- this.unmount(oldVNode, currentNode, context);
684
- return nextNode;
685
- }
686
- if (!(currentNode instanceof HTMLElement)) {
687
- return currentNode;
688
- }
689
- if (newVNode.directions?.if === false) {
690
- const nextNode = document.createComment("if");
691
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
692
- this.unmount(oldVNode, currentNode, context);
693
- return nextNode;
694
- }
695
- this.applyProps(currentNode, oldVNode.props ?? {}, newVNode.props ?? {}, context);
696
- this.updateListeners(currentNode, this.collectListeners(oldVNode), this.collectListeners(newVNode));
697
- this.updateChildren(currentNode, oldVNode, newVNode, context);
698
- this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
699
- return currentNode;
700
- }
701
- unmount(vnode, currentNode, context) {
702
- if (!(currentNode instanceof HTMLElement)) {
703
- return;
704
- }
705
- this.effects.get(currentNode)?.forEach((item) => stop(item));
706
- this.effects.delete(currentNode);
707
- this.listeners.get(currentNode)?.forEach(({ eventName, listener }) => {
708
- currentNode.removeEventListener(eventName, listener);
709
- });
710
- this.listeners.delete(currentNode);
711
- this.modelBindings.cleanup(currentNode);
712
- this.unmountChildren(currentNode, vnode, context);
713
- }
714
- mountChildren(element, vnode, context) {
715
- (vnode.children ?? []).forEach((child) => {
716
- element.appendChild(context.renderer.mount(child, context));
717
- });
718
- }
719
- updateChildren(element, oldVNode, newVNode, context) {
720
- this.updateOrdinaryChildren(element, oldVNode.children ?? [], newVNode.children ?? [], context);
721
- }
722
- unmountChildren(element, vnode, context) {
723
- (vnode.children ?? []).forEach((child, index) => {
724
- const childNode = element.childNodes[index];
725
- if (childNode) {
726
- context.renderer.unmount(child, childNode, context);
727
- }
728
- });
729
- }
730
- applyProps(element, oldProps, newProps, context) {
731
- Object.keys(oldProps).forEach((key) => {
732
- if (isEventProp(key) || key in newProps) {
733
- return;
734
- }
735
- if (key === "className" || key === "class") {
736
- element.removeAttribute("class");
737
- } else if (key === "style") {
738
- element.removeAttribute("style");
739
- } else {
740
- element.removeAttribute(key);
741
- }
742
- });
743
- Object.entries(newProps).forEach(([key, value]) => {
744
- if (isEventProp(key)) {
745
- return;
746
- }
747
- if (key === "className" || key === "class") {
748
- element.className = String(value ?? "");
749
- return;
750
- }
751
- if (key === "style" && typeof value === "object" && value !== null) {
752
- element.removeAttribute("style");
753
- Object.entries(value).forEach(([cssKey, cssValue]) => {
754
- setStyleValue(element.style, cssKey, cssValue);
755
- });
756
- return;
757
- }
758
- if (value === false || value === undefined || value === null) {
759
- element.removeAttribute(key);
760
- return;
761
- }
762
- if (value === true) {
763
- element.setAttribute(key, "");
764
- return;
765
- }
766
- if (typeof value === "string" && context.templateEngine.hasExpressions(value)) {
767
- this.setupReactiveAttribute(element, key, value, context);
768
- return;
769
- }
770
- element.setAttribute(key, String(value));
771
- });
772
- }
773
- applyDirections(element, oldDirections, newDirections, context) {
774
- if (newDirections && "show" in newDirections) {
775
- element.style.display = newDirections.show ? "" : "none";
776
- } else if (oldDirections && "show" in oldDirections) {
777
- element.style.display = "";
778
- }
779
- if (!newDirections?.model) {
780
- this.modelBindings.cleanup(element);
781
- return;
782
- }
783
- this.modelBindings.bind(element, newDirections.model, context.templateEngine.state);
784
- }
785
- updateOrdinaryChildren(element, oldChildren, newChildren, context) {
786
- this.assertNoDuplicateKeys(oldChildren);
787
- this.assertNoDuplicateKeys(newChildren);
788
- if (this.hasOnlyKeyedChildren(oldChildren, newChildren)) {
789
- this.updateKeyedChildren(element, oldChildren, newChildren, context);
790
- return;
791
- }
792
- const sharedLength = Math.min(oldChildren.length, newChildren.length);
793
- for (let index = 0;index < sharedLength; index += 1) {
794
- const childNode = element.childNodes[index];
795
- if (!childNode) {
796
- element.appendChild(context.renderer.mount(newChildren[index], context));
797
- continue;
798
- }
799
- context.renderer.patch(oldChildren[index], newChildren[index], childNode, context);
800
- }
801
- for (let index = sharedLength;index < newChildren.length; index += 1) {
802
- element.appendChild(context.renderer.mount(newChildren[index], context));
803
- }
804
- for (let index = oldChildren.length - 1;index >= newChildren.length; index -= 1) {
805
- const childNode = element.childNodes[index];
806
- if (childNode) {
807
- context.renderer.unmount(oldChildren[index], childNode, context);
808
- if (childNode.parentNode === element) {
809
- element.removeChild(childNode);
810
- }
811
- }
812
- }
813
- }
814
- updateKeyedChildren(element, oldChildren, newChildren, context) {
815
- const oldEntries = oldChildren.map((vnode, index) => ({
816
- vnode,
817
- node: element.childNodes[index],
818
- index
819
- }));
820
- const keyedOldEntries = new Map;
821
- const usedOldIndexes = new Set;
822
- oldEntries.forEach((entry) => {
823
- const key = this.getVNodeKey(entry.vnode);
824
- if (key !== undefined && entry.node) {
825
- keyedOldEntries.set(key, {
826
- vnode: entry.vnode,
827
- node: entry.node,
828
- index: entry.index
829
- });
830
- }
831
- });
832
- newChildren.forEach((newChild, newIndex) => {
833
- const key = this.getVNodeKey(newChild);
834
- const oldEntry = key === undefined ? undefined : keyedOldEntries.get(key);
835
- let nextNode;
836
- if (oldEntry) {
837
- nextNode = context.renderer.patch(oldEntry.vnode, newChild, oldEntry.node, context);
838
- usedOldIndexes.add(oldEntry.index);
839
- } else {
840
- nextNode = context.renderer.mount(newChild, context);
841
- }
842
- const referenceNode = element.childNodes[newIndex] ?? null;
843
- if (nextNode !== referenceNode) {
844
- element.insertBefore(nextNode, referenceNode);
845
- }
846
- });
847
- oldEntries.forEach((entry) => {
848
- if (!entry.node || usedOldIndexes.has(entry.index)) {
849
- return;
850
- }
851
- context.renderer.unmount(entry.vnode, entry.node, context);
852
- if (entry.node.parentNode === element) {
853
- element.removeChild(entry.node);
854
- }
855
- });
856
- }
857
- hasOnlyKeyedChildren(oldChildren, newChildren) {
858
- return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
859
- }
860
- assertNoDuplicateKeys(children) {
861
- const keys = new Set;
862
- children.forEach((child) => {
863
- const key = this.getVNodeKey(child);
864
- if (key === undefined) {
865
- return;
866
- }
867
- if (keys.has(key)) {
868
- throw new Error(`Duplicate key "${key}"`);
869
- }
870
- keys.add(key);
871
- });
872
- }
873
- getVNodeKey(vnode) {
874
- if (typeof vnode === "string") {
875
- return;
876
- }
877
- return vnode.key;
878
- }
879
- collectListeners(vnode) {
880
- const listeners = {};
881
- Object.entries(vnode.props ?? {}).forEach(([key, value]) => {
882
- if (isEventProp(key) && typeof value === "function") {
883
- listeners[eventNameFromProp(key)] = value;
884
- }
885
- });
886
- return {
887
- ...listeners,
888
- ...vnode.listeners ?? {}
889
- };
890
- }
891
- updateListeners(element, oldListeners, newListeners) {
892
- const store = this.listeners.get(element) ?? new Map;
893
- const oldKeys = new Set(Object.keys(oldListeners));
894
- const newKeys = new Set(Object.keys(newListeners));
895
- oldKeys.forEach((event) => {
896
- if (!newKeys.has(event) || oldListeners[event] !== newListeners[event]) {
897
- const stored = store.get(event);
898
- if (stored) {
899
- element.removeEventListener(stored.eventName, stored.listener);
900
- store.delete(event);
901
- }
902
- }
903
- });
904
- newKeys.forEach((event) => {
905
- if (!oldKeys.has(event) || oldListeners[event] !== newListeners[event]) {
906
- const { eventName, modifiers } = parseEventName(event);
907
- const listener = wrapEventHandler(newListeners[event], modifiers);
908
- element.addEventListener(eventName, listener);
909
- store.set(event, { eventName, listener });
910
- }
911
- });
912
- this.listeners.set(element, store);
913
- }
914
- setupReactiveAttribute(element, attrName, attrValue, context) {
915
- const effectRef = effect(() => {
916
- element.setAttribute(attrName, context.templateEngine.evaluateTemplateValue(attrValue));
917
- });
918
- this.trackEffect(element, effectRef);
919
- }
920
- trackEffect(element, effectRef) {
921
- const effects = this.effects.get(element) ?? new Set;
922
- effects.add(effectRef);
923
- this.effects.set(element, effects);
924
- }
925
- }
926
-
927
- // lib/core/animation/list-animation-controller.ts
928
- var ENTER_KEYFRAMES = {
929
- fade: [{ opacity: 0 }, { opacity: 1 }],
930
- "slide-up": [
931
- { opacity: 0, transform: "translateY(12px)" },
932
- { opacity: 1, transform: "translateY(0)" }
933
- ],
934
- "slide-down": [
935
- { opacity: 0, transform: "translateY(-12px)" },
936
- { opacity: 1, transform: "translateY(0)" }
937
- ],
938
- "slide-left": [
939
- { opacity: 0, transform: "translateX(12px)" },
940
- { opacity: 1, transform: "translateX(0)" }
941
- ],
942
- "slide-right": [
943
- { opacity: 0, transform: "translateX(-12px)" },
944
- { opacity: 1, transform: "translateX(0)" }
945
- ],
946
- scale: [
947
- { opacity: 0, transform: "scale(0.95)" },
948
- { opacity: 1, transform: "scale(1)" }
949
- ]
950
- };
951
-
952
- class ListAnimationController {
953
- runs = new WeakMap;
954
- playEnter(element, options) {
955
- return this.play(element, options, "enter");
956
- }
957
- playExit(element, options) {
958
- return this.play(element, options, "exit");
959
- }
960
- cancel(element) {
961
- const current = this.runs.get(element);
962
- if (!current) {
963
- return;
964
- }
965
- this.runs.delete(element);
966
- current.animation.cancel();
967
- }
968
- play(element, transition, phase) {
969
- this.cancel(element);
970
- if (!this.canAnimate(element)) {
971
- return null;
972
- }
973
- const enterKeyframes = ENTER_KEYFRAMES[transition.type];
974
- const keyframes = phase === "enter" ? [...enterKeyframes] : [...enterKeyframes].reverse();
975
- const options = {
976
- duration: transition.duration,
977
- easing: "ease",
978
- fill: "both"
979
- };
980
- const animation = element.animate(keyframes, options);
981
- const token = Symbol("list-animation");
982
- const finished = animation.finished.then(() => "finished", () => "cancelled");
983
- const run = {
984
- animation,
985
- token,
986
- keyframes,
987
- options,
988
- finished
989
- };
990
- this.runs.set(element, run);
991
- finished.then((result) => {
992
- if (this.runs.get(element)?.token !== token) {
993
- return;
994
- }
995
- this.runs.delete(element);
996
- if (phase === "enter" && result === "finished") {
997
- animation.cancel();
998
- }
999
- });
1000
- return run;
1001
- }
1002
- canAnimate(element) {
1003
- const reduced = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1004
- return !reduced && typeof element.animate === "function";
1005
- }
1006
- }
1007
-
1008
- // lib/core/animation/types.ts
1009
- var TRANSITION_ANIMATION_TYPES = [
1010
- "fade",
1011
- "slide-up",
1012
- "slide-down",
1013
- "slide-left",
1014
- "slide-right",
1015
- "scale"
1016
- ];
1017
- function normalizeTransitionGroupProps(props) {
1018
- const tag = (props.tag ?? "div").trim();
1019
- const type = props.type ?? "fade";
1020
- const duration = props.duration ?? 300;
1021
- if (!tag) {
1022
- throw new Error("TransitionGroup tag must not be empty");
1023
- }
1024
- if (!TRANSITION_ANIMATION_TYPES.includes(type)) {
1025
- throw new Error(`Unknown TransitionGroup animation type "${type}"`);
1026
- }
1027
- if (!Number.isFinite(duration) || duration < 0) {
1028
- throw new Error("TransitionGroup duration must be a non-negative finite number");
1029
- }
1030
- return { tag, type, duration };
1031
- }
1032
- function validateTransitionGroupChildren(children) {
1033
- const keys = new Set;
1034
- return children.map((child) => {
1035
- if (typeof child === "string" || child.key === undefined || keys.has(child.key)) {
1036
- throw new Error("TransitionGroup children must have unique keys");
1037
- }
1038
- keys.add(child.key);
1039
- return child;
1040
- });
1041
- }
1042
- function isTransitionGroupNode(vnode) {
1043
- return typeof vnode === "object" && vnode !== null && "transitionGroup" in vnode && isHTMLNode(vnode);
1044
- }
1045
-
1046
- // lib/core/animation/transition-group-strategy.ts
1047
- class TransitionGroupRenderStrategy extends ElementRenderStrategy {
1048
- entries = new WeakMap;
1049
- animations = new ListAnimationController;
1050
- matches(vnode) {
1051
- return isTransitionGroupNode(vnode);
1052
- }
1053
- mountChildren(element, groupVNode, context) {
1054
- const keyedChildren = validateTransitionGroupChildren(groupVNode.children ?? []);
1055
- const entries = new Map;
1056
- keyedChildren.forEach((childVNode) => {
1057
- const node = context.renderer.mount(childVNode, context);
1058
- element.appendChild(node);
1059
- const entry = {
1060
- key: childVNode.key,
1061
- vnode: childVNode,
1062
- node,
1063
- status: "active"
1064
- };
1065
- entries.set(entry.key, entry);
1066
- this.playEnter(entry, node, groupVNode.transitionGroup);
1067
- });
1068
- this.entries.set(element, entries);
1069
- }
1070
- updateChildren(element, _oldVNode, newVNode, context) {
1071
- const entries = this.entries.get(element) ?? new Map;
1072
- const nextChildren = validateTransitionGroupChildren(newVNode.children ?? []);
1073
- const nextKeys = new Set(nextChildren.map((child) => child.key));
1074
- const ordered = [];
1075
- nextChildren.forEach((childVNode) => {
1076
- const key = childVNode.key;
1077
- const current = entries.get(key);
1078
- if (current) {
1079
- const wasExiting = current.status === "exiting";
1080
- if (wasExiting && current.node instanceof HTMLElement) {
1081
- this.animations.cancel(current.node);
1082
- }
1083
- current.status = "active";
1084
- current.animationToken = undefined;
1085
- current.node = context.renderer.patch(current.vnode, childVNode, current.node, context);
1086
- current.vnode = childVNode;
1087
- ordered.push(current);
1088
- if (wasExiting) {
1089
- this.playEnter(current, current.node, newVNode.transitionGroup);
1090
- }
1091
- return;
1092
- }
1093
- const node = context.renderer.mount(childVNode, context);
1094
- const entry = {
1095
- key,
1096
- vnode: childVNode,
1097
- node,
1098
- status: "active"
1099
- };
1100
- entries.set(key, entry);
1101
- ordered.push(entry);
1102
- this.playEnter(entry, node, newVNode.transitionGroup);
1103
- });
1104
- entries.forEach((entry, key) => {
1105
- if (nextKeys.has(key) || entry.status !== "active") {
1106
- return;
1107
- }
1108
- this.startExit(element, entry, newVNode.transitionGroup, context);
1109
- });
1110
- this.placeActiveEntries(element, ordered);
1111
- this.entries.set(element, entries);
1112
- }
1113
- unmountChildren(element, vnode, context) {
1114
- const entries = this.entries.get(element);
1115
- if (!entries) {
1116
- super.unmountChildren(element, vnode, context);
1117
- return;
1118
- }
1119
- entries.forEach((entry) => {
1120
- if (entry.node instanceof HTMLElement) {
1121
- this.animations.cancel(entry.node);
1122
- }
1123
- context.renderer.unmount(entry.vnode, entry.node, context);
1124
- if (entry.node.parentNode === element) {
1125
- element.removeChild(entry.node);
1126
- }
1127
- });
1128
- entries.clear();
1129
- this.entries.delete(element);
1130
- }
1131
- playEnter(entry, node, options) {
1132
- if (!(node instanceof HTMLElement)) {
1133
- return;
1134
- }
1135
- const run = this.animations.playEnter(node, options);
1136
- entry.animationToken = run?.token;
1137
- }
1138
- startExit(wrapper, entry, options, context) {
1139
- entry.status = "exiting";
1140
- const run = entry.node instanceof HTMLElement ? this.animations.playExit(entry.node, options) : null;
1141
- if (!run) {
1142
- this.finishExit(wrapper, entry, context);
1143
- return;
1144
- }
1145
- entry.animationToken = run.token;
1146
- run.finished.then((result) => {
1147
- if (result === "finished" && entry.status === "exiting" && entry.animationToken === run.token) {
1148
- this.finishExit(wrapper, entry, context);
1149
- }
1150
- });
1151
- }
1152
- finishExit(wrapper, entry, context) {
1153
- const entries = this.entries.get(wrapper);
1154
- if (entries?.get(entry.key) !== entry) {
1155
- return;
1156
- }
1157
- context.renderer.unmount(entry.vnode, entry.node, context);
1158
- if (entry.node.parentNode === wrapper) {
1159
- wrapper.removeChild(entry.node);
1160
- }
1161
- entries.delete(entry.key);
1162
- }
1163
- placeActiveEntries(wrapper, ordered) {
1164
- let reference = null;
1165
- for (let index = ordered.length - 1;index >= 0; index -= 1) {
1166
- wrapper.insertBefore(ordered[index].node, reference);
1167
- reference = ordered[index].node;
1168
- }
1169
- }
1170
- }
1171
-
1172
- // lib/core/renderer.ts
1173
- class RendererContext {
1174
- strategies;
1175
- constructor() {
1176
- this.strategies = [
1177
- new TextRenderStrategy,
1178
- new ComponentRenderStrategy,
1179
- new SlotRenderStrategy,
1180
- new TransitionGroupRenderStrategy,
1181
- new ElementRenderStrategy
1182
- ];
1183
- }
1184
- mount(vnode, context) {
1185
- return this.findStrategy(vnode).mount(vnode, context);
1186
- }
1187
- patch(oldVNode, newVNode, currentNode, context) {
1188
- const oldStrategy = this.findStrategy(oldVNode);
1189
- const newStrategy = this.findStrategy(newVNode);
1190
- if (oldStrategy !== newStrategy) {
1191
- const nextNode = newStrategy.mount(newVNode, context);
1192
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
1193
- oldStrategy.unmount(oldVNode, currentNode, context);
1194
- return nextNode;
1195
- }
1196
- return oldStrategy.patch(oldVNode, newVNode, currentNode, context);
1197
- }
1198
- unmount(vnode, currentNode, context) {
1199
- this.findStrategy(vnode).unmount(vnode, currentNode, context);
1200
- }
1201
- findStrategy(vnode) {
1202
- const strategy = this.strategies.find((item) => item.matches(vnode));
1203
- if (!strategy) {
1204
- throw new Error("No render strategy found for vnode");
1205
- }
1206
- return strategy;
1207
- }
1208
- }
1209
-
1210
- class TextRenderStrategy {
1211
- matches(vnode) {
1212
- return typeof vnode === "string";
1213
- }
1214
- mount(vnode, context) {
1215
- return context.templateEngine.parseTemplate(vnode);
1216
- }
1217
- patch(oldVNode, newVNode, currentNode, context) {
1218
- if (oldVNode === newVNode) {
1219
- return currentNode;
1220
- }
1221
- const nextNode = this.mount(newVNode, context);
1222
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
1223
- return nextNode;
1224
- }
1225
- unmount() {}
1226
- }
1227
-
1228
- class ComponentRenderStrategy {
1229
- instances = new WeakMap;
1230
- instanceNodes = new Map;
1231
- emitterUnsubscribers = new WeakMap;
1232
- matches(vnode) {
1233
- return typeof vnode === "object" && vnode !== null && isComponentNode(vnode);
1234
- }
1235
- mount(vnode, context) {
1236
- if (vnode.directions?.if === false) {
1237
- return document.createComment("if");
1238
- }
1239
- const ComponentClass = vnode.component;
1240
- const instance = new ComponentClass(this.createProps(vnode));
1241
- if (context.appContext && instance.setAppContext) {
1242
- instance.setAppContext(context.appContext);
1243
- }
1244
- this.syncEmitters(instance, vnode.emitters ?? {});
1245
- context.registerChild(instance);
1246
- const node = instance.mountToNode();
1247
- this.trackInstanceNode(instance, node);
1248
- instance.setElementChangeListener?.((previousNode, nextNode) => {
1249
- this.trackInstanceNode(instance, previousNode);
1250
- this.trackInstanceNode(instance, nextNode);
1251
- });
1252
- return node;
1253
- }
1254
- patch(oldVNode, newVNode, currentNode, context) {
1255
- if (currentNode.nodeType === Node.COMMENT_NODE) {
1256
- const nextNode2 = this.mount(newVNode, context);
1257
- currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1258
- return nextNode2;
1259
- }
1260
- if (newVNode.directions?.if === false) {
1261
- const nextNode2 = document.createComment("if");
1262
- currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1263
- this.unmount(oldVNode, currentNode, context);
1264
- return nextNode2;
1265
- }
1266
- const instance = this.instances.get(currentNode);
1267
- if (instance && oldVNode.component === newVNode.component) {
1268
- this.syncEmitters(instance, newVNode.emitters ?? {});
1269
- instance.setProps(this.createProps(newVNode));
1270
- const nextNode2 = instance.getElement() ?? currentNode;
1271
- this.trackInstanceNode(instance, nextNode2);
1272
- return nextNode2;
1273
- }
1274
- const nextNode = this.mount(newVNode, context);
1275
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
1276
- this.unmount(oldVNode, currentNode, context);
1277
- return nextNode;
1278
- }
1279
- unmount(_vnode, currentNode, context) {
1280
- const instance = this.instances.get(currentNode);
1281
- if (instance) {
1282
- this.clearEmitters(instance);
1283
- instance.unmount();
1284
- this.clearInstanceNodes(instance);
1285
- context.unregisterChild(instance);
1286
- }
1287
- }
1288
- createProps(vnode) {
1289
- return {
1290
- ...vnode.props ?? {},
1291
- children: vnode.children ?? []
1292
- };
1293
- }
1294
- syncEmitters(instance, emitters) {
1295
- const current = this.emitterUnsubscribers.get(instance) ?? new Map;
1296
- current.forEach(({ listener: currentListener, unsubscribe }, eventName) => {
1297
- const listener = emitters[eventName];
1298
- if (!listener || listener !== currentListener) {
1299
- unsubscribe();
1300
- current.delete(eventName);
1301
- }
1302
- });
1303
- Object.entries(emitters).forEach(([eventName, listener]) => {
1304
- if (current.get(eventName)?.listener === listener) {
1305
- return;
1306
- }
1307
- current.set(eventName, {
1308
- listener,
1309
- unsubscribe: instance.on(eventName, listener)
1310
- });
1311
- });
1312
- this.emitterUnsubscribers.set(instance, current);
1313
- }
1314
- clearEmitters(instance) {
1315
- this.emitterUnsubscribers.get(instance)?.forEach(({ unsubscribe }) => {
1316
- unsubscribe();
1317
- });
1318
- this.emitterUnsubscribers.delete(instance);
1319
- }
1320
- trackInstanceNode(instance, node) {
1321
- this.instances.set(node, instance);
1322
- const nodes = this.instanceNodes.get(instance) ?? new Set;
1323
- nodes.add(node);
1324
- this.instanceNodes.set(instance, nodes);
1325
- }
1326
- clearInstanceNodes(instance) {
1327
- this.instanceNodes.get(instance)?.forEach((node) => {
1328
- this.instances.delete(node);
1329
- });
1330
- this.instanceNodes.delete(instance);
1331
- }
1332
- }
1333
-
1334
- class SlotRenderStrategy {
1335
- renderedChildren = new WeakMap;
1336
- matches(vnode) {
1337
- return typeof vnode === "object" && vnode !== null && isSlotProvider(vnode);
1338
- }
1339
- mount(vnode, context) {
1340
- if (vnode.directions?.if === false) {
1341
- return document.createComment("if");
1342
- }
1343
- const slotContainer = document.createElement("div");
1344
- slotContainer.setAttribute("data-slot", vnode.props.name);
1345
- this.mountSlotChildren(slotContainer, this.resolveChildren(vnode, context), context);
1346
- return slotContainer;
1347
- }
1348
- patch(oldVNode, newVNode, currentNode, context) {
1349
- if (currentNode.nodeType === Node.COMMENT_NODE) {
1350
- const nextNode = this.mount(newVNode, context);
1351
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
1352
- return nextNode;
1353
- }
1354
- if (newVNode.directions?.if === false) {
1355
- const nextNode = document.createComment("if");
1356
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
1357
- this.unmount(oldVNode, currentNode, context);
1358
- return nextNode;
1359
- }
1360
- if (currentNode instanceof HTMLElement) {
1361
- currentNode.setAttribute("data-slot", newVNode.props.name);
1362
- this.replaceSlotChildren(currentNode, oldVNode, newVNode, context);
1363
- }
1364
- return currentNode;
1365
- }
1366
- unmount(_vnode, currentNode, context) {
1367
- if (!(currentNode instanceof HTMLElement)) {
1368
- return;
1369
- }
1370
- this.unmountSlotChildren(currentNode, context);
1371
- this.renderedChildren.delete(currentNode);
1372
- }
1373
- resolveChildren(vnode, context) {
1374
- return context.slots[vnode.props.name] ?? vnode.children ?? [];
1375
- }
1376
- replaceSlotChildren(element, _oldVNode, newVNode, context) {
1377
- this.unmountSlotChildren(element, context);
1378
- element.textContent = "";
1379
- this.mountSlotChildren(element, this.resolveChildren(newVNode, context), context);
1380
- }
1381
- mountSlotChildren(element, children, context) {
1382
- children.forEach((child) => {
1383
- element.appendChild(context.renderer.mount(child, context));
1384
- });
1385
- this.renderedChildren.set(element, children);
1386
- }
1387
- unmountSlotChildren(element, context) {
1388
- const children = this.renderedChildren.get(element) ?? [];
1389
- children.forEach((child, index) => {
1390
- const childNode = element.childNodes[index];
1391
- if (childNode) {
1392
- context.renderer.unmount(child, childNode, context);
1393
- }
1394
- });
1395
- }
1396
- }
1397
-
1398
- // lib/core/template.ts
1399
- class TemplateEngine {
1400
- state;
1401
- bindings = [];
1402
- templateRegex = /{{(.*?)}}/g;
1403
- constructor(state) {
1404
- this.state = state;
1405
- if (!state || typeof state !== "object") {
1406
- throw new Error("TemplateEngine requires a valid state object");
1407
- }
1408
- }
1409
- parseTemplate(text) {
1410
- if (typeof text !== "string") {
1411
- text = String(text);
1412
- }
1413
- const textNode = document.createTextNode("");
1414
- this.templateRegex.lastIndex = 0;
1415
- const matches = Array.from(text.matchAll(this.templateRegex));
1416
- if (matches && matches.length > 0) {
1417
- this.setupReactiveBindings(textNode, text, matches);
1418
- } else {
1419
- textNode.textContent = text;
1420
- }
1421
- return textNode;
1422
- }
1423
- setupReactiveBindings(node, originalText, matches) {
1424
- const keys = new Set;
1425
- const initialText = this.evaluateTemplate(originalText, matches, keys);
1426
- node.textContent = initialText;
1427
- const effectFn = effect(() => {
1428
- try {
1429
- const updatedText = this.evaluateTemplate(originalText, matches, keys);
1430
- if (node.textContent !== updatedText) {
1431
- node.textContent = updatedText;
1432
- }
1433
- } catch (error) {
1434
- console.error("Template update error:", error);
1435
- node.textContent = `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
1436
- }
1437
- });
1438
- this.bindings.push({
1439
- node,
1440
- originalText,
1441
- effect: effectFn
1442
- });
1443
- }
1444
- evaluateTemplate(text, matches, keys) {
1445
- let result = text;
1446
- matches.forEach((match) => {
1447
- const key = match[1]?.trim();
1448
- if (key) {
1449
- keys.add(key);
1450
- const value = this.getValueFromState(key);
1451
- const displayValue2 = value === undefined || value === null ? "" : String(value);
1452
- result = result.replace(match[0], displayValue2);
1453
- }
1454
- });
1455
- return result;
1456
- }
1457
- getValueFromState(keyPath) {
1458
- if (!keyPath)
1459
- return;
1460
- const keys = keyPath.split(".");
1461
- let value = this.state;
1462
- for (const key of keys) {
1463
- if (!value || typeof value !== "object") {
1464
- return;
1465
- }
1466
- value = value[key];
1467
- }
1468
- return value;
1469
- }
1470
- clearBindings() {
1471
- this.bindings.forEach((binding) => {
1472
- stop(binding.effect);
1473
- });
1474
- this.bindings = [];
1475
- }
1476
- getBindingCount() {
1477
- return this.bindings.length;
1478
- }
1479
- hasExpressions(text) {
1480
- this.templateRegex.lastIndex = 0;
1481
- return this.templateRegex.test(text);
1482
- }
1483
- extractKeys(text) {
1484
- const keys = [];
1485
- let match;
1486
- const regex = new RegExp(this.templateRegex, "g");
1487
- while ((match = regex.exec(text)) !== null) {
1488
- const key = match[1]?.trim();
1489
- if (key) {
1490
- keys.push(key);
1491
- }
1492
- }
1493
- return keys;
1494
- }
1495
- evaluateTemplateValue(text) {
1496
- this.templateRegex.lastIndex = 0;
1497
- const matches = Array.from(text.matchAll(this.templateRegex));
1498
- if (!matches || matches.length === 0) {
1499
- return text;
1500
- }
1501
- const keys = new Set;
1502
- return this.evaluateTemplate(text, matches, keys);
1503
- }
1504
- }
1505
-
1506
- // lib/core/component/base.ts
1507
- class Component {
1508
- props;
1509
- vnode = null;
1510
- el = null;
1511
- renderer = new RendererContext;
1512
- templateEngine;
1513
- childComponents = new Set;
1514
- eventListeners = {};
1515
- providers = new Map;
1516
- updateEffect;
1517
- appContext = null;
1518
- parentComponent = null;
1519
- elementChangeListener = null;
1520
- styleManager;
1521
- state;
1522
- mounted = false;
1523
- constructor(props = {}) {
1524
- this.props = props;
1525
- this.styleManager = new StyleManager;
1526
- this.state = reactive(this.initState() ?? {});
1527
- this.templateEngine = new TemplateEngine(this.state);
1528
- this.initStyles();
1529
- this.updateEffect = effect(() => {
1530
- this.trackStateProperties();
1531
- if (this.mounted) {
1532
- this.update();
1533
- }
1534
- }, { throwOnError: true });
1535
- }
1536
- mount(container) {
1537
- if (!container || !(container instanceof HTMLElement)) {
1538
- throw new Error("Invalid container element");
1539
- }
1540
- try {
1541
- container.appendChild(this.mountToNode());
1542
- } catch (error) {
1543
- console.error("组件渲染错误:", error);
1544
- throw error;
1545
- }
1546
- }
1547
- mountToNode() {
1548
- if (this.mounted && this.el) {
1549
- return this.el;
1550
- }
1551
- this.beforeMount();
1552
- this.vnode = this.render();
1553
- this.el = this.renderer.mount(this.vnode, this.createRenderContext());
1554
- this.mounted = true;
1555
- this.onMounted();
1556
- return this.el;
1557
- }
1558
- update() {
1559
- if (!this.el || !this.vnode) {
1560
- return;
1561
- }
1562
- this.beforeUpdate();
1563
- const newVNode = this.render();
1564
- const previousElement = this.el;
1565
- this.el = this.renderer.patch(this.vnode, newVNode, this.el, this.createRenderContext());
1566
- if (previousElement !== this.el) {
1567
- this.elementChangeListener?.(previousElement, this.el);
1568
- }
1569
- this.vnode = newVNode;
1570
- this.onUpdated();
1571
- }
1572
- unmount() {
1573
- if (!this.mounted) {
1574
- return;
1575
- }
1576
- this.beforeUnmount();
1577
- if (this.vnode && this.el) {
1578
- this.renderer.unmount(this.vnode, this.el, this.createRenderContext());
1579
- }
1580
- this.childComponents.clear();
1581
- Object.keys(this.eventListeners).forEach((eventName) => {
1582
- this.eventListeners[eventName].clear();
1583
- delete this.eventListeners[eventName];
1584
- });
1585
- this.providers.clear();
1586
- this.parentComponent = null;
1587
- this.elementChangeListener = null;
1588
- this.templateEngine.clearBindings();
1589
- this.styleManager.destroy();
1590
- stop(this.updateEffect);
1591
- if (this.el?.parentNode) {
1592
- this.el.parentNode.removeChild(this.el);
1593
- }
1594
- this.el = null;
1595
- this.vnode = null;
1596
- this.mounted = false;
1597
- this.onUnmounted();
1598
- }
1599
- setProps(props) {
1600
- this.props = {
1601
- ...this.props,
1602
- ...props
1603
- };
1604
- if (this.mounted) {
1605
- this.update();
1606
- }
1607
- }
1608
- setState(state) {
1609
- Object.assign(this.state, state);
1610
- }
1611
- setAppContext(context) {
1612
- this.appContext = context;
1613
- this.childComponents.forEach((child) => {
1614
- child.setAppContext?.(context);
1615
- });
1616
- }
1617
- setParentComponent(parent) {
1618
- this.parentComponent = parent;
1619
- }
1620
- setElementChangeListener(listener) {
1621
- this.elementChangeListener = listener;
1622
- }
1623
- provide(key, value) {
1624
- this.providers.set(key, value);
1625
- }
1626
- inject(key, fallback) {
1627
- const result = this.resolveInjection(key);
1628
- return result.found ? result.value : fallback;
1629
- }
1630
- resolveInjection(key) {
1631
- if (this.providers.has(key)) {
1632
- return { found: true, value: this.providers.get(key) };
1633
- }
1634
- if (this.parentComponent?.resolveInjection) {
1635
- return this.parentComponent.resolveInjection(key);
1636
- }
1637
- return this.resolveAppInjection(key);
1638
- }
1639
- getElement() {
1640
- return this.el;
1641
- }
1642
- beforeMount() {}
1643
- onMounted() {}
1644
- beforeUpdate() {}
1645
- onUpdated() {}
1646
- beforeUnmount() {}
1647
- onUnmounted() {}
1648
- getContext() {
1649
- return this.appContext;
1650
- }
1651
- get router() {
1652
- return this.getRouterFrom(this.appContext) ?? this.getRouterFromGlobalApp();
1653
- }
1654
- emit(eventName, ...args) {
1655
- this.eventListeners[eventName]?.forEach((listener) => {
1656
- listener(...args);
1657
- });
1658
- }
1659
- on(eventName, listener) {
1660
- if (!this.eventListeners[eventName]) {
1661
- this.eventListeners[eventName] = new Set;
1662
- }
1663
- this.eventListeners[eventName].add(listener);
1664
- return () => this.off(eventName, listener);
1665
- }
1666
- off(eventName, listener) {
1667
- this.eventListeners[eventName]?.delete(listener);
1668
- }
1669
- createRenderContext() {
1670
- return {
1671
- appContext: this.appContext,
1672
- templateEngine: this.templateEngine,
1673
- renderer: this.renderer,
1674
- slots: this.collectSlots(),
1675
- registerChild: (component) => {
1676
- this.childComponents.add(component);
1677
- component.setParentComponent?.(this);
1678
- component.setAppContext?.(this.appContext);
1679
- },
1680
- unregisterChild: (component) => {
1681
- this.childComponents.delete(component);
1682
- component.setParentComponent?.(null);
1683
- }
1684
- };
1685
- }
1686
- collectSlots() {
1687
- const slots = { default: [] };
1688
- const children = this.props.children ?? [];
1689
- children.forEach((child) => {
1690
- const slotName = this.getSlotName(child);
1691
- if (!slots[slotName]) {
1692
- slots[slotName] = [];
1693
- }
1694
- slots[slotName].push(this.normalizeSlotChild(child));
1695
- });
1696
- return slots;
1697
- }
1698
- getSlotName(child) {
1699
- if (typeof child === "string") {
1700
- return "default";
1701
- }
1702
- return "slot" in child && typeof child.slot === "string" ? child.slot : "default";
1703
- }
1704
- normalizeSlotChild(child) {
1705
- if (typeof child === "string" || !("slot" in child)) {
1706
- return child;
1707
- }
1708
- const clone = { ...child };
1709
- delete clone.slot;
1710
- return clone;
1711
- }
1712
- trackStateProperties() {
1713
- this.trackReactiveValue(this.state, new Set);
1714
- }
1715
- getRouterFrom(value) {
1716
- if (!value || typeof value !== "object" || !("router" in value)) {
1717
- return;
1718
- }
1719
- return value.router;
1720
- }
1721
- getRouterFromGlobalApp() {
1722
- const globalApp = globalThis.__APP__;
1723
- return this.getRouterFrom(globalApp);
1724
- }
1725
- resolveAppInjection(key) {
1726
- if (!this.appContext || typeof this.appContext !== "object") {
1727
- return { found: false, value: undefined };
1728
- }
1729
- const app = this.appContext.app;
1730
- return app?.resolveInjection?.(key) ?? { found: false, value: undefined };
1731
- }
1732
- trackReactiveValue(value, seen) {
1733
- if (!value || typeof value !== "object" || seen.has(value)) {
1734
- return;
1735
- }
1736
- seen.add(value);
1737
- if (Array.isArray(value)) {
1738
- value.length;
1739
- }
1740
- Object.keys(value).forEach((key) => {
1741
- const child = value[key];
1742
- this.trackReactiveValue(child, seen);
1743
- });
1744
- }
1745
- }
1746
- // lib/router/instance.ts
1747
- var router = null;
1748
- function setRouter(r) {
1749
- if (!r || !(r instanceof Router)) {
1750
- throw new Error("Invalid router instance");
1751
- }
1752
- router = r;
1753
- }
1754
- function useRouter() {
1755
- if (!router) {
1756
- throw new Error("Router is not initialized. Please make sure you have installed the router plugin.");
1757
- }
1758
- return router;
1759
- }
1760
-
1761
- // lib/router/matcher.ts
1762
- function normalizePath(path) {
1763
- if (!path.startsWith("/")) {
1764
- return `/${path}`;
1765
- }
1766
- return path || "/";
1767
- }
1768
- function matchRoute(routes, path) {
1769
- const normalizedPath = normalizePath(path.split("?")[0]);
1770
- for (const route of routes) {
1771
- const params = matchRoutePath(route.path, normalizedPath);
1772
- if (params) {
1773
- return { route, params };
1774
- }
1775
- }
1776
- const fallback = routes.find((route) => route.path === "/");
1777
- return fallback ? { route: fallback, params: {} } : null;
1778
- }
1779
- function matchRoutePath(routePath, currentPath) {
1780
- const routeSegments = getPathSegments(routePath);
1781
- const currentSegments = getPathSegments(currentPath);
1782
- if (routeSegments.length !== currentSegments.length) {
1783
- return null;
1784
- }
1785
- const params = {};
1786
- for (let index = 0;index < routeSegments.length; index += 1) {
1787
- const routeSegment = routeSegments[index];
1788
- const currentSegment = currentSegments[index];
1789
- if (routeSegment.startsWith(":")) {
1790
- const paramName = routeSegment.slice(1);
1791
- if (!paramName) {
1792
- return null;
1793
- }
1794
- params[decodeURIComponent(paramName)] = decodeURIComponent(currentSegment);
1795
- continue;
1796
- }
1797
- if (routeSegment !== currentSegment) {
1798
- return null;
1799
- }
1800
- }
1801
- return params;
1802
- }
1803
- function getPathSegments(path) {
1804
- const normalizedPath = normalizePath(path);
1805
- if (normalizedPath === "/") {
1806
- return [];
1807
- }
1808
- return normalizedPath.split("/").filter(Boolean);
1809
- }
1810
-
1811
- // lib/router/history.ts
1812
- function createRouterHref(path, mode, base) {
1813
- const normalizedPath = normalizePath(path);
1814
- const fullPath = base === "/" ? normalizedPath : base + normalizedPath;
1815
- return mode === "hash" ? `#${fullPath}` : fullPath;
1816
- }
1817
- function getBrowserLocation(mode, base) {
1818
- let path;
1819
- let fullPath;
1820
- let queryString;
1821
- if (mode === "history") {
1822
- fullPath = window.location.pathname + window.location.search;
1823
- path = window.location.pathname;
1824
- queryString = window.location.search;
1825
- } else {
1826
- const hash = window.location.hash;
1827
- fullPath = hash || "#/";
1828
- path = fullPath.startsWith("#") ? fullPath.slice(1) : fullPath;
1829
- const queryStart = path.indexOf("?");
1830
- queryString = queryStart >= 0 ? path.slice(queryStart + 1) : "";
1831
- path = queryStart >= 0 ? path.slice(0, queryStart) : path;
1832
- }
1833
- if (path.startsWith(base) && base !== "/" && path !== "/") {
1834
- path = path.slice(base.length);
1835
- }
1836
- path = normalizePath(path);
1837
- return {
1838
- path,
1839
- fullPath,
1840
- query: parseQuery(queryString),
1841
- params: {}
1842
- };
1843
- }
1844
- function navigateBrowser(path, replace, mode, base) {
1845
- const normalizedPath = normalizePath(path);
1846
- const fullPath = base === "/" ? normalizedPath : base + normalizedPath;
1847
- if (mode === "history") {
1848
- if (replace) {
1849
- window.history.replaceState({}, "", fullPath);
1850
- } else {
1851
- window.history.pushState({}, "", fullPath);
1852
- }
1853
- return;
1854
- }
1855
- if (replace) {
1856
- const href = window.location.href.split("#")[0];
1857
- window.location.replace(`${href}#${fullPath}`);
1858
- return;
1859
- }
1860
- window.location.hash = fullPath;
1861
- }
1862
- function parseQuery(queryString) {
1863
- const query = {};
1864
- const normalizedQuery = queryString.startsWith("?") ? queryString.slice(1) : queryString;
1865
- if (!normalizedQuery) {
1866
- return query;
1867
- }
1868
- normalizedQuery.split("&").forEach((param) => {
1869
- const [key, value] = param.split("=");
1870
- if (key) {
1871
- query[decodeURIComponent(key)] = value ? decodeURIComponent(value) : "";
1872
- }
1873
- });
1874
- return query;
1875
- }
1876
-
1877
- // lib/router/index.ts
1878
- class Router {
1879
- currentRoute = null;
1880
- currentLocation = null;
1881
- routes = [];
1882
- app = null;
1883
- mode;
1884
- base;
1885
- routeChangeListeners = [];
1886
- removeWindowListener;
1887
- constructor(options) {
1888
- const resolvedOptions = Array.isArray(options) ? { routes: options } : options;
1889
- this.routes = resolvedOptions.routes || [];
1890
- this.mode = resolvedOptions.mode || "history";
1891
- this.base = resolvedOptions.base || "/";
1892
- this.validateRoutes();
1893
- this.initEvents();
1894
- this.resolveCurrentRoute();
1895
- }
1896
- install(app) {
1897
- this.app = app;
1898
- app.router = this;
1899
- setRouter(this);
1900
- const context = app.getContext();
1901
- context.router = this;
1902
- this.resolveCurrentRoute();
1903
- }
1904
- push(path) {
1905
- this.navigate(path, false);
1906
- }
1907
- replace(path) {
1908
- this.navigate(path, true);
1909
- }
1910
- forward() {
1911
- window.history.forward();
1912
- }
1913
- back() {
1914
- window.history.back();
1915
- }
1916
- go(delta) {
1917
- window.history.go(delta);
1918
- }
1919
- getCurrentRoute() {
1920
- if (!this.currentLocation) {
1921
- this.resolveCurrentRoute();
1922
- }
1923
- return this.currentLocation;
1924
- }
1925
- getCurrentRouteRecord() {
1926
- if (!this.currentRoute) {
1927
- this.resolveCurrentRoute();
1928
- }
1929
- return this.currentRoute;
1930
- }
1931
- onRouteChange(listener) {
1932
- this.routeChangeListeners.push(listener);
1933
- return () => {
1934
- const index = this.routeChangeListeners.indexOf(listener);
1935
- if (index > -1) {
1936
- this.routeChangeListeners.splice(index, 1);
1937
- }
1938
- };
1939
- }
1940
- getRoutes() {
1941
- return [...this.routes];
1942
- }
1943
- addRoute(route) {
1944
- if (this.routes.some((item) => item.path === route.path)) {
1945
- throw new Error(`Route already exists: ${route.path}`);
1946
- }
1947
- this.routes.push(route);
1948
- const location = this.getCurrentLocation();
1949
- if (location.path === route.path) {
1950
- this.handleRouteChange();
1951
- }
1952
- }
1953
- createHref(path) {
1954
- return createRouterHref(path, this.mode, this.base);
1955
- }
1956
- destroy() {
1957
- this.removeWindowListener?.();
1958
- this.removeWindowListener = undefined;
1959
- if (this.app?.router === this) {
1960
- this.app.router = undefined;
1961
- }
1962
- this.app = null;
1963
- }
1964
- navigate(path, replace) {
1965
- if (!path || typeof path !== "string") {
1966
- throw new Error("Path must be a non-empty string");
1967
- }
1968
- navigateBrowser(path, replace, this.mode, this.base);
1969
- this.handleRouteChange();
1970
- }
1971
- validateRoutes() {
1972
- if (!Array.isArray(this.routes)) {
1973
- throw new Error("Router routes must be an array");
1974
- }
1975
- const paths = new Set;
1976
- this.routes.forEach((route) => {
1977
- if (paths.has(route.path)) {
1978
- throw new Error(`Duplicate route path: ${route.path}`);
1979
- }
1980
- paths.add(route.path);
1981
- });
1982
- }
1983
- initEvents() {
1984
- if (typeof window === "undefined") {
1985
- return;
1986
- }
1987
- const eventName = this.mode === "history" ? "popstate" : "hashchange";
1988
- const listener = () => {
1989
- this.handleRouteChange();
1990
- };
1991
- window.addEventListener(eventName, listener);
1992
- this.removeWindowListener = () => {
1993
- window.removeEventListener(eventName, listener);
1994
- };
1995
- }
1996
- handleRouteChange() {
1997
- const fromLocation = this.currentLocation;
1998
- const nextLocation = this.resolveCurrentRoute();
1999
- if (!this.isSameLocation(fromLocation, nextLocation)) {
2000
- this.triggerRouteChangeListeners(nextLocation, fromLocation);
2001
- }
2002
- }
2003
- resolveCurrentRoute() {
2004
- const location = this.getCurrentLocation();
2005
- const match = matchRoute(this.routes, location.path);
2006
- const route = match?.route ?? null;
2007
- this.currentRoute = route;
2008
- this.currentLocation = {
2009
- ...location,
2010
- params: match?.params ?? {},
2011
- name: route?.name,
2012
- meta: route?.meta
2013
- };
2014
- return this.currentLocation;
2015
- }
2016
- getCurrentLocation() {
2017
- return getBrowserLocation(this.mode, this.base);
2018
- }
2019
- isSameLocation(from, to) {
2020
- return from?.fullPath === to?.fullPath && from?.name === to?.name;
2021
- }
2022
- triggerRouteChangeListeners(to, from) {
2023
- this.routeChangeListeners.forEach((listener) => {
2024
- try {
2025
- listener(to, from);
2026
- } catch (error) {
2027
- console.error("Route change listener error:", error);
2028
- }
2029
- });
2030
- }
2031
- }
2032
-
2033
- class RouterLink extends Component {
2034
- unsubscribe;
2035
- initState() {
2036
- const router2 = this.router;
2037
- return {
2038
- currentPath: router2?.getCurrentRoute()?.path ?? window.location.pathname
2039
- };
2040
- }
2041
- initStyles() {}
2042
- onMounted() {
2043
- const router2 = this.router;
2044
- this.unsubscribe = router2?.onRouteChange((to) => {
2045
- this.state.currentPath = to.path;
2046
- });
2047
- }
2048
- onUnmounted() {
2049
- this.unsubscribe?.();
2050
- }
2051
- render() {
2052
- const router2 = this.router;
2053
- const activeClass = this.props.activeClass ?? "active";
2054
- const isActive = this.state.currentPath === this.props.to;
2055
- const className = [this.props.className, isActive ? activeClass : undefined].filter(Boolean).join(" ");
2056
- return {
2057
- tag: "a",
2058
- props: {
2059
- href: router2?.createHref(this.props.to) ?? this.props.to,
2060
- className
2061
- },
2062
- listeners: {
2063
- click: (event) => {
2064
- event.preventDefault();
2065
- if (this.props.replace) {
2066
- router2?.replace(this.props.to);
2067
- } else {
2068
- router2?.push(this.props.to);
2069
- }
2070
- }
2071
- },
2072
- children: this.props.children && this.props.children.length > 0 ? this.props.children : [this.props.to]
2073
- };
2074
- }
2075
- }
2076
-
2077
- class RouterView extends Component {
2078
- unsubscribe;
2079
- initState() {
2080
- const router2 = this.router;
2081
- return {
2082
- route: router2?.getCurrentRoute() ?? null,
2083
- record: router2?.getCurrentRouteRecord() ?? null
2084
- };
2085
- }
2086
- initStyles() {}
2087
- onMounted() {
2088
- const router2 = this.router;
2089
- this.unsubscribe = router2?.onRouteChange((to) => {
2090
- this.state.route = to;
2091
- this.state.record = router2.getCurrentRouteRecord();
2092
- });
2093
- }
2094
- onUnmounted() {
2095
- this.unsubscribe?.();
2096
- }
2097
- render() {
2098
- const routeRecord = this.state.record ?? this.router?.getCurrentRouteRecord();
2099
- return {
2100
- tag: "div",
2101
- props: { "data-router-view": "" },
2102
- children: routeRecord ? [{ component: routeRecord.component }] : []
2103
- };
2104
- }
2105
- }
2106
- function createRouter(options) {
2107
- return new Router(options);
2108
- }
2109
-
2110
- // lib/core/document.ts
2111
- var VOID_HEAD_TAGS = new Set(["base", "link", "meta"]);
2112
- function renderHtmlDocument(options) {
2113
- const lang = options.lang ?? "en";
2114
- const charset = options.charset ?? "utf-8";
2115
- const viewport = options.viewport ?? "width=device-width, initial-scale=1";
2116
- const htmlAttributes = renderAttributes({
2117
- lang,
2118
- ...options.htmlAttributes ?? {}
2119
- });
2120
- const bodyAttributes = renderAttributes(options.bodyAttributes);
2121
- const bodyHtml = renderDocumentBody(options.body);
2122
- return [
2123
- "<!doctype html>",
2124
- `<html${htmlAttributes}>`,
2125
- "<head>",
2126
- ` <meta charset="${escapeHtml(charset)}">`,
2127
- ` <meta name="viewport" content="${escapeHtml(viewport)}">`,
2128
- ` <title>${escapeHtml(options.title)}</title>`,
2129
- options.description ? ` <meta name="description" content="${escapeHtml(options.description)}">` : "",
2130
- ...(options.head ?? []).map((element) => ` ${renderHeadElement(element)}`),
2131
- options.styles && options.styles.length > 0 ? ` <style>${renderStyleSheet(options.styles)}</style>` : "",
2132
- "</head>",
2133
- `<body${bodyAttributes}>`,
2134
- bodyHtml,
2135
- ...(options.scripts ?? []).map((script) => ` ${renderScript(script)}`),
2136
- "</body>",
2137
- "</html>"
2138
- ].filter((line) => line !== "").join(`
2139
- `);
2140
- }
2141
- function renderDocumentBody(body) {
2142
- if (typeof document === "undefined") {
2143
- throw new Error("renderHtmlDocument requires a DOM-like document");
2144
- }
2145
- const container = document.createElement("div");
2146
- const renderer = new RendererContext;
2147
- const mountedComponents = new Set;
2148
- const renderables = Array.isArray(body) ? body : [body];
2149
- const context = {
2150
- templateEngine: new TemplateEngine({}),
2151
- renderer,
2152
- slots: { default: [] },
2153
- registerChild: (component2) => {
2154
- mountedComponents.add(component2);
2155
- },
2156
- unregisterChild: (component2) => {
2157
- mountedComponents.delete(component2);
2158
- }
2159
- };
2160
- renderables.forEach((renderable) => {
2161
- container.appendChild(renderer.mount(renderable, context));
2162
- });
2163
- const html = container.innerHTML;
2164
- mountedComponents.forEach((component2) => {
2165
- component2.unmount();
2166
- });
2167
- return html;
2168
- }
2169
- function renderHeadElement(element) {
2170
- const attributes = renderAttributes(element.attributes);
2171
- if (VOID_HEAD_TAGS.has(element.tag) && !element.text) {
2172
- return `<${element.tag}${attributes}>`;
2173
- }
2174
- return `<${element.tag}${attributes}>${escapeHtml(element.text ?? "")}</${element.tag}>`;
2175
- }
2176
- function renderScript(script) {
2177
- const attributes = renderAttributes({
2178
- type: script.type,
2179
- src: script.src,
2180
- async: script.async,
2181
- defer: script.defer,
2182
- ...script.attributes ?? {}
2183
- });
2184
- return `<script${attributes}></script>`;
2185
- }
2186
- function renderAttributes(attributes = {}) {
2187
- const rendered = Object.entries(attributes).flatMap(([name, value]) => {
2188
- if (value === false || value === null || value === undefined) {
2189
- return [];
2190
- }
2191
- return value === true ? [name] : [`${name}="${escapeHtml(String(value))}"`];
2192
- }).join(" ");
2193
- return rendered ? ` ${rendered}` : "";
2194
- }
2195
- function escapeHtml(value) {
2196
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2197
- }
2198
-
2199
- // lib/core/app.ts
2200
- var DEFAULT_ROOT_ELEMENT = "#app";
2201
-
2202
- class OneApp {
2203
- options;
2204
- container = null;
2205
- rootInstance = null;
2206
- mounted = false;
2207
- templateEngine = null;
2208
- appContext;
2209
- providers = new Map;
2210
- plugins = [];
2211
- unmountedCallback;
2212
- router;
2213
- constructor(options = {}) {
2214
- this.options = options;
2215
- this.appContext = {
2216
- app: this,
2217
- version: "0.2.1",
2218
- config: options.config || {}
2219
- };
2220
- }
2221
- handleError(error) {
2222
- console.error("应用错误:", error);
2223
- this.renderErrorUI(error);
2224
- }
2225
- renderErrorUI(error) {
2226
- if (!this.container) {
2227
- return;
2228
- }
2229
- this.container.innerHTML = `
2230
- <div style="padding: 20px; background-color: #ffebee; color: #c62828; font-family: Arial, sans-serif;">
2231
- <h3>应用错误</h3>
2232
- <p>${error.message}</p>
2233
- <pre style="background-color: #fff; padding: 10px; border-radius: 4px; overflow: auto;">${error.stack}</pre>
2234
- </div>
2235
- `;
2236
- }
2237
- use(plugin, ...args) {
2238
- if (typeof plugin.install !== "function") {
2239
- throw new Error("插件必须提供 install 方法");
2240
- }
2241
- plugin.install(this, ...args);
2242
- this.plugins.push({ plugin, args });
2243
- return this;
2244
- }
2245
- mount() {
2246
- if (this.mounted) {
2247
- console.warn("应用已经处于运行状态");
2248
- return;
2249
- }
2250
- const mountContainer = this.resolveMountContainer();
2251
- if (!mountContainer) {
2252
- return;
2253
- }
2254
- try {
2255
- this.container = mountContainer;
2256
- globalThis.__APP__ = this;
2257
- if (this.options.root) {
2258
- this.rootInstance = new this.options.root(this.options.rootProps);
2259
- if ("setAppContext" in this.rootInstance) {
2260
- this.rootInstance.setAppContext(this.appContext);
2261
- }
2262
- if (this.options.state && "setState" in this.rootInstance) {
2263
- this.rootInstance.setState(this.options.state);
2264
- }
2265
- this.rootInstance.mount(this.container);
2266
- this.templateEngine = new TemplateEngine(this.options.state || {});
2267
- }
2268
- this.mounted = true;
2269
- this.onMounted();
2270
- } catch (error) {
2271
- this.handleError(error);
2272
- }
2273
- }
2274
- unmount() {
2275
- if (!this.mounted) {
2276
- console.warn("应用未处于运行状态");
2277
- return;
2278
- }
2279
- try {
2280
- this.onBeforeUnmount();
2281
- if (this.rootInstance) {
2282
- if ("unmount" in this.rootInstance) {
2283
- this.rootInstance.unmount();
2284
- }
2285
- this.rootInstance = null;
2286
- this.mounted = false;
2287
- delete globalThis.__APP__;
2288
- }
2289
- if (this.templateEngine) {
2290
- this.templateEngine.clearBindings();
2291
- this.templateEngine = null;
2292
- }
2293
- if (this.container) {
2294
- this.container.innerHTML = "";
2295
- }
2296
- if (this.unmountedCallback) {
2297
- this.unmountedCallback();
2298
- }
2299
- } catch (error) {
2300
- console.error("Failed to unmount app:", error);
2301
- }
2302
- }
2303
- isRunning() {
2304
- return this.mounted;
2305
- }
2306
- updateRootComponent(component2) {
2307
- if (this.mounted) {
2308
- this.unmount();
2309
- }
2310
- this.options.root = component2;
2311
- this.mount();
2312
- }
2313
- update(state) {
2314
- if (!this.mounted) {
2315
- console.warn("Cannot update unmounted app");
2316
- return this;
2317
- }
2318
- try {
2319
- if (state && this.options.state) {
2320
- this.options.state = { ...this.options.state, ...state };
2321
- if (this.rootInstance && "setState" in this.rootInstance) {
2322
- this.rootInstance.setState(state);
2323
- }
2324
- if (this.templateEngine) {
2325
- this.templateEngine.state = this.options.state;
2326
- }
2327
- }
2328
- this.onUpdated();
2329
- } catch (error) {
2330
- console.error("Failed to update app:", error);
2331
- }
2332
- return this;
2333
- }
2334
- getContext() {
2335
- return this.appContext;
2336
- }
2337
- provide(key, value) {
2338
- this.providers.set(key, value);
2339
- return this;
2340
- }
2341
- inject(key, fallback) {
2342
- const result = this.resolveInjection(key);
2343
- return result.found ? result.value : fallback;
2344
- }
2345
- resolveInjection(key) {
2346
- if (!this.providers.has(key)) {
2347
- return { found: false, value: undefined };
2348
- }
2349
- return { found: true, value: this.providers.get(key) };
2350
- }
2351
- getState() {
2352
- return this.options.state;
2353
- }
2354
- setState(newState) {
2355
- this.options.state = newState;
2356
- if (this.mounted) {
2357
- this.update();
2358
- }
2359
- return this;
2360
- }
2361
- onUnmounted(callback) {
2362
- this.unmountedCallback = callback;
2363
- return this;
2364
- }
2365
- renderHtmlDocument(options = {}) {
2366
- const appDocument = this.options.document ?? {};
2367
- const scripts = this.mergeDocumentScripts(appDocument.scripts, options.scripts);
2368
- return renderHtmlDocument({
2369
- ...appDocument,
2370
- ...options,
2371
- title: options.title ?? appDocument.title ?? "TSone App",
2372
- body: options.body ?? appDocument.body ?? this.createMountDocumentBody(),
2373
- scripts
2374
- });
2375
- }
2376
- resolveRootElement(selector) {
2377
- if (!selector) {
2378
- return null;
2379
- }
2380
- if (typeof selector === "string") {
2381
- if (typeof document === "undefined") {
2382
- return null;
2383
- }
2384
- return document.querySelector(selector);
2385
- }
2386
- return typeof Element !== "undefined" && selector instanceof Element ? selector : null;
2387
- }
2388
- resolveMountContainer() {
2389
- if (typeof document === "undefined") {
2390
- return null;
2391
- }
2392
- const rootElement = this.resolveRootElement(this.options.rootElement ?? DEFAULT_ROOT_ELEMENT);
2393
- return rootElement instanceof HTMLElement ? rootElement : null;
2394
- }
2395
- createMountDocumentBody() {
2396
- const rootElement = this.options.rootElement ?? DEFAULT_ROOT_ELEMENT;
2397
- if (typeof rootElement === "string") {
2398
- return this.createMountElementFromSelector(rootElement);
2399
- }
2400
- if (typeof Element !== "undefined" && rootElement instanceof Element) {
2401
- const props = {};
2402
- if (rootElement.id) {
2403
- props.id = rootElement.id;
2404
- }
2405
- if (rootElement.className) {
2406
- props.className = rootElement.className;
2407
- }
2408
- return { tag: rootElement.tagName.toLowerCase(), props };
2409
- }
2410
- return Div({ props: { id: "app" } });
2411
- }
2412
- createMountElementFromSelector(selector) {
2413
- if (selector.startsWith("#") && selector.length > 1) {
2414
- return Div({ props: { id: selector.slice(1) } });
2415
- }
2416
- if (selector.startsWith(".") && selector.length > 1) {
2417
- return Div({ props: { className: selector.slice(1) } });
2418
- }
2419
- return Div({ props: { "data-tsone-root": selector } });
2420
- }
2421
- mergeDocumentScripts(baseScripts, extraScripts) {
2422
- if (!baseScripts && !extraScripts) {
2423
- return;
2424
- }
2425
- return [...baseScripts ?? [], ...extraScripts ?? []];
2426
- }
2427
- onMounted() {
2428
- this.plugins.forEach(({ plugin: pluginObj }) => {
2429
- if (pluginObj && typeof pluginObj.onMounted === "function") {
2430
- pluginObj.onMounted(this);
2431
- }
2432
- });
2433
- }
2434
- onUpdated() {
2435
- this.plugins.forEach(({ plugin: pluginObj }) => {
2436
- if (pluginObj && typeof pluginObj.onUpdated === "function") {
2437
- pluginObj.onUpdated(this);
2438
- }
2439
- });
2440
- }
2441
- onBeforeUnmount() {
2442
- this.plugins.forEach(({ plugin: pluginObj }) => {
2443
- if (pluginObj && typeof pluginObj.onBeforeUnmount === "function") {
2444
- pluginObj.onBeforeUnmount(this);
2445
- }
2446
- });
2447
- }
2448
- }
2449
-
2450
- export { ReactiveSystem, reactive, readonly, effect, computed, ref, isRef, unref, stop, isReactive, isReadonly, isComponentNode, isHTMLNode, isSlotProvider, h, Tag, Div, Span, P, Button, Input, Section, Main, Header, Footer, Nav, Article, Aside, H1, H2, H3, H4, H5, H6, Strong, Em, Small, Pre, Code, Blockquote, Ul, Ol, Li, A, Img, Form, Label, Textarea, Select, Option, Table, Thead, Tbody, Tr, Th, Td, createComponent, slot, each, modelPath, getModelValue, setModelValue, ModelBindingController, ElementRenderStrategy, normalizeTransitionGroupProps, validateTransitionGroupChildren, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, TemplateEngine, Component, renderHtmlDocument, OneApp, useRouter, Router, RouterLink, RouterView, createRouter };
2451
-
2452
- //# debugId=ABE10854C25EBD6364756E2164756E21
2453
- //# sourceMappingURL=index-1x4ectvr.js.map