@geektech/tsone 0.0.1 → 0.1.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.
Files changed (40) hide show
  1. package/README-zh.md +405 -0
  2. package/README.md +288 -32
  3. package/dist/core/animation/TransitionGroup.d.ts +7 -0
  4. package/dist/core/animation/index.d.ts +2 -0
  5. package/dist/core/animation/list-animation-controller.d.ts +16 -0
  6. package/dist/core/animation/transition-group-strategy.d.ts +15 -0
  7. package/dist/core/animation/types.d.ts +25 -0
  8. package/dist/core/app.d.ts +30 -8
  9. package/dist/core/component/base.d.ts +18 -1
  10. package/dist/core/component/index.d.ts +2 -2
  11. package/dist/core/document.d.ts +32 -0
  12. package/dist/core/form.d.ts +24 -0
  13. package/dist/core/index.d.ts +4 -0
  14. package/dist/core/model.d.ts +18 -0
  15. package/dist/core/reactive/types.d.ts +5 -0
  16. package/dist/core/reactive.d.ts +5 -2
  17. package/dist/core/renderer/element-strategy.d.ts +25 -0
  18. package/dist/core/renderer/types.d.ts +6 -1
  19. package/dist/core/renderer.d.ts +9 -23
  20. package/dist/core/vnode.d.ts +56 -16
  21. package/dist/{index-dgv88dz4.js → index-8wjswsye.js} +6 -2
  22. package/dist/index-8wjswsye.js.map +10 -0
  23. package/dist/index-vpx80nq5.js +32 -0
  24. package/dist/index-vpx80nq5.js.map +10 -0
  25. package/dist/{index-3j2jsdpc.js → index-wv9gyjqt.js} +858 -244
  26. package/dist/index-wv9gyjqt.js.map +25 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +380 -20
  29. package/dist/index.js.map +8 -5
  30. package/dist/router/index.d.ts +1 -1
  31. package/dist/router/index.js +6 -4
  32. package/dist/router/index.js.map +1 -1
  33. package/dist/style/StyleManager.d.ts +1 -0
  34. package/dist/style/index.d.ts +1 -0
  35. package/dist/style/index.js +6 -2
  36. package/dist/style/index.js.map +1 -1
  37. package/dist/style/sheet.d.ts +13 -0
  38. package/package.json +4 -14
  39. package/dist/index-3j2jsdpc.js.map +0 -20
  40. package/dist/index-dgv88dz4.js.map +0 -10
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  StyleManager
3
- } from "./index-dgv88dz4.js";
3
+ } from "./index-8wjswsye.js";
4
4
 
5
5
  // lib/core/reactive/types.ts
6
6
  var IS_REACTIVE = Symbol("is_reactive");
7
7
  var IS_READONLY = Symbol("is_readonly");
8
+ var IS_REF = Symbol("is_ref");
8
9
  var MUTATING_ARRAY_METHODS = [
9
10
  "push",
10
11
  "pop",
@@ -62,7 +63,7 @@ class ReactiveSystem {
62
63
  }
63
64
  this.track(target2, key);
64
65
  const value = Reflect.get(target2, key);
65
- if (value && typeof value === "object" && !Array.isArray(value)) {
66
+ if (isObject(value) && !hasReactiveFlag(value, IS_READONLY)) {
66
67
  return this.reactive(value);
67
68
  }
68
69
  return value;
@@ -73,7 +74,7 @@ class ReactiveSystem {
73
74
  return false;
74
75
  }
75
76
  const oldValue = Reflect.get(target2, key);
76
- if (value && typeof value === "object" && !Array.isArray(value) && !hasReactiveFlag(value, IS_REACTIVE)) {
77
+ if (isObject(value) && !hasReactiveFlag(value, IS_REACTIVE) && !hasReactiveFlag(value, IS_READONLY)) {
77
78
  value = this.reactive(value);
78
79
  }
79
80
  const result = Reflect.set(target2, key, value);
@@ -121,6 +122,9 @@ class ReactiveSystem {
121
122
  return result;
122
123
  };
123
124
  }
125
+ if (isObject(value) && !hasReactiveFlag(value, IS_READONLY)) {
126
+ return this.reactive(value);
127
+ }
124
128
  return value;
125
129
  },
126
130
  set: (target2, key, value) => {
@@ -129,7 +133,7 @@ class ReactiveSystem {
129
133
  return false;
130
134
  }
131
135
  const oldValue = Reflect.get(target2, key);
132
- if (value && typeof value === "object" && !Array.isArray(value) && !hasReactiveFlag(value, IS_REACTIVE)) {
136
+ if (isObject(value) && !hasReactiveFlag(value, IS_REACTIVE) && !hasReactiveFlag(value, IS_READONLY)) {
133
137
  value = this.reactive(value);
134
138
  }
135
139
  const result = Reflect.set(target2, key, value);
@@ -171,11 +175,14 @@ class ReactiveSystem {
171
175
  }
172
176
  const proxy = new Proxy(target, {
173
177
  get: (target2, key) => {
174
- if (key === IS_REACTIVE || key === IS_READONLY) {
178
+ if (key === IS_REACTIVE) {
179
+ return false;
180
+ }
181
+ if (key === IS_READONLY) {
175
182
  return true;
176
183
  }
177
184
  const value = Reflect.get(target2, key);
178
- if (value && typeof value === "object" && !Array.isArray(value)) {
185
+ if (isObject(value)) {
179
186
  return this.readonly(value);
180
187
  }
181
188
  return value;
@@ -193,7 +200,7 @@ class ReactiveSystem {
193
200
  return proxy;
194
201
  }
195
202
  effect(fn, options) {
196
- const { lazy = false, scheduler } = options || {};
203
+ const { lazy = false, scheduler, throwOnError = false } = options || {};
197
204
  const effectFn = () => {
198
205
  if (!effectFn.active) {
199
206
  return fn();
@@ -204,6 +211,9 @@ class ReactiveSystem {
204
211
  this.activeEffect = effectFn;
205
212
  return fn();
206
213
  } catch (error) {
214
+ if (throwOnError) {
215
+ throw error;
216
+ }
207
217
  console.error("Effect error:", error);
208
218
  return;
209
219
  } finally {
@@ -310,6 +320,21 @@ function effect(fn, options) {
310
320
  function computed(getter) {
311
321
  return ReactiveSystem.getInstance().computed(getter);
312
322
  }
323
+ function ref(value) {
324
+ const wrapper = { value };
325
+ Object.defineProperty(wrapper, IS_REF, {
326
+ configurable: false,
327
+ enumerable: false,
328
+ value: true
329
+ });
330
+ return reactive(wrapper);
331
+ }
332
+ function isRef(value) {
333
+ return isObject(value) && Boolean(Reflect.get(value, IS_REF));
334
+ }
335
+ function unref(value) {
336
+ return isRef(value) ? value.value : value;
337
+ }
313
338
  function stop(effect2) {
314
339
  ReactiveSystem.getInstance().stop(effect2);
315
340
  }
@@ -320,40 +345,6 @@ function isReadonly(value) {
320
345
  return isObject(value) && hasReactiveFlag(value, IS_READONLY);
321
346
  }
322
347
 
323
- // lib/core/renderer/props.ts
324
- function isEventProp(key) {
325
- return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
326
- }
327
- function eventNameFromProp(key) {
328
- return key.slice(2).toLowerCase();
329
- }
330
- function parseEventName(event) {
331
- const [eventName, ...modifiers] = event.split(".");
332
- return { eventName, modifiers: new Set(modifiers) };
333
- }
334
- function wrapEventHandler(handler, modifiers) {
335
- const eventHandler = (event) => {
336
- if (modifiers.has("stop")) {
337
- event.stopPropagation();
338
- }
339
- if (modifiers.has("prevent")) {
340
- event.preventDefault();
341
- }
342
- if (modifiers.has("self") && event.currentTarget !== event.target) {
343
- return;
344
- }
345
- if (modifiers.has("once")) {
346
- event.currentTarget.removeEventListener(event.type, eventHandler);
347
- }
348
- handler(event);
349
- };
350
- return eventHandler;
351
- }
352
- function setStyleValue(style, property, value) {
353
- const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
354
- style.setProperty(cssProperty, String(value));
355
- }
356
-
357
348
  // lib/core/vnode.ts
358
349
  function isComponentNode(vnode) {
359
350
  return typeof vnode === "object" && vnode !== null && "component" in vnode;
@@ -374,6 +365,55 @@ function h(tag, props, children, listeners, key, directions) {
374
365
  directions
375
366
  };
376
367
  }
368
+ function Tag(tag, options = {}) {
369
+ return {
370
+ tag,
371
+ ...options
372
+ };
373
+ }
374
+ function createElementFactory(tag) {
375
+ return (options = {}) => Tag(tag, options);
376
+ }
377
+ var Div = createElementFactory("div");
378
+ var Span = createElementFactory("span");
379
+ var P = createElementFactory("p");
380
+ var Button = createElementFactory("button");
381
+ var Input = createElementFactory("input");
382
+ var Section = createElementFactory("section");
383
+ var Main = createElementFactory("main");
384
+ var Header = createElementFactory("header");
385
+ var Footer = createElementFactory("footer");
386
+ var Nav = createElementFactory("nav");
387
+ var Article = createElementFactory("article");
388
+ var Aside = createElementFactory("aside");
389
+ var H1 = createElementFactory("h1");
390
+ var H2 = createElementFactory("h2");
391
+ var H3 = createElementFactory("h3");
392
+ var H4 = createElementFactory("h4");
393
+ var H5 = createElementFactory("h5");
394
+ var H6 = createElementFactory("h6");
395
+ var Strong = createElementFactory("strong");
396
+ var Em = createElementFactory("em");
397
+ var Small = createElementFactory("small");
398
+ var Pre = createElementFactory("pre");
399
+ var Code = createElementFactory("code");
400
+ var Blockquote = createElementFactory("blockquote");
401
+ var Ul = createElementFactory("ul");
402
+ var Ol = createElementFactory("ol");
403
+ var Li = createElementFactory("li");
404
+ var A = createElementFactory("a");
405
+ var Img = createElementFactory("img");
406
+ var Form = createElementFactory("form");
407
+ var Label = createElementFactory("label");
408
+ var Textarea = createElementFactory("textarea");
409
+ var Select = createElementFactory("select");
410
+ var Option = createElementFactory("option");
411
+ var Table = createElementFactory("table");
412
+ var Thead = createElementFactory("thead");
413
+ var Tbody = createElementFactory("tbody");
414
+ var Tr = createElementFactory("tr");
415
+ var Th = createElementFactory("th");
416
+ var Td = createElementFactory("td");
377
417
  function createComponent(componentClass, props, children, key, directions) {
378
418
  return {
379
419
  component: componentClass,
@@ -391,166 +431,236 @@ function slot(name, key, directions) {
391
431
  directions
392
432
  };
393
433
  }
434
+ function each(items, render, key) {
435
+ return items.map((item, index) => {
436
+ const vnode = render(item, index);
437
+ if (typeof vnode === "string") {
438
+ throw new Error("each render callback must return a VNode");
439
+ }
440
+ return { ...vnode, key: key(item, index) };
441
+ });
442
+ }
394
443
 
395
- // lib/core/renderer.ts
396
- class RendererContext {
397
- strategies;
398
- constructor() {
399
- this.strategies = [
400
- new TextRenderStrategy,
401
- new ComponentRenderStrategy,
402
- new SlotRenderStrategy,
403
- new ElementRenderStrategy
404
- ];
444
+ // lib/core/model.ts
445
+ function pathSegments(path) {
446
+ const segments = path.split(".");
447
+ if (path.length === 0 || segments.some((segment) => segment.length === 0 || segment === "__proto__" || segment === "prototype" || segment === "constructor")) {
448
+ throw new Error(`Invalid model path "${path}"`);
405
449
  }
406
- mount(vnode, context) {
407
- return this.findStrategy(vnode).mount(vnode, context);
450
+ return segments;
451
+ }
452
+ function isRecord(value) {
453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
454
+ }
455
+ function hasOwn(value, key) {
456
+ return Object.prototype.hasOwnProperty.call(value, key);
457
+ }
458
+ function modelPath(binding) {
459
+ return typeof binding === "string" ? binding : binding.path;
460
+ }
461
+ function getModelValue(state, path) {
462
+ let value = state;
463
+ for (const segment of pathSegments(path)) {
464
+ if (!isRecord(value)) {
465
+ throw new Error(`Invalid model path "${path}"`);
466
+ }
467
+ if (!hasOwn(value, segment)) {
468
+ if (segment in value) {
469
+ throw new Error(`Invalid model path "${path}"`);
470
+ }
471
+ return;
472
+ }
473
+ value = value[segment];
408
474
  }
409
- patch(oldVNode, newVNode, currentNode, context) {
410
- const oldStrategy = this.findStrategy(oldVNode);
411
- const newStrategy = this.findStrategy(newVNode);
412
- if (oldStrategy !== newStrategy) {
413
- const nextNode = newStrategy.mount(newVNode, context);
414
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
415
- oldStrategy.unmount(oldVNode, currentNode, context);
416
- return nextNode;
475
+ return value;
476
+ }
477
+ function setModelValue(state, path, value) {
478
+ const segments = pathSegments(path);
479
+ let target = state;
480
+ for (const segment of segments.slice(0, -1)) {
481
+ if (!hasOwn(target, segment)) {
482
+ if (segment in target) {
483
+ throw new Error(`Invalid model path "${path}"`);
484
+ }
485
+ target[segment] = {};
486
+ } else if (!isRecord(target[segment])) {
487
+ throw new Error(`Invalid model path "${path}"`);
417
488
  }
418
- return oldStrategy.patch(oldVNode, newVNode, currentNode, context);
489
+ const nextTarget = target[segment];
490
+ if (!isRecord(nextTarget)) {
491
+ throw new Error(`Invalid model path "${path}"`);
492
+ }
493
+ target = nextTarget;
419
494
  }
420
- unmount(vnode, currentNode, context) {
421
- this.findStrategy(vnode).unmount(vnode, currentNode, context);
495
+ const lastSegment = segments[segments.length - 1];
496
+ target[lastSegment] = value;
497
+ }
498
+ function displayValue(binding, value) {
499
+ if (typeof binding !== "string" && binding.format) {
500
+ return binding.format(value);
422
501
  }
423
- findStrategy(vnode) {
424
- const strategy = this.strategies.find((item) => item.matches(vnode));
425
- if (!strategy) {
426
- throw new Error("No render strategy found for vnode");
427
- }
428
- return strategy;
502
+ return value === undefined || value === null ? "" : String(value);
503
+ }
504
+ function toModelValue(binding, value) {
505
+ if (typeof binding !== "string" && binding.parse) {
506
+ return binding.parse(value);
429
507
  }
508
+ return value;
430
509
  }
431
-
432
- class TextRenderStrategy {
433
- matches(vnode) {
434
- return typeof vnode === "string";
510
+ function syncControl(element, binding, value) {
511
+ if (element instanceof HTMLInputElement) {
512
+ if (element.type === "checkbox") {
513
+ element.checked = Array.isArray(value) ? value.some((item) => String(item) === element.value) : Boolean(value);
514
+ return;
515
+ }
516
+ if (element.type === "radio") {
517
+ element.checked = value === element.value;
518
+ return;
519
+ }
520
+ element.value = displayValue(binding, value);
521
+ return;
435
522
  }
436
- mount(vnode, context) {
437
- return context.templateEngine.parseTemplate(vnode);
523
+ if (element instanceof HTMLTextAreaElement) {
524
+ element.value = displayValue(binding, value);
525
+ return;
438
526
  }
439
- patch(oldVNode, newVNode, currentNode, context) {
440
- if (oldVNode === newVNode) {
441
- return currentNode;
527
+ if (element instanceof HTMLSelectElement) {
528
+ if (element.multiple) {
529
+ const selected = Array.isArray(value) ? new Set(value.map(String)) : new Set;
530
+ for (let index = 0;index < element.options.length; index += 1) {
531
+ const option = element.options.item(index);
532
+ if (!option) {
533
+ continue;
534
+ }
535
+ option.selected = selected.has(option.value);
536
+ }
537
+ return;
442
538
  }
443
- const nextNode = this.mount(newVNode, context);
444
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
445
- return nextNode;
539
+ element.value = displayValue(binding, value);
446
540
  }
447
- unmount() {}
448
541
  }
449
-
450
- class ComponentRenderStrategy {
451
- instances = new WeakMap;
452
- matches(vnode) {
453
- return typeof vnode === "object" && vnode !== null && isComponentNode(vnode);
454
- }
455
- mount(vnode, context) {
456
- const ComponentClass = vnode.component;
457
- const instance = new ComponentClass(this.createProps(vnode));
458
- if (context.appContext && instance.setAppContext) {
459
- instance.setAppContext(context.appContext);
542
+ function controlValue(element, currentValue) {
543
+ if (element instanceof HTMLInputElement) {
544
+ if (element.type === "checkbox") {
545
+ if (Array.isArray(currentValue)) {
546
+ const values = currentValue.filter((value) => String(value) !== element.value);
547
+ return element.checked ? [...values, element.value] : values;
548
+ }
549
+ return element.checked;
460
550
  }
461
- if (vnode.emitters) {
462
- Object.entries(vnode.emitters).forEach(([eventName, listener]) => {
463
- instance.on(eventName, listener);
464
- });
551
+ if (element.type === "radio") {
552
+ return element.checked ? element.value : currentValue;
465
553
  }
466
- context.registerChild(instance);
467
- const node = instance.mountToNode();
468
- this.instances.set(node, instance);
469
- return node;
554
+ return element.value;
470
555
  }
471
- patch(oldVNode, newVNode, currentNode, context) {
472
- const instance = this.instances.get(currentNode);
473
- if (instance && oldVNode.component === newVNode.component) {
474
- instance.setProps(this.createProps(newVNode));
475
- instance.update();
476
- const nextNode2 = instance.getElement() ?? currentNode;
477
- this.instances.set(nextNode2, instance);
478
- return nextNode2;
479
- }
480
- const nextNode = this.mount(newVNode, context);
481
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
482
- this.unmount(oldVNode, currentNode);
483
- return nextNode;
556
+ if (element instanceof HTMLTextAreaElement) {
557
+ return element.value;
484
558
  }
485
- unmount(_vnode, currentNode) {
486
- const instance = this.instances.get(currentNode);
487
- if (instance) {
488
- instance.unmount();
489
- this.instances.delete(currentNode);
559
+ if (element instanceof HTMLSelectElement) {
560
+ if (!element.multiple) {
561
+ return element.value;
490
562
  }
563
+ const values = [];
564
+ for (let index = 0;index < element.selectedOptions.length; index += 1) {
565
+ const option = element.selectedOptions.item(index);
566
+ if (option) {
567
+ values.push(option.value);
568
+ }
569
+ }
570
+ return values;
491
571
  }
492
- createProps(vnode) {
493
- return {
494
- ...vnode.props ?? {},
495
- children: vnode.children ?? []
496
- };
497
- }
572
+ return;
498
573
  }
499
574
 
500
- class SlotRenderStrategy {
501
- renderedChildren = new WeakMap;
502
- matches(vnode) {
503
- return typeof vnode === "object" && vnode !== null && isSlotProvider(vnode);
504
- }
505
- mount(vnode, context) {
506
- const slotContainer = document.createElement("div");
507
- slotContainer.setAttribute("data-slot", vnode.props.name);
508
- this.mountSlotChildren(slotContainer, this.resolveChildren(vnode, context), context);
509
- return slotContainer;
510
- }
511
- patch(oldVNode, newVNode, currentNode, context) {
512
- if (currentNode instanceof HTMLElement) {
513
- currentNode.setAttribute("data-slot", newVNode.props.name);
514
- this.replaceSlotChildren(currentNode, oldVNode, newVNode, context);
575
+ class ModelBindingController {
576
+ bindings = new WeakMap;
577
+ bind(element, binding, state) {
578
+ if (!this.isSupportedControl(element)) {
579
+ return;
515
580
  }
516
- return currentNode;
517
- }
518
- unmount(_vnode, currentNode, context) {
519
- if (!(currentNode instanceof HTMLElement)) {
581
+ const existing = this.bindings.get(element);
582
+ if (existing && this.sameBinding(existing, binding)) {
520
583
  return;
521
584
  }
522
- this.unmountSlotChildren(currentNode, context);
523
- this.renderedChildren.delete(currentNode);
524
- }
525
- resolveChildren(vnode, context) {
526
- return context.slots[vnode.props.name] ?? vnode.children ?? [];
585
+ this.cleanup(element);
586
+ const path = modelPath(binding);
587
+ const sync = () => syncControl(element, binding, getModelValue(state, path));
588
+ const eventName = element instanceof HTMLTextAreaElement || element instanceof HTMLInputElement && !["checkbox", "radio"].includes(element.type) ? "input" : "change";
589
+ const listener = () => {
590
+ const currentValue = getModelValue(state, path);
591
+ setModelValue(state, path, toModelValue(binding, controlValue(element, currentValue)));
592
+ };
593
+ element.addEventListener(eventName, listener);
594
+ const effectRef = effect(sync);
595
+ this.bindings.set(element, {
596
+ binding,
597
+ path,
598
+ parse: typeof binding === "string" ? undefined : binding.parse,
599
+ format: typeof binding === "string" ? undefined : binding.format,
600
+ eventName,
601
+ listener,
602
+ effect: effectRef
603
+ });
527
604
  }
528
- replaceSlotChildren(element, _oldVNode, newVNode, context) {
529
- this.unmountSlotChildren(element, context);
530
- element.textContent = "";
531
- this.mountSlotChildren(element, this.resolveChildren(newVNode, context), context);
605
+ cleanup(element) {
606
+ const existing = this.bindings.get(element);
607
+ if (!existing) {
608
+ return;
609
+ }
610
+ element.removeEventListener(existing.eventName, existing.listener);
611
+ stop(existing.effect);
612
+ this.bindings.delete(element);
532
613
  }
533
- mountSlotChildren(element, children, context) {
534
- children.forEach((child) => {
535
- element.appendChild(context.renderer.mount(child, context));
536
- });
537
- this.renderedChildren.set(element, children);
614
+ isSupportedControl(element) {
615
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
538
616
  }
539
- unmountSlotChildren(element, context) {
540
- const children = this.renderedChildren.get(element) ?? [];
541
- children.forEach((child, index) => {
542
- const childNode = element.childNodes[index];
543
- if (childNode) {
544
- context.renderer.unmount(child, childNode, context);
545
- }
546
- });
617
+ sameBinding(record, binding) {
618
+ if (typeof record.binding === "string" || typeof binding === "string") {
619
+ return record.binding === binding;
620
+ }
621
+ return record.path === binding.path && record.parse === binding.parse && record.format === binding.format;
547
622
  }
548
623
  }
549
624
 
625
+ // lib/core/renderer/props.ts
626
+ function isEventProp(key) {
627
+ return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
628
+ }
629
+ function eventNameFromProp(key) {
630
+ return key.slice(2).toLowerCase();
631
+ }
632
+ function parseEventName(event) {
633
+ const [eventName, ...modifiers] = event.split(".");
634
+ return { eventName, modifiers: new Set(modifiers) };
635
+ }
636
+ function wrapEventHandler(handler, modifiers) {
637
+ const eventHandler = (event) => {
638
+ if (modifiers.has("stop")) {
639
+ event.stopPropagation();
640
+ }
641
+ if (modifiers.has("prevent")) {
642
+ event.preventDefault();
643
+ }
644
+ if (modifiers.has("self") && event.currentTarget !== event.target) {
645
+ return;
646
+ }
647
+ if (modifiers.has("once")) {
648
+ event.currentTarget.removeEventListener(event.type, eventHandler);
649
+ }
650
+ handler(event);
651
+ };
652
+ return eventHandler;
653
+ }
654
+ function setStyleValue(style, property, value) {
655
+ const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
656
+ style.setProperty(cssProperty, String(value));
657
+ }
658
+
659
+ // lib/core/renderer/element-strategy.ts
550
660
  class ElementRenderStrategy {
551
661
  listeners = new WeakMap;
552
662
  effects = new WeakMap;
553
- modelBindings = new WeakMap;
663
+ modelBindings = new ModelBindingController;
554
664
  matches(vnode) {
555
665
  return typeof vnode === "object" && vnode !== null && isHTMLNode(vnode);
556
666
  }
@@ -560,11 +670,9 @@ class ElementRenderStrategy {
560
670
  }
561
671
  const element = document.createElement(vnode.tag);
562
672
  this.applyProps(element, {}, vnode.props ?? {}, context);
563
- this.applyDirections(element, undefined, vnode.directions, context);
564
673
  this.updateListeners(element, {}, this.collectListeners(vnode));
565
- (vnode.children ?? []).forEach((child) => {
566
- element.appendChild(context.renderer.mount(child, context));
567
- });
674
+ this.mountChildren(element, vnode, context);
675
+ this.applyDirections(element, undefined, vnode.directions, context);
568
676
  return element;
569
677
  }
570
678
  patch(oldVNode, newVNode, currentNode, context) {
@@ -584,9 +692,9 @@ class ElementRenderStrategy {
584
692
  return nextNode;
585
693
  }
586
694
  this.applyProps(currentNode, oldVNode.props ?? {}, newVNode.props ?? {}, context);
587
- this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
588
695
  this.updateListeners(currentNode, this.collectListeners(oldVNode), this.collectListeners(newVNode));
589
- this.updateChildren(currentNode, oldVNode.children ?? [], newVNode.children ?? [], context);
696
+ this.updateChildren(currentNode, oldVNode, newVNode, context);
697
+ this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
590
698
  return currentNode;
591
699
  }
592
700
  unmount(vnode, currentNode, context) {
@@ -599,9 +707,20 @@ class ElementRenderStrategy {
599
707
  currentNode.removeEventListener(eventName, listener);
600
708
  });
601
709
  this.listeners.delete(currentNode);
602
- this.modelBindings.delete(currentNode);
710
+ this.modelBindings.cleanup(currentNode);
711
+ this.unmountChildren(currentNode, vnode, context);
712
+ }
713
+ mountChildren(element, vnode, context) {
714
+ (vnode.children ?? []).forEach((child) => {
715
+ element.appendChild(context.renderer.mount(child, context));
716
+ });
717
+ }
718
+ updateChildren(element, oldVNode, newVNode, context) {
719
+ this.updateOrdinaryChildren(element, oldVNode.children ?? [], newVNode.children ?? [], context);
720
+ }
721
+ unmountChildren(element, vnode, context) {
603
722
  (vnode.children ?? []).forEach((child, index) => {
604
- const childNode = currentNode.childNodes[index];
723
+ const childNode = element.childNodes[index];
605
724
  if (childNode) {
606
725
  context.renderer.unmount(child, childNode, context);
607
726
  }
@@ -656,12 +775,16 @@ class ElementRenderStrategy {
656
775
  } else if (oldDirections && "show" in oldDirections) {
657
776
  element.style.display = "";
658
777
  }
659
- if (newDirections?.model) {
660
- this.setupTwoWayBinding(element, newDirections.model, context);
778
+ if (!newDirections?.model) {
779
+ this.modelBindings.cleanup(element);
780
+ return;
661
781
  }
782
+ this.modelBindings.bind(element, newDirections.model, context.templateEngine.state);
662
783
  }
663
- updateChildren(element, oldChildren, newChildren, context) {
664
- if (this.hasKeyedChildren(oldChildren, newChildren)) {
784
+ updateOrdinaryChildren(element, oldChildren, newChildren, context) {
785
+ this.assertNoDuplicateKeys(oldChildren);
786
+ this.assertNoDuplicateKeys(newChildren);
787
+ if (this.hasOnlyKeyedChildren(oldChildren, newChildren)) {
665
788
  this.updateKeyedChildren(element, oldChildren, newChildren, context);
666
789
  return;
667
790
  }
@@ -730,13 +853,26 @@ class ElementRenderStrategy {
730
853
  }
731
854
  });
732
855
  }
733
- hasKeyedChildren(oldChildren, newChildren) {
734
- return [...oldChildren, ...newChildren].some((child) => this.getVNodeKey(child) !== undefined);
856
+ hasOnlyKeyedChildren(oldChildren, newChildren) {
857
+ return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
735
858
  }
736
- getVNodeKey(vnode) {
737
- if (typeof vnode === "string") {
738
- return;
739
- }
859
+ assertNoDuplicateKeys(children) {
860
+ const keys = new Set;
861
+ children.forEach((child) => {
862
+ const key = this.getVNodeKey(child);
863
+ if (key === undefined) {
864
+ return;
865
+ }
866
+ if (keys.has(key)) {
867
+ throw new Error(`Duplicate key "${key}"`);
868
+ }
869
+ keys.add(key);
870
+ });
871
+ }
872
+ getVNodeKey(vnode) {
873
+ if (typeof vnode === "string") {
874
+ return;
875
+ }
740
876
  return vnode.key;
741
877
  }
742
878
  collectListeners(vnode) {
@@ -780,59 +916,481 @@ class ElementRenderStrategy {
780
916
  });
781
917
  this.trackEffect(element, effectRef);
782
918
  }
783
- setupTwoWayBinding(element, modelKey, context) {
784
- if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement) && !(element instanceof HTMLSelectElement)) {
919
+ trackEffect(element, effectRef) {
920
+ const effects = this.effects.get(element) ?? new Set;
921
+ effects.add(effectRef);
922
+ this.effects.set(element, effects);
923
+ }
924
+ }
925
+
926
+ // lib/core/animation/list-animation-controller.ts
927
+ var ENTER_KEYFRAMES = {
928
+ fade: [{ opacity: 0 }, { opacity: 1 }],
929
+ "slide-up": [
930
+ { opacity: 0, transform: "translateY(12px)" },
931
+ { opacity: 1, transform: "translateY(0)" }
932
+ ],
933
+ "slide-down": [
934
+ { opacity: 0, transform: "translateY(-12px)" },
935
+ { opacity: 1, transform: "translateY(0)" }
936
+ ],
937
+ "slide-left": [
938
+ { opacity: 0, transform: "translateX(12px)" },
939
+ { opacity: 1, transform: "translateX(0)" }
940
+ ],
941
+ "slide-right": [
942
+ { opacity: 0, transform: "translateX(-12px)" },
943
+ { opacity: 1, transform: "translateX(0)" }
944
+ ],
945
+ scale: [
946
+ { opacity: 0, transform: "scale(0.95)" },
947
+ { opacity: 1, transform: "scale(1)" }
948
+ ]
949
+ };
950
+
951
+ class ListAnimationController {
952
+ runs = new WeakMap;
953
+ playEnter(element, options) {
954
+ return this.play(element, options, "enter");
955
+ }
956
+ playExit(element, options) {
957
+ return this.play(element, options, "exit");
958
+ }
959
+ cancel(element) {
960
+ const current = this.runs.get(element);
961
+ if (!current) {
785
962
  return;
786
963
  }
787
- if (this.modelBindings.get(element) === modelKey) {
788
- return;
964
+ this.runs.delete(element);
965
+ current.animation.cancel();
966
+ }
967
+ play(element, transition, phase) {
968
+ this.cancel(element);
969
+ if (!this.canAnimate(element)) {
970
+ return null;
789
971
  }
790
- this.modelBindings.set(element, modelKey);
791
- const getValue = () => {
792
- const value = this.getStateValue(context, modelKey);
793
- return value === undefined || value === null ? "" : String(value);
972
+ const enterKeyframes = ENTER_KEYFRAMES[transition.type];
973
+ const keyframes = phase === "enter" ? [...enterKeyframes] : [...enterKeyframes].reverse();
974
+ const options = {
975
+ duration: transition.duration,
976
+ easing: "ease",
977
+ fill: "both"
978
+ };
979
+ const animation = element.animate(keyframes, options);
980
+ const token = Symbol("list-animation");
981
+ const finished = animation.finished.then(() => "finished", () => "cancelled");
982
+ const run = {
983
+ animation,
984
+ token,
985
+ keyframes,
986
+ options,
987
+ finished
794
988
  };
795
- const setValue = (value) => {
796
- const keys = modelKey.split(".");
797
- let target = context.templateEngine.state;
798
- for (let index = 0;index < keys.length - 1; index += 1) {
799
- const key = keys[index];
800
- if (!target[key] || typeof target[key] !== "object") {
801
- target[key] = {};
989
+ this.runs.set(element, run);
990
+ finished.then((result) => {
991
+ if (this.runs.get(element)?.token !== token) {
992
+ return;
993
+ }
994
+ this.runs.delete(element);
995
+ if (phase === "enter" && result === "finished") {
996
+ animation.cancel();
997
+ }
998
+ });
999
+ return run;
1000
+ }
1001
+ canAnimate(element) {
1002
+ const reduced = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1003
+ return !reduced && typeof element.animate === "function";
1004
+ }
1005
+ }
1006
+
1007
+ // lib/core/animation/types.ts
1008
+ var TRANSITION_ANIMATION_TYPES = [
1009
+ "fade",
1010
+ "slide-up",
1011
+ "slide-down",
1012
+ "slide-left",
1013
+ "slide-right",
1014
+ "scale"
1015
+ ];
1016
+ function normalizeTransitionGroupProps(props) {
1017
+ const tag = (props.tag ?? "div").trim();
1018
+ const type = props.type ?? "fade";
1019
+ const duration = props.duration ?? 300;
1020
+ if (!tag) {
1021
+ throw new Error("TransitionGroup tag must not be empty");
1022
+ }
1023
+ if (!TRANSITION_ANIMATION_TYPES.includes(type)) {
1024
+ throw new Error(`Unknown TransitionGroup animation type "${type}"`);
1025
+ }
1026
+ if (!Number.isFinite(duration) || duration < 0) {
1027
+ throw new Error("TransitionGroup duration must be a non-negative finite number");
1028
+ }
1029
+ return { tag, type, duration };
1030
+ }
1031
+ function validateTransitionGroupChildren(children) {
1032
+ const keys = new Set;
1033
+ return children.map((child) => {
1034
+ if (typeof child === "string" || child.key === undefined || keys.has(child.key)) {
1035
+ throw new Error("TransitionGroup children must have unique keys");
1036
+ }
1037
+ keys.add(child.key);
1038
+ return child;
1039
+ });
1040
+ }
1041
+ function isTransitionGroupNode(vnode) {
1042
+ return typeof vnode === "object" && vnode !== null && "transitionGroup" in vnode && isHTMLNode(vnode);
1043
+ }
1044
+
1045
+ // lib/core/animation/transition-group-strategy.ts
1046
+ class TransitionGroupRenderStrategy extends ElementRenderStrategy {
1047
+ entries = new WeakMap;
1048
+ animations = new ListAnimationController;
1049
+ matches(vnode) {
1050
+ return isTransitionGroupNode(vnode);
1051
+ }
1052
+ mountChildren(element, groupVNode, context) {
1053
+ const keyedChildren = validateTransitionGroupChildren(groupVNode.children ?? []);
1054
+ const entries = new Map;
1055
+ keyedChildren.forEach((childVNode) => {
1056
+ const node = context.renderer.mount(childVNode, context);
1057
+ element.appendChild(node);
1058
+ const entry = {
1059
+ key: childVNode.key,
1060
+ vnode: childVNode,
1061
+ node,
1062
+ status: "active"
1063
+ };
1064
+ entries.set(entry.key, entry);
1065
+ this.playEnter(entry, node, groupVNode.transitionGroup);
1066
+ });
1067
+ this.entries.set(element, entries);
1068
+ }
1069
+ updateChildren(element, _oldVNode, newVNode, context) {
1070
+ const entries = this.entries.get(element) ?? new Map;
1071
+ const nextChildren = validateTransitionGroupChildren(newVNode.children ?? []);
1072
+ const nextKeys = new Set(nextChildren.map((child) => child.key));
1073
+ const ordered = [];
1074
+ nextChildren.forEach((childVNode) => {
1075
+ const key = childVNode.key;
1076
+ const current = entries.get(key);
1077
+ if (current) {
1078
+ const wasExiting = current.status === "exiting";
1079
+ if (wasExiting && current.node instanceof HTMLElement) {
1080
+ this.animations.cancel(current.node);
802
1081
  }
803
- target = target[key];
1082
+ current.status = "active";
1083
+ current.animationToken = undefined;
1084
+ current.node = context.renderer.patch(current.vnode, childVNode, current.node, context);
1085
+ current.vnode = childVNode;
1086
+ ordered.push(current);
1087
+ if (wasExiting) {
1088
+ this.playEnter(current, current.node, newVNode.transitionGroup);
1089
+ }
1090
+ return;
804
1091
  }
805
- target[keys[keys.length - 1]] = value;
806
- };
807
- element.value = getValue();
808
- const eventName = element instanceof HTMLSelectElement ? "change" : "input";
809
- const inputListener = () => {
810
- setValue(element.value);
811
- };
812
- element.addEventListener(eventName, inputListener);
813
- const store = this.listeners.get(element) ?? new Map;
814
- store.set(`model:${modelKey}`, { eventName, listener: inputListener });
815
- this.listeners.set(element, store);
816
- const effectRef = effect(() => {
817
- const nextValue = getValue();
818
- if (element.value !== nextValue) {
819
- element.value = nextValue;
1092
+ const node = context.renderer.mount(childVNode, context);
1093
+ const entry = {
1094
+ key,
1095
+ vnode: childVNode,
1096
+ node,
1097
+ status: "active"
1098
+ };
1099
+ entries.set(key, entry);
1100
+ ordered.push(entry);
1101
+ this.playEnter(entry, node, newVNode.transitionGroup);
1102
+ });
1103
+ entries.forEach((entry, key) => {
1104
+ if (nextKeys.has(key) || entry.status !== "active") {
1105
+ return;
820
1106
  }
1107
+ this.startExit(element, entry, newVNode.transitionGroup, context);
821
1108
  });
822
- this.trackEffect(element, effectRef);
1109
+ this.placeActiveEntries(element, ordered);
1110
+ this.entries.set(element, entries);
823
1111
  }
824
- getStateValue(context, modelKey) {
825
- return modelKey.split(".").reduce((value, key) => {
826
- if (!value || typeof value !== "object") {
1112
+ unmountChildren(element, vnode, context) {
1113
+ const entries = this.entries.get(element);
1114
+ if (!entries) {
1115
+ super.unmountChildren(element, vnode, context);
1116
+ return;
1117
+ }
1118
+ entries.forEach((entry) => {
1119
+ if (entry.node instanceof HTMLElement) {
1120
+ this.animations.cancel(entry.node);
1121
+ }
1122
+ context.renderer.unmount(entry.vnode, entry.node, context);
1123
+ if (entry.node.parentNode === element) {
1124
+ element.removeChild(entry.node);
1125
+ }
1126
+ });
1127
+ entries.clear();
1128
+ this.entries.delete(element);
1129
+ }
1130
+ playEnter(entry, node, options) {
1131
+ if (!(node instanceof HTMLElement)) {
1132
+ return;
1133
+ }
1134
+ const run = this.animations.playEnter(node, options);
1135
+ entry.animationToken = run?.token;
1136
+ }
1137
+ startExit(wrapper, entry, options, context) {
1138
+ entry.status = "exiting";
1139
+ const run = entry.node instanceof HTMLElement ? this.animations.playExit(entry.node, options) : null;
1140
+ if (!run) {
1141
+ this.finishExit(wrapper, entry, context);
1142
+ return;
1143
+ }
1144
+ entry.animationToken = run.token;
1145
+ run.finished.then((result) => {
1146
+ if (result === "finished" && entry.status === "exiting" && entry.animationToken === run.token) {
1147
+ this.finishExit(wrapper, entry, context);
1148
+ }
1149
+ });
1150
+ }
1151
+ finishExit(wrapper, entry, context) {
1152
+ const entries = this.entries.get(wrapper);
1153
+ if (entries?.get(entry.key) !== entry) {
1154
+ return;
1155
+ }
1156
+ context.renderer.unmount(entry.vnode, entry.node, context);
1157
+ if (entry.node.parentNode === wrapper) {
1158
+ wrapper.removeChild(entry.node);
1159
+ }
1160
+ entries.delete(entry.key);
1161
+ }
1162
+ placeActiveEntries(wrapper, ordered) {
1163
+ let reference = null;
1164
+ for (let index = ordered.length - 1;index >= 0; index -= 1) {
1165
+ wrapper.insertBefore(ordered[index].node, reference);
1166
+ reference = ordered[index].node;
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ // lib/core/renderer.ts
1172
+ class RendererContext {
1173
+ strategies;
1174
+ constructor() {
1175
+ this.strategies = [
1176
+ new TextRenderStrategy,
1177
+ new ComponentRenderStrategy,
1178
+ new SlotRenderStrategy,
1179
+ new TransitionGroupRenderStrategy,
1180
+ new ElementRenderStrategy
1181
+ ];
1182
+ }
1183
+ mount(vnode, context) {
1184
+ return this.findStrategy(vnode).mount(vnode, context);
1185
+ }
1186
+ patch(oldVNode, newVNode, currentNode, context) {
1187
+ const oldStrategy = this.findStrategy(oldVNode);
1188
+ const newStrategy = this.findStrategy(newVNode);
1189
+ if (oldStrategy !== newStrategy) {
1190
+ const nextNode = newStrategy.mount(newVNode, context);
1191
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1192
+ oldStrategy.unmount(oldVNode, currentNode, context);
1193
+ return nextNode;
1194
+ }
1195
+ return oldStrategy.patch(oldVNode, newVNode, currentNode, context);
1196
+ }
1197
+ unmount(vnode, currentNode, context) {
1198
+ this.findStrategy(vnode).unmount(vnode, currentNode, context);
1199
+ }
1200
+ findStrategy(vnode) {
1201
+ const strategy = this.strategies.find((item) => item.matches(vnode));
1202
+ if (!strategy) {
1203
+ throw new Error("No render strategy found for vnode");
1204
+ }
1205
+ return strategy;
1206
+ }
1207
+ }
1208
+
1209
+ class TextRenderStrategy {
1210
+ matches(vnode) {
1211
+ return typeof vnode === "string";
1212
+ }
1213
+ mount(vnode, context) {
1214
+ return context.templateEngine.parseTemplate(vnode);
1215
+ }
1216
+ patch(oldVNode, newVNode, currentNode, context) {
1217
+ if (oldVNode === newVNode) {
1218
+ return currentNode;
1219
+ }
1220
+ const nextNode = this.mount(newVNode, context);
1221
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1222
+ return nextNode;
1223
+ }
1224
+ unmount() {}
1225
+ }
1226
+
1227
+ class ComponentRenderStrategy {
1228
+ instances = new WeakMap;
1229
+ instanceNodes = new Map;
1230
+ emitterUnsubscribers = new WeakMap;
1231
+ matches(vnode) {
1232
+ return typeof vnode === "object" && vnode !== null && isComponentNode(vnode);
1233
+ }
1234
+ mount(vnode, context) {
1235
+ if (vnode.directions?.if === false) {
1236
+ return document.createComment("if");
1237
+ }
1238
+ const ComponentClass = vnode.component;
1239
+ const instance = new ComponentClass(this.createProps(vnode));
1240
+ if (context.appContext && instance.setAppContext) {
1241
+ instance.setAppContext(context.appContext);
1242
+ }
1243
+ this.syncEmitters(instance, vnode.emitters ?? {});
1244
+ context.registerChild(instance);
1245
+ const node = instance.mountToNode();
1246
+ this.trackInstanceNode(instance, node);
1247
+ instance.setElementChangeListener?.((previousNode, nextNode) => {
1248
+ this.trackInstanceNode(instance, previousNode);
1249
+ this.trackInstanceNode(instance, nextNode);
1250
+ });
1251
+ return node;
1252
+ }
1253
+ patch(oldVNode, newVNode, currentNode, context) {
1254
+ if (currentNode.nodeType === Node.COMMENT_NODE) {
1255
+ const nextNode2 = this.mount(newVNode, context);
1256
+ currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1257
+ return nextNode2;
1258
+ }
1259
+ if (newVNode.directions?.if === false) {
1260
+ const nextNode2 = document.createComment("if");
1261
+ currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1262
+ this.unmount(oldVNode, currentNode, context);
1263
+ return nextNode2;
1264
+ }
1265
+ const instance = this.instances.get(currentNode);
1266
+ if (instance && oldVNode.component === newVNode.component) {
1267
+ this.syncEmitters(instance, newVNode.emitters ?? {});
1268
+ instance.setProps(this.createProps(newVNode));
1269
+ const nextNode2 = instance.getElement() ?? currentNode;
1270
+ this.trackInstanceNode(instance, nextNode2);
1271
+ return nextNode2;
1272
+ }
1273
+ const nextNode = this.mount(newVNode, context);
1274
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1275
+ this.unmount(oldVNode, currentNode, context);
1276
+ return nextNode;
1277
+ }
1278
+ unmount(_vnode, currentNode, context) {
1279
+ const instance = this.instances.get(currentNode);
1280
+ if (instance) {
1281
+ this.clearEmitters(instance);
1282
+ instance.unmount();
1283
+ this.clearInstanceNodes(instance);
1284
+ context.unregisterChild(instance);
1285
+ }
1286
+ }
1287
+ createProps(vnode) {
1288
+ return {
1289
+ ...vnode.props ?? {},
1290
+ children: vnode.children ?? []
1291
+ };
1292
+ }
1293
+ syncEmitters(instance, emitters) {
1294
+ const current = this.emitterUnsubscribers.get(instance) ?? new Map;
1295
+ current.forEach(({ listener: currentListener, unsubscribe }, eventName) => {
1296
+ const listener = emitters[eventName];
1297
+ if (!listener || listener !== currentListener) {
1298
+ unsubscribe();
1299
+ current.delete(eventName);
1300
+ }
1301
+ });
1302
+ Object.entries(emitters).forEach(([eventName, listener]) => {
1303
+ if (current.get(eventName)?.listener === listener) {
827
1304
  return;
828
1305
  }
829
- return value[key];
830
- }, context.templateEngine.state);
1306
+ current.set(eventName, {
1307
+ listener,
1308
+ unsubscribe: instance.on(eventName, listener)
1309
+ });
1310
+ });
1311
+ this.emitterUnsubscribers.set(instance, current);
831
1312
  }
832
- trackEffect(element, effectRef) {
833
- const effects = this.effects.get(element) ?? new Set;
834
- effects.add(effectRef);
835
- this.effects.set(element, effects);
1313
+ clearEmitters(instance) {
1314
+ this.emitterUnsubscribers.get(instance)?.forEach(({ unsubscribe }) => {
1315
+ unsubscribe();
1316
+ });
1317
+ this.emitterUnsubscribers.delete(instance);
1318
+ }
1319
+ trackInstanceNode(instance, node) {
1320
+ this.instances.set(node, instance);
1321
+ const nodes = this.instanceNodes.get(instance) ?? new Set;
1322
+ nodes.add(node);
1323
+ this.instanceNodes.set(instance, nodes);
1324
+ }
1325
+ clearInstanceNodes(instance) {
1326
+ this.instanceNodes.get(instance)?.forEach((node) => {
1327
+ this.instances.delete(node);
1328
+ });
1329
+ this.instanceNodes.delete(instance);
1330
+ }
1331
+ }
1332
+
1333
+ class SlotRenderStrategy {
1334
+ renderedChildren = new WeakMap;
1335
+ matches(vnode) {
1336
+ return typeof vnode === "object" && vnode !== null && isSlotProvider(vnode);
1337
+ }
1338
+ mount(vnode, context) {
1339
+ if (vnode.directions?.if === false) {
1340
+ return document.createComment("if");
1341
+ }
1342
+ const slotContainer = document.createElement("div");
1343
+ slotContainer.setAttribute("data-slot", vnode.props.name);
1344
+ this.mountSlotChildren(slotContainer, this.resolveChildren(vnode, context), context);
1345
+ return slotContainer;
1346
+ }
1347
+ patch(oldVNode, newVNode, currentNode, context) {
1348
+ if (currentNode.nodeType === Node.COMMENT_NODE) {
1349
+ const nextNode = this.mount(newVNode, context);
1350
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1351
+ return nextNode;
1352
+ }
1353
+ if (newVNode.directions?.if === false) {
1354
+ const nextNode = document.createComment("if");
1355
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1356
+ this.unmount(oldVNode, currentNode, context);
1357
+ return nextNode;
1358
+ }
1359
+ if (currentNode instanceof HTMLElement) {
1360
+ currentNode.setAttribute("data-slot", newVNode.props.name);
1361
+ this.replaceSlotChildren(currentNode, oldVNode, newVNode, context);
1362
+ }
1363
+ return currentNode;
1364
+ }
1365
+ unmount(_vnode, currentNode, context) {
1366
+ if (!(currentNode instanceof HTMLElement)) {
1367
+ return;
1368
+ }
1369
+ this.unmountSlotChildren(currentNode, context);
1370
+ this.renderedChildren.delete(currentNode);
1371
+ }
1372
+ resolveChildren(vnode, context) {
1373
+ return context.slots[vnode.props.name] ?? vnode.children ?? [];
1374
+ }
1375
+ replaceSlotChildren(element, _oldVNode, newVNode, context) {
1376
+ this.unmountSlotChildren(element, context);
1377
+ element.textContent = "";
1378
+ this.mountSlotChildren(element, this.resolveChildren(newVNode, context), context);
1379
+ }
1380
+ mountSlotChildren(element, children, context) {
1381
+ children.forEach((child) => {
1382
+ element.appendChild(context.renderer.mount(child, context));
1383
+ });
1384
+ this.renderedChildren.set(element, children);
1385
+ }
1386
+ unmountSlotChildren(element, context) {
1387
+ const children = this.renderedChildren.get(element) ?? [];
1388
+ children.forEach((child, index) => {
1389
+ const childNode = element.childNodes[index];
1390
+ if (childNode) {
1391
+ context.renderer.unmount(child, childNode, context);
1392
+ }
1393
+ });
836
1394
  }
837
1395
  }
838
1396
 
@@ -889,8 +1447,8 @@ class TemplateEngine {
889
1447
  if (key) {
890
1448
  keys.add(key);
891
1449
  const value = this.getValueFromState(key);
892
- const displayValue = value === undefined || value === null ? "" : String(value);
893
- result = result.replace(match[0], displayValue);
1450
+ const displayValue2 = value === undefined || value === null ? "" : String(value);
1451
+ result = result.replace(match[0], displayValue2);
894
1452
  }
895
1453
  });
896
1454
  return result;
@@ -910,7 +1468,7 @@ class TemplateEngine {
910
1468
  }
911
1469
  clearBindings() {
912
1470
  this.bindings.forEach((binding) => {
913
- binding.effect.active = false;
1471
+ stop(binding.effect);
914
1472
  });
915
1473
  this.bindings = [];
916
1474
  }
@@ -953,8 +1511,11 @@ class Component {
953
1511
  templateEngine;
954
1512
  childComponents = new Set;
955
1513
  eventListeners = {};
1514
+ providers = new Map;
956
1515
  updateEffect;
957
1516
  appContext = null;
1517
+ parentComponent = null;
1518
+ elementChangeListener = null;
958
1519
  styleManager;
959
1520
  state;
960
1521
  mounted = false;
@@ -969,7 +1530,7 @@ class Component {
969
1530
  if (this.mounted) {
970
1531
  this.update();
971
1532
  }
972
- });
1533
+ }, { throwOnError: true });
973
1534
  }
974
1535
  mount(container) {
975
1536
  if (!container || !(container instanceof HTMLElement)) {
@@ -997,15 +1558,15 @@ class Component {
997
1558
  if (!this.el || !this.vnode) {
998
1559
  return;
999
1560
  }
1000
- try {
1001
- this.beforeUpdate();
1002
- const newVNode = this.render();
1003
- this.el = this.renderer.patch(this.vnode, newVNode, this.el, this.createRenderContext());
1004
- this.vnode = newVNode;
1005
- this.onUpdated();
1006
- } catch (error) {
1007
- console.error("组件更新错误:", error);
1561
+ this.beforeUpdate();
1562
+ const newVNode = this.render();
1563
+ const previousElement = this.el;
1564
+ this.el = this.renderer.patch(this.vnode, newVNode, this.el, this.createRenderContext());
1565
+ if (previousElement !== this.el) {
1566
+ this.elementChangeListener?.(previousElement, this.el);
1008
1567
  }
1568
+ this.vnode = newVNode;
1569
+ this.onUpdated();
1009
1570
  }
1010
1571
  unmount() {
1011
1572
  if (!this.mounted) {
@@ -1016,8 +1577,15 @@ class Component {
1016
1577
  this.renderer.unmount(this.vnode, this.el, this.createRenderContext());
1017
1578
  }
1018
1579
  this.childComponents.clear();
1580
+ Object.keys(this.eventListeners).forEach((eventName) => {
1581
+ this.eventListeners[eventName].clear();
1582
+ delete this.eventListeners[eventName];
1583
+ });
1584
+ this.providers.clear();
1585
+ this.parentComponent = null;
1586
+ this.elementChangeListener = null;
1019
1587
  this.templateEngine.clearBindings();
1020
- this.styleManager.clearStyles();
1588
+ this.styleManager.destroy();
1021
1589
  stop(this.updateEffect);
1022
1590
  if (this.el?.parentNode) {
1023
1591
  this.el.parentNode.removeChild(this.el);
@@ -1045,6 +1613,28 @@ class Component {
1045
1613
  child.setAppContext?.(context);
1046
1614
  });
1047
1615
  }
1616
+ setParentComponent(parent) {
1617
+ this.parentComponent = parent;
1618
+ }
1619
+ setElementChangeListener(listener) {
1620
+ this.elementChangeListener = listener;
1621
+ }
1622
+ provide(key, value) {
1623
+ this.providers.set(key, value);
1624
+ }
1625
+ inject(key, fallback) {
1626
+ const result = this.resolveInjection(key);
1627
+ return result.found ? result.value : fallback;
1628
+ }
1629
+ resolveInjection(key) {
1630
+ if (this.providers.has(key)) {
1631
+ return { found: true, value: this.providers.get(key) };
1632
+ }
1633
+ if (this.parentComponent?.resolveInjection) {
1634
+ return this.parentComponent.resolveInjection(key);
1635
+ }
1636
+ return this.resolveAppInjection(key);
1637
+ }
1048
1638
  getElement() {
1049
1639
  return this.el;
1050
1640
  }
@@ -1070,6 +1660,7 @@ class Component {
1070
1660
  this.eventListeners[eventName] = new Set;
1071
1661
  }
1072
1662
  this.eventListeners[eventName].add(listener);
1663
+ return () => this.off(eventName, listener);
1073
1664
  }
1074
1665
  off(eventName, listener) {
1075
1666
  this.eventListeners[eventName]?.delete(listener);
@@ -1082,7 +1673,12 @@ class Component {
1082
1673
  slots: this.collectSlots(),
1083
1674
  registerChild: (component) => {
1084
1675
  this.childComponents.add(component);
1676
+ component.setParentComponent?.(this);
1085
1677
  component.setAppContext?.(this.appContext);
1678
+ },
1679
+ unregisterChild: (component) => {
1680
+ this.childComponents.delete(component);
1681
+ component.setParentComponent?.(null);
1086
1682
  }
1087
1683
  };
1088
1684
  }
@@ -1125,6 +1721,13 @@ class Component {
1125
1721
  const globalApp = globalThis.__APP__;
1126
1722
  return this.getRouterFrom(globalApp);
1127
1723
  }
1724
+ resolveAppInjection(key) {
1725
+ if (!this.appContext || typeof this.appContext !== "object") {
1726
+ return { found: false, value: undefined };
1727
+ }
1728
+ const app = this.appContext.app;
1729
+ return app?.resolveInjection?.(key) ?? { found: false, value: undefined };
1730
+ }
1128
1731
  trackReactiveValue(value, seen) {
1129
1732
  if (!value || typeof value !== "object" || seen.has(value)) {
1130
1733
  return;
@@ -1147,6 +1750,12 @@ function setRouter(r) {
1147
1750
  }
1148
1751
  router = r;
1149
1752
  }
1753
+ function useRouter() {
1754
+ if (!router) {
1755
+ throw new Error("Router is not initialized. Please make sure you have installed the router plugin.");
1756
+ }
1757
+ return router;
1758
+ }
1150
1759
 
1151
1760
  // lib/router/matcher.ts
1152
1761
  function normalizePath(path) {
@@ -1207,13 +1816,18 @@ function createRouterHref(path, mode, base) {
1207
1816
  function getBrowserLocation(mode, base) {
1208
1817
  let path;
1209
1818
  let fullPath;
1819
+ let queryString;
1210
1820
  if (mode === "history") {
1211
1821
  fullPath = window.location.pathname + window.location.search;
1212
1822
  path = window.location.pathname;
1823
+ queryString = window.location.search;
1213
1824
  } else {
1214
1825
  const hash = window.location.hash;
1215
1826
  fullPath = hash || "#/";
1216
1827
  path = fullPath.startsWith("#") ? fullPath.slice(1) : fullPath;
1828
+ const queryStart = path.indexOf("?");
1829
+ queryString = queryStart >= 0 ? path.slice(queryStart + 1) : "";
1830
+ path = queryStart >= 0 ? path.slice(0, queryStart) : path;
1217
1831
  }
1218
1832
  if (path.startsWith(base) && base !== "/" && path !== "/") {
1219
1833
  path = path.slice(base.length);
@@ -1222,7 +1836,7 @@ function getBrowserLocation(mode, base) {
1222
1836
  return {
1223
1837
  path,
1224
1838
  fullPath,
1225
- query: parseQuery(mode === "hash" ? path.split("?")[1] ?? "" : window.location.search),
1839
+ query: parseQuery(queryString),
1226
1840
  params: {}
1227
1841
  };
1228
1842
  }
@@ -1492,7 +2106,7 @@ function createRouter(options) {
1492
2106
  return new Router(options);
1493
2107
  }
1494
2108
 
1495
- export { ReactiveSystem, reactive, readonly, effect, computed, stop, isReactive, isReadonly, TemplateEngine, isComponentNode, isHTMLNode, isSlotProvider, h, createComponent, slot, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, ElementRenderStrategy, Component, Router, RouterLink, RouterView, createRouter };
2109
+ export { 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, ReactiveSystem, reactive, readonly, effect, computed, ref, isRef, unref, stop, isReactive, isReadonly, modelPath, getModelValue, setModelValue, ModelBindingController, ElementRenderStrategy, normalizeTransitionGroupProps, validateTransitionGroupChildren, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, TemplateEngine, Component, useRouter, Router, RouterLink, RouterView, createRouter };
1496
2110
 
1497
- //# debugId=97822D95A36A4F3E64756E2164756E21
1498
- //# sourceMappingURL=index-3j2jsdpc.js.map
2111
+ //# debugId=91AF0EEFB67A584364756E2164756E21
2112
+ //# sourceMappingURL=index-wv9gyjqt.js.map