olova 2.0.67 → 2.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.js CHANGED
@@ -1,9 +1,180 @@
1
- import { effect as effect$1 } from '@preact/signals-core';
1
+ import { signal, effect as effect$1 } from '@preact/signals-core';
2
2
 
3
3
  // core/signals-core.ts
4
+ var rawToProxyByOwner = /* @__PURE__ */ new WeakMap();
5
+ var proxyToRaw = /* @__PURE__ */ new WeakMap();
6
+ var proxyOwner = /* @__PURE__ */ new WeakMap();
4
7
  var hmrCaptureStack = [];
5
- function effect(fn, options) {
6
- const stop = effect$1(() => fn());
8
+ var runtimeErrorHandlerStack = [];
9
+ function isObject(value) {
10
+ return typeof value === "object" && value !== null;
11
+ }
12
+ function unwrapValue(value) {
13
+ if (!isObject(value)) {
14
+ return value;
15
+ }
16
+ return proxyToRaw.get(value) ?? value;
17
+ }
18
+ var Signal = class {
19
+ source;
20
+ version;
21
+ proxy = null;
22
+ constructor(initial, source, version) {
23
+ this.source = source ?? signal(initial);
24
+ this.version = version ?? signal(0);
25
+ }
26
+ get value() {
27
+ this.version.value;
28
+ return this.getReactiveValue(this.source.value);
29
+ }
30
+ set value(next) {
31
+ const resolvedNext = unwrapValue(next);
32
+ if (Object.is(this.source.peek(), resolvedNext)) {
33
+ return;
34
+ }
35
+ this.proxy = null;
36
+ this.source.value = resolvedNext;
37
+ }
38
+ peek() {
39
+ return this.source.peek();
40
+ }
41
+ notify() {
42
+ this.version.value = this.version.peek() + 1;
43
+ }
44
+ peekVersion() {
45
+ return this.version.peek();
46
+ }
47
+ subscribe(fn) {
48
+ return this.source.subscribe(fn);
49
+ }
50
+ valueOf() {
51
+ return this.value;
52
+ }
53
+ toString() {
54
+ return String(this.value);
55
+ }
56
+ toJSON() {
57
+ return this.peek();
58
+ }
59
+ getReactiveValue(value) {
60
+ if (!isObject(value)) {
61
+ return value;
62
+ }
63
+ if (this.proxy && proxyToRaw.get(this.proxy) === value) {
64
+ return this.proxy;
65
+ }
66
+ this.proxy = this.wrapObject(value);
67
+ return this.proxy;
68
+ }
69
+ wrapObject(value) {
70
+ const ownedProxyMap = rawToProxyByOwner.get(value);
71
+ const existing = ownedProxyMap?.get(this);
72
+ if (existing) {
73
+ return existing;
74
+ }
75
+ const proxy = new Proxy(value, {
76
+ get: (target, key, receiver) => {
77
+ const result = Reflect.get(target, key, receiver);
78
+ return isObject(result) ? this.wrapNested(result) : result;
79
+ },
80
+ set: (target, key, next, receiver) => {
81
+ const resolvedNext = unwrapValue(next);
82
+ const previous = Reflect.get(target, key, receiver);
83
+ const changed = Reflect.set(target, key, resolvedNext, receiver);
84
+ if (changed && !Object.is(previous, resolvedNext)) {
85
+ this.notify();
86
+ }
87
+ return changed;
88
+ },
89
+ deleteProperty: (target, key) => {
90
+ const hadKey = Reflect.has(target, key);
91
+ const changed = Reflect.deleteProperty(target, key);
92
+ if (hadKey && changed) {
93
+ this.notify();
94
+ }
95
+ return changed;
96
+ }
97
+ });
98
+ const nextOwnedProxyMap = ownedProxyMap ?? /* @__PURE__ */ new WeakMap();
99
+ nextOwnedProxyMap.set(this, proxy);
100
+ if (!ownedProxyMap) {
101
+ rawToProxyByOwner.set(value, nextOwnedProxyMap);
102
+ }
103
+ proxyToRaw.set(proxy, value);
104
+ proxyOwner.set(proxy, this);
105
+ return proxy;
106
+ }
107
+ wrapNested(value) {
108
+ const owner = proxyOwner.get(value);
109
+ if (owner === this) {
110
+ return value;
111
+ }
112
+ return this.wrapObject(value);
113
+ }
114
+ };
115
+ function state(initial, hmrKey) {
116
+ const frame = hmrCaptureStack[hmrCaptureStack.length - 1];
117
+ let nextInitial = initial;
118
+ if (frame) {
119
+ if (frame.cursor < frame.restoredUnkeyed.length) {
120
+ nextInitial = frame.restoredUnkeyed[frame.cursor];
121
+ }
122
+ }
123
+ const signal = new Signal(nextInitial);
124
+ if (frame) {
125
+ frame.created.push({ key: hmrKey, signal });
126
+ {
127
+ frame.cursor += 1;
128
+ }
129
+ }
130
+ return signal;
131
+ }
132
+ function getReactiveProxyVersion(value) {
133
+ if (!isObject(value)) {
134
+ return null;
135
+ }
136
+ const owner = proxyOwner.get(value);
137
+ return owner ? owner.peekVersion() : null;
138
+ }
139
+ function reportRuntimeError(error, handler) {
140
+ if (handler) {
141
+ handler(error);
142
+ return;
143
+ }
144
+ throw error;
145
+ }
146
+ function withRuntimeErrorHandler(handler, fn) {
147
+ if (!handler) {
148
+ return fn();
149
+ }
150
+ runtimeErrorHandlerStack.push(handler);
151
+ try {
152
+ return fn();
153
+ } finally {
154
+ runtimeErrorHandlerStack.pop();
155
+ }
156
+ }
157
+ function effect(fn) {
158
+ const handler = runtimeErrorHandlerStack[runtimeErrorHandlerStack.length - 1] ?? null;
159
+ const stop = effect$1(
160
+ () => withRuntimeErrorHandler(handler, () => {
161
+ try {
162
+ const cleanup = fn();
163
+ if (typeof cleanup !== "function") {
164
+ return;
165
+ }
166
+ return () => withRuntimeErrorHandler(handler, () => {
167
+ try {
168
+ cleanup();
169
+ } catch (error) {
170
+ reportRuntimeError(error, handler);
171
+ }
172
+ });
173
+ } catch (error) {
174
+ reportRuntimeError(error, handler);
175
+ }
176
+ })
177
+ );
7
178
  return () => stop();
8
179
  }
9
180
  function beginHmrStateCapture(restoredValues) {
@@ -32,6 +203,40 @@ function endHmrStateCapture() {
32
203
  // runtime/component.ts
33
204
  var EMPTY_SLOTS = Object.freeze({});
34
205
  var currentSetupContext = null;
206
+ var currentLifecycleHooks = null;
207
+ var ERROR_BOUNDARY = /* @__PURE__ */ Symbol("olova.error-boundary");
208
+ var SUSPENSE_BOUNDARY = /* @__PURE__ */ Symbol("olova.suspense-boundary");
209
+ function isPromiseLike(value) {
210
+ return !!value && typeof value === "object" && "then" in value;
211
+ }
212
+ function toOlovaError(error, context) {
213
+ if (error instanceof Error && error.message.startsWith("[olova]")) {
214
+ return error;
215
+ }
216
+ const original = error instanceof Error ? error : new Error(String(error));
217
+ const wrapped = new Error(`[olova] ${context}: ${original.message}`);
218
+ wrapped.cause = error;
219
+ return wrapped;
220
+ }
221
+ function getBoundaryHandler(context) {
222
+ return context?.get(ERROR_BOUNDARY) ?? null;
223
+ }
224
+ function handleRuntimeError(error, contextMessage, context) {
225
+ const normalized = toOlovaError(error, contextMessage);
226
+ const boundaryHandler = getBoundaryHandler(context);
227
+ if (boundaryHandler) {
228
+ boundaryHandler(normalized);
229
+ return;
230
+ }
231
+ throw normalized;
232
+ }
233
+ function runWithRuntimeErrorHandler(context, action, fn) {
234
+ try {
235
+ return withRuntimeErrorHandler(getBoundaryHandler(context), fn);
236
+ } catch (error) {
237
+ return handleRuntimeError(error, action, context);
238
+ }
239
+ }
35
240
  function requireSetupContext(apiName) {
36
241
  if (!currentSetupContext) {
37
242
  throw new Error(
@@ -90,12 +295,12 @@ function sanitizeHtml(html) {
90
295
  }
91
296
  for (const attr of Array.from(element.attributes)) {
92
297
  const attrName = attr.name.toLowerCase();
93
- const attrValue = attr.value.trim().toLowerCase();
298
+ const attrValue = attr.value.trim();
94
299
  if (attrName.startsWith("on")) {
95
300
  element.removeAttribute(attr.name);
96
301
  continue;
97
302
  }
98
- if ((attrName === "href" || attrName === "src" || attrName === "xlink:href") && attrValue.startsWith("javascript:")) {
303
+ if ((attrName === "href" || attrName === "src" || attrName === "xlink:href") && !isSafeUrl(attrName, attrValue)) {
99
304
  element.removeAttribute(attr.name);
100
305
  }
101
306
  }
@@ -114,7 +319,27 @@ function toHtml(value) {
114
319
  }
115
320
  return sanitizeHtml(String(value));
116
321
  }
117
- function shallowEqual(a, b) {
322
+ var SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
323
+ var SAFE_IMAGE_DATA_URL_RE = /^data:image\/(?:png|gif|jpe?g|webp|avif|bmp);(?:charset=[^;,]+;)?base64,[a-z0-9+/=\s]+$/i;
324
+ function isSafeUrl(attrName, value) {
325
+ const normalized = value.replace(/[\u0000-\u001F\u007F\s]+/g, "");
326
+ if (!normalized) {
327
+ return true;
328
+ }
329
+ if (normalized.startsWith("#") || normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../") || normalized.startsWith("?")) {
330
+ return true;
331
+ }
332
+ const match = normalized.match(/^([a-z0-9+.-]+):/i);
333
+ if (!match) {
334
+ return true;
335
+ }
336
+ const protocol = match[1].toLowerCase();
337
+ if (SAFE_URL_PROTOCOLS.has(protocol)) {
338
+ return true;
339
+ }
340
+ return protocol === "data" && attrName === "src" && SAFE_IMAGE_DATA_URL_RE.test(normalized);
341
+ }
342
+ function shallowEqualSlots(a, b) {
118
343
  if (a === b) {
119
344
  return true;
120
345
  }
@@ -133,20 +358,39 @@ function shallowEqual(a, b) {
133
358
  }
134
359
  return true;
135
360
  }
136
- function shallowEqualSlots(a, b) {
137
- if (a === b) {
361
+ function createComparablePropsSnapshot(props) {
362
+ return Object.fromEntries(
363
+ Object.entries(props).map(([key, value]) => [
364
+ key,
365
+ {
366
+ value,
367
+ reactiveVersion: getReactiveProxyVersion(value)
368
+ }
369
+ ])
370
+ );
371
+ }
372
+ function shallowComparablePropsEqual(snapshot, props) {
373
+ if (!snapshot && !props) {
138
374
  return true;
139
375
  }
140
- if (!a || !b) {
376
+ if (!snapshot || !props) {
141
377
  return false;
142
378
  }
143
- const aKeys = Object.keys(a);
144
- const bKeys = Object.keys(b);
145
- if (aKeys.length !== bKeys.length) {
379
+ const keys = Object.keys(snapshot);
380
+ const propKeys = Object.keys(props);
381
+ if (keys.length !== propKeys.length) {
146
382
  return false;
147
383
  }
148
- for (const key of aKeys) {
149
- if (!Object.is(a[key], b[key])) {
384
+ for (const key of keys) {
385
+ if (!(key in props)) {
386
+ return false;
387
+ }
388
+ const entry = snapshot[key];
389
+ const nextValue = props[key];
390
+ if (!Object.is(entry.value, nextValue)) {
391
+ return false;
392
+ }
393
+ if (entry.reactiveVersion !== getReactiveProxyVersion(nextValue)) {
150
394
  return false;
151
395
  }
152
396
  }
@@ -179,7 +423,7 @@ function bindJsxEventsInRange(range) {
179
423
  const listeners = [];
180
424
  const handlerIds = /* @__PURE__ */ new Set();
181
425
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
182
- let current = walker.currentNode;
426
+ let current = walker.nextNode();
183
427
  while (current) {
184
428
  let intersects = false;
185
429
  try {
@@ -299,6 +543,42 @@ function createRangeAtTokenWithinRange(hostRange, token) {
299
543
  function resolveTokenRange(target, token) {
300
544
  return target instanceof HTMLElement ? createRangeAtToken(target, token) : createRangeAtTokenWithinRange(target, token);
301
545
  }
546
+ function createLifecycleHooks() {
547
+ return {
548
+ mount: [],
549
+ cleanup: []
550
+ };
551
+ }
552
+ function flushLifecycleHooks(hooks, context) {
553
+ for (const mountHook of hooks.mount) {
554
+ const cleanup = runWithRuntimeErrorHandler(
555
+ context,
556
+ "Failed to run onMount() callback",
557
+ mountHook
558
+ );
559
+ if (typeof cleanup === "function") {
560
+ hooks.cleanup.push(cleanup);
561
+ }
562
+ }
563
+ return () => {
564
+ for (let index = hooks.cleanup.length - 1; index >= 0; index -= 1) {
565
+ runWithRuntimeErrorHandler(
566
+ context,
567
+ "Failed to run cleanup callback",
568
+ hooks.cleanup[index]
569
+ );
570
+ }
571
+ };
572
+ }
573
+ function createDescriptorEffect(context, fn) {
574
+ const stop = runWithRuntimeErrorHandler(
575
+ context,
576
+ "Failed to create reactive binding",
577
+ () => effect(fn)
578
+ );
579
+ return typeof stop === "function" ? stop : () => {
580
+ };
581
+ }
302
582
  function mountDescriptor(descriptor, target, slots, context) {
303
583
  let fragment;
304
584
  let nodes = null;
@@ -318,7 +598,7 @@ function mountDescriptor(descriptor, target, slots, context) {
318
598
  if (!textNode) {
319
599
  continue;
320
600
  }
321
- const stop = effect(() => {
601
+ const stop = createDescriptorEffect(context, () => {
322
602
  textNode.data = toText(binding.get());
323
603
  });
324
604
  cleanups.push(stop);
@@ -329,7 +609,7 @@ function mountDescriptor(descriptor, target, slots, context) {
329
609
  (element) => element.getAttribute(binding.attr) === token
330
610
  );
331
611
  for (const element of elements) {
332
- const stop = effect(() => {
612
+ const stop = createDescriptorEffect(context, () => {
333
613
  const next = binding.attr === "class" ? normalizeClassValue(binding.get()) : toAttr(binding.get());
334
614
  if (next === null) {
335
615
  element.removeAttribute(binding.attr);
@@ -348,7 +628,7 @@ function mountDescriptor(descriptor, target, slots, context) {
348
628
  }
349
629
  if (!nodes) element.removeAttribute(`data-o-on-${binding.event}`);
350
630
  let currentHandler;
351
- const stop = effect(() => {
631
+ const stop = createDescriptorEffect(context, () => {
352
632
  currentHandler = binding.get();
353
633
  });
354
634
  const listener = (event) => {
@@ -384,7 +664,7 @@ function mountDescriptor(descriptor, target, slots, context) {
384
664
  let lastHtml = "__OLOVA_INIT__";
385
665
  let cleanupJsxEvents = () => {
386
666
  };
387
- const stop = effect(() => {
667
+ const stop = createDescriptorEffect(context, () => {
388
668
  const nextHtml = toHtml(binding.get());
389
669
  if (nextHtml === lastHtml) {
390
670
  return;
@@ -420,7 +700,15 @@ function mountDescriptor(descriptor, target, slots, context) {
420
700
  range.deleteContents();
421
701
  continue;
422
702
  }
423
- const slotDescriptor = slotFactory();
703
+ const slotDescriptor = runWithRuntimeErrorHandler(
704
+ context,
705
+ `Failed to render slot "${binding.name}"`,
706
+ slotFactory
707
+ );
708
+ if (!slotDescriptor) {
709
+ range.deleteContents();
710
+ continue;
711
+ }
424
712
  const disposeSlot = mountDescriptor(slotDescriptor, range, slots, context);
425
713
  cleanups.push(disposeSlot);
426
714
  }
@@ -443,17 +731,17 @@ function mountDescriptor(descriptor, target, slots, context) {
443
731
  let lastComponent = null;
444
732
  let lastProps;
445
733
  let lastSlots;
446
- const stop = effect(() => {
734
+ const stop = createDescriptorEffect(context, () => {
447
735
  const nextComponent = binding.getComponent();
448
736
  const nextProps = binding.getProps();
449
737
  const nextSlots = binding.getSlots ? binding.getSlots() : EMPTY_SLOTS;
450
- if (lastComponent === nextComponent && shallowEqual(lastProps, nextProps) && shallowEqualSlots(lastSlots, nextSlots)) {
738
+ if (lastComponent === nextComponent && shallowComparablePropsEqual(lastProps, nextProps) && shallowEqualSlots(lastSlots, nextSlots)) {
451
739
  return;
452
740
  }
453
741
  disposeChild();
454
742
  disposeChild = mount(nextComponent, range, nextProps, nextSlots, context);
455
743
  lastComponent = nextComponent;
456
- lastProps = { ...nextProps };
744
+ lastProps = createComparablePropsSnapshot(nextProps);
457
745
  lastSlots = nextSlots;
458
746
  });
459
747
  cleanups.push(() => {
@@ -479,17 +767,39 @@ function mountDescriptor(descriptor, target, slots, context) {
479
767
  let disposeBranch = () => {
480
768
  };
481
769
  let lastCondition = void 0;
482
- const stop = effect(() => {
770
+ const stop = createDescriptorEffect(context, () => {
483
771
  const condition = !!binding.get();
484
772
  if (condition === lastCondition) {
485
773
  return;
486
774
  }
487
775
  disposeBranch();
488
776
  if (condition) {
489
- const branchDescriptor = binding.trueBranch();
777
+ const branchDescriptor = runWithRuntimeErrorHandler(
778
+ context,
779
+ "Failed to render conditional branch",
780
+ binding.trueBranch
781
+ );
782
+ if (!branchDescriptor) {
783
+ range.deleteContents();
784
+ disposeBranch = () => {
785
+ };
786
+ lastCondition = condition;
787
+ return;
788
+ }
490
789
  disposeBranch = mountDescriptor(branchDescriptor, range, slots, context);
491
790
  } else if (binding.falseBranch) {
492
- const branchDescriptor = binding.falseBranch();
791
+ const branchDescriptor = runWithRuntimeErrorHandler(
792
+ context,
793
+ "Failed to render conditional fallback branch",
794
+ binding.falseBranch
795
+ );
796
+ if (!branchDescriptor) {
797
+ range.deleteContents();
798
+ disposeBranch = () => {
799
+ };
800
+ lastCondition = condition;
801
+ return;
802
+ }
493
803
  disposeBranch = mountDescriptor(branchDescriptor, range, slots, context);
494
804
  } else {
495
805
  range.deleteContents();
@@ -525,22 +835,69 @@ function unregisterMountedInstance(instance) {
525
835
  function renderMountedInstance(instance, restoredValues) {
526
836
  const context = new Map(instance.parentContext ?? []);
527
837
  const previousContext = currentSetupContext;
838
+ const previousLifecycleHooks = currentLifecycleHooks;
839
+ const lifecycleHooks = createLifecycleHooks();
528
840
  currentSetupContext = context;
841
+ currentLifecycleHooks = lifecycleHooks;
529
842
  beginHmrStateCapture(restoredValues);
530
- try {
531
- const descriptor = instance.component.setup(instance.props, instance.slots);
532
- instance.disposeDescriptor = mountDescriptor(
843
+ const finalizeRender = (descriptor) => {
844
+ const disposeDescriptor = mountDescriptor(
533
845
  descriptor,
534
846
  instance.target,
535
847
  instance.slots,
536
848
  context
537
849
  );
850
+ const disposeLifecycle = flushLifecycleHooks(lifecycleHooks, context);
851
+ instance.disposeDescriptor = () => {
852
+ disposeLifecycle();
853
+ disposeDescriptor();
854
+ };
855
+ };
856
+ try {
857
+ const descriptor = runWithRuntimeErrorHandler(
858
+ context,
859
+ "Failed to render component setup",
860
+ () => instance.component.setup(instance.props, instance.slots)
861
+ );
862
+ if (!descriptor) {
863
+ instance.disposeDescriptor = () => {
864
+ };
865
+ instance.hmrSignals = endHmrStateCapture();
866
+ return;
867
+ }
868
+ if (isPromiseLike(descriptor)) {
869
+ const suspenseBoundary = context.get(SUSPENSE_BOUNDARY);
870
+ const settleSuspense = suspenseBoundary?.begin() ?? (() => {
871
+ });
872
+ let disposed = false;
873
+ instance.disposeDescriptor = () => {
874
+ disposed = true;
875
+ settleSuspense();
876
+ };
877
+ instance.hmrSignals = endHmrStateCapture();
878
+ descriptor.then((resolvedDescriptor) => {
879
+ if (disposed) {
880
+ return;
881
+ }
882
+ settleSuspense();
883
+ finalizeRender(resolvedDescriptor);
884
+ }).catch((error) => {
885
+ settleSuspense();
886
+ if (disposed) {
887
+ return;
888
+ }
889
+ handleRuntimeError(error, "Failed to resolve async component", context);
890
+ });
891
+ return;
892
+ }
893
+ finalizeRender(descriptor);
538
894
  instance.hmrSignals = endHmrStateCapture();
539
895
  } catch (error) {
540
896
  endHmrStateCapture();
541
897
  throw error;
542
898
  } finally {
543
899
  currentSetupContext = previousContext;
900
+ currentLifecycleHooks = previousLifecycleHooks;
544
901
  }
545
902
  }
546
903
  function collectHmrSignalValues(instance) {
@@ -584,6 +941,124 @@ function replaceComponent(current, next) {
584
941
  function defineComponent(setup, hmrId) {
585
942
  return { setup, __hmrId: hmrId };
586
943
  }
944
+ function isOlovaComponent(value) {
945
+ return typeof value === "object" && value !== null && "setup" in value;
946
+ }
947
+ function onMount(fn) {
948
+ if (!currentLifecycleHooks) {
949
+ throw new Error("[olova] onMount() can only be used during component setup.");
950
+ }
951
+ currentLifecycleHooks.mount.push(fn);
952
+ }
953
+ function onCleanup(fn) {
954
+ if (!currentLifecycleHooks) {
955
+ throw new Error("[olova] onCleanup() can only be used during component setup.");
956
+ }
957
+ currentLifecycleHooks.cleanup.push(fn);
958
+ }
959
+ function createErrorBoundary(component, fallback) {
960
+ return defineComponent((props, slots) => {
961
+ const errorState = state(null);
962
+ setContext(ERROR_BOUNDARY, (error) => {
963
+ errorState.value = error;
964
+ });
965
+ const renderFallback = () => {
966
+ const error = errorState.value ?? new Error("[olova] Unknown boundary error.");
967
+ if (isOlovaComponent(fallback)) {
968
+ return {
969
+ template: "__O_COMP_boundary_fallback__",
970
+ textBindings: [],
971
+ htmlBindings: [],
972
+ attrBindings: [],
973
+ eventBindings: [],
974
+ slotBindings: [],
975
+ componentBindings: [
976
+ {
977
+ id: "boundary_fallback",
978
+ getComponent: () => fallback,
979
+ getProps: () => ({ error })
980
+ }
981
+ ],
982
+ ifBindings: []
983
+ };
984
+ }
985
+ return fallback(error);
986
+ };
987
+ return {
988
+ template: "__O_IF_boundary__",
989
+ textBindings: [],
990
+ htmlBindings: [],
991
+ attrBindings: [],
992
+ eventBindings: [],
993
+ slotBindings: [],
994
+ componentBindings: [],
995
+ ifBindings: [
996
+ {
997
+ id: "boundary",
998
+ get: () => errorState.value === null,
999
+ trueBranch: () => ({
1000
+ template: "__O_COMP_boundary_child__",
1001
+ textBindings: [],
1002
+ htmlBindings: [],
1003
+ attrBindings: [],
1004
+ eventBindings: [],
1005
+ slotBindings: [],
1006
+ componentBindings: [
1007
+ {
1008
+ id: "boundary_child",
1009
+ getComponent: () => component,
1010
+ getProps: () => props,
1011
+ getSlots: () => slots
1012
+ }
1013
+ ],
1014
+ ifBindings: []
1015
+ }),
1016
+ falseBranch: renderFallback
1017
+ }
1018
+ ]
1019
+ };
1020
+ }, component.__hmrId ? `${component.__hmrId}:boundary` : void 0);
1021
+ }
1022
+ var Suspense = defineComponent((_props, slots) => {
1023
+ const pending = state(0);
1024
+ setContext(SUSPENSE_BOUNDARY, {
1025
+ begin() {
1026
+ pending.value += 1;
1027
+ let settled = false;
1028
+ return () => {
1029
+ if (settled) {
1030
+ return;
1031
+ }
1032
+ settled = true;
1033
+ pending.value = Math.max(0, pending.value - 1);
1034
+ };
1035
+ }
1036
+ });
1037
+ return {
1038
+ template: `<div style="__O_ATTR_default_style__">__O_SLOT_default__</div><div style="__O_ATTR_fallback_style__">__O_SLOT_fallback__</div>`,
1039
+ textBindings: [],
1040
+ htmlBindings: [],
1041
+ attrBindings: [
1042
+ {
1043
+ id: "default_style",
1044
+ attr: "style",
1045
+ get: () => pending.value === 0 ? null : "display:none"
1046
+ },
1047
+ {
1048
+ id: "fallback_style",
1049
+ attr: "style",
1050
+ get: () => pending.value > 0 ? null : "display:none"
1051
+ }
1052
+ ],
1053
+ eventBindings: [],
1054
+ slotBindings: [
1055
+ { id: "default", name: "default" },
1056
+ { id: "fallback", name: "fallback" }
1057
+ ],
1058
+ componentBindings: [],
1059
+ ifBindings: []
1060
+ };
1061
+ });
587
1062
  function dangerouslySetHtml(value) {
588
1063
  return {
589
1064
  __dangerousHtml: true,
@@ -615,7 +1090,12 @@ function mount(component, target, props = {}, slots = EMPTY_SLOTS, parentContext
615
1090
  hmrSignals: []
616
1091
  };
617
1092
  registerMountedInstance(instance);
618
- renderMountedInstance(instance);
1093
+ try {
1094
+ renderMountedInstance(instance);
1095
+ } catch (error) {
1096
+ unregisterMountedInstance(instance);
1097
+ throw toOlovaError(error, "Failed to mount component");
1098
+ }
619
1099
  return () => {
620
1100
  unregisterMountedInstance(instance);
621
1101
  instance.disposeDescriptor();
@@ -626,13 +1106,13 @@ function createApp(root) {
626
1106
  mount(target) {
627
1107
  const element = typeof target === "string" ? document.querySelector(target) : target;
628
1108
  if (!element) {
629
- throw new Error(`Target not found: ${String(target)}`);
1109
+ throw new Error(`[olova] Target not found: ${String(target)}`);
630
1110
  }
631
1111
  return mount(root, element);
632
1112
  }
633
1113
  };
634
1114
  }
635
1115
 
636
- export { createApp, dangerouslySetHtml, defineComponent, getContext, hasContext, mount, replaceComponent, setContext };
1116
+ export { Suspense, createApp, createErrorBoundary, dangerouslySetHtml, defineComponent, getContext, hasContext, mount, onCleanup, onMount, replaceComponent, setContext };
637
1117
  //# sourceMappingURL=runtime.js.map
638
1118
  //# sourceMappingURL=runtime.js.map