sibujs 3.4.0 → 4.0.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 (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
@@ -1,25 +1,33 @@
1
1
  import {
2
- bindAttribute
3
- } from "./chunk-K35BAR4P.js";
2
+ bindAttribute,
3
+ setSafeAttribute
4
+ } from "./chunk-4MFZMLUU.js";
4
5
  import {
5
6
  derived
6
- } from "./chunk-ITWCTZYH.js";
7
+ } from "./chunk-Z2CE42DB.js";
8
+ import {
9
+ isEventHandlerAttr
10
+ } from "./chunk-5INI7D2L.js";
7
11
  import {
8
12
  globalSingleton
9
13
  } from "./chunk-7ZHH77QA.js";
10
14
  import {
11
15
  effect
12
- } from "./chunk-BUKYCWHX.js";
16
+ } from "./chunk-PBQEW5VW.js";
13
17
  import {
14
18
  dispose,
15
- registerDisposer
16
- } from "./chunk-5VGSK6D2.js";
19
+ registerDisposer,
20
+ replaceChildrenSafely
21
+ } from "./chunk-DHDZ7IVN.js";
17
22
  import {
18
23
  signal
19
- } from "./chunk-KZBEVFXZ.js";
24
+ } from "./chunk-W55YHPEP.js";
20
25
  import {
21
26
  track
22
- } from "./chunk-OOUQUCNN.js";
27
+ } from "./chunk-ACBT6UZS.js";
28
+ import {
29
+ reportError
30
+ } from "./chunk-VPP2FONR.js";
23
31
 
24
32
  // src/ui/form.ts
25
33
  function required(message = "This field is required") {
@@ -114,6 +122,8 @@ function bindField(field, extras) {
114
122
  }
115
123
  };
116
124
  return {
125
+ // The field's own getter, no longer widened to `unknown` — that widening is
126
+ // what forced every consumer to cast. (TYPE-010)
117
127
  value: field.value,
118
128
  on: mergedOn,
119
129
  onElement,
@@ -691,21 +701,169 @@ function sanitizeCSS(css) {
691
701
  sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
692
702
  return sanitized;
693
703
  }
704
+ function firstCompoundEnd(selector) {
705
+ let parens = 0;
706
+ let brackets = 0;
707
+ let quote = null;
708
+ for (let i = 0; i < selector.length; i++) {
709
+ const ch = selector[i];
710
+ if (quote) {
711
+ if (ch === "\\") i++;
712
+ else if (ch === quote) quote = null;
713
+ continue;
714
+ }
715
+ if (ch === '"' || ch === "'") {
716
+ quote = ch;
717
+ continue;
718
+ }
719
+ if (ch === "\\") {
720
+ i++;
721
+ continue;
722
+ }
723
+ if (ch === "(") parens++;
724
+ else if (ch === ")") parens--;
725
+ else if (ch === "[") brackets++;
726
+ else if (ch === "]") brackets--;
727
+ if (parens > 0 || brackets > 0) continue;
728
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f") return i;
729
+ if (ch === ">" || ch === "+" || ch === "~") return i;
730
+ if (ch === ":" && selector[i + 1] === ":") return i;
731
+ }
732
+ return selector.length;
733
+ }
734
+ function scopeSelector(selector, attr) {
735
+ const end = firstCompoundEnd(selector);
736
+ const selfForm = `${selector.slice(0, end)}[${attr}]${selector.slice(end)}`;
737
+ const descendantForm = `[${attr}] ${selector}`;
738
+ return `${descendantForm}, ${selfForm}`;
739
+ }
740
+ function skipNonCode(text, i) {
741
+ const ch = text[i];
742
+ if (ch === "\\") return i + 2;
743
+ if (ch === '"' || ch === "'") {
744
+ let j = i + 1;
745
+ while (j < text.length) {
746
+ if (text[j] === "\\") {
747
+ j += 2;
748
+ continue;
749
+ }
750
+ if (text[j] === ch) return j + 1;
751
+ j++;
752
+ }
753
+ return text.length;
754
+ }
755
+ if (ch === "/" && text[i + 1] === "*") {
756
+ const end = text.indexOf("*/", i + 2);
757
+ return end === -1 ? text.length : end + 2;
758
+ }
759
+ return -1;
760
+ }
761
+ function splitSelectorList(prelude) {
762
+ const parts = [];
763
+ let parens = 0;
764
+ let brackets = 0;
765
+ let start = 0;
766
+ for (let i = 0; i < prelude.length; i++) {
767
+ const skip = skipNonCode(prelude, i);
768
+ if (skip !== -1) {
769
+ i = skip - 1;
770
+ continue;
771
+ }
772
+ const ch = prelude[i];
773
+ if (ch === "(") parens++;
774
+ else if (ch === ")") parens--;
775
+ else if (ch === "[") brackets++;
776
+ else if (ch === "]") brackets--;
777
+ else if (ch === "," && parens === 0 && brackets === 0) {
778
+ parts.push(prelude.slice(start, i));
779
+ start = i + 1;
780
+ }
781
+ }
782
+ parts.push(prelude.slice(start));
783
+ return parts;
784
+ }
785
+ function findBlockEnd(css, openBrace) {
786
+ let depth = 0;
787
+ for (let i = openBrace; i < css.length; i++) {
788
+ const skip = skipNonCode(css, i);
789
+ if (skip !== -1) {
790
+ i = skip - 1;
791
+ continue;
792
+ }
793
+ const ch = css[i];
794
+ if (ch === "{") depth++;
795
+ else if (ch === "}") {
796
+ depth--;
797
+ if (depth === 0) return i;
798
+ }
799
+ }
800
+ return css.length;
801
+ }
802
+ var NESTED_AT_RULE = /^@(media|supports|layer|container|scope|document)\b/i;
803
+ function scopeStylesheet(css, attr) {
804
+ let out = "";
805
+ let preludeStart = 0;
806
+ let parens = 0;
807
+ let brackets = 0;
808
+ let i = 0;
809
+ while (i < css.length) {
810
+ const skip = skipNonCode(css, i);
811
+ if (skip !== -1) {
812
+ i = skip;
813
+ continue;
814
+ }
815
+ const ch = css[i];
816
+ if (ch === "(") {
817
+ parens++;
818
+ i++;
819
+ continue;
820
+ }
821
+ if (ch === ")") {
822
+ parens--;
823
+ i++;
824
+ continue;
825
+ }
826
+ if (ch === "[") {
827
+ brackets++;
828
+ i++;
829
+ continue;
830
+ }
831
+ if (ch === "]") {
832
+ brackets--;
833
+ i++;
834
+ continue;
835
+ }
836
+ if (ch === "{" && parens === 0 && brackets === 0) {
837
+ const prelude = css.slice(preludeStart, i);
838
+ const bodyEnd = findBlockEnd(css, i);
839
+ const body = css.slice(i + 1, bodyEnd);
840
+ const trimmed = prelude.trim();
841
+ const lead = prelude.slice(0, prelude.length - prelude.trimStart().length);
842
+ if (trimmed === "") {
843
+ out += `${prelude}{${body}}`;
844
+ } else if (trimmed.startsWith("@")) {
845
+ out += NESTED_AT_RULE.test(trimmed) ? `${prelude}{${scopeStylesheet(body, attr)}}` : `${prelude}{${body}}`;
846
+ } else {
847
+ const scoped = splitSelectorList(trimmed).map((part) => {
848
+ const selector = part.trim();
849
+ return selector ? scopeSelector(selector, attr) : part;
850
+ }).join(", ");
851
+ out += `${lead}${scoped}{${body}}`;
852
+ }
853
+ i = bodyEnd + 1;
854
+ preludeStart = i;
855
+ continue;
856
+ }
857
+ i++;
858
+ }
859
+ out += css.slice(preludeStart);
860
+ return out;
861
+ }
694
862
  function scopedStyle(css) {
695
863
  const id = `sibu-s${_scope.n++}`;
696
864
  const attr = `data-${id}`;
697
865
  const safeCss = sanitizeCSS(css);
698
- const scopedCSS = safeCss.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, (match, selector, delimiter) => {
699
- const trimmed = selector.trim();
700
- if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
701
- return match;
702
- }
703
- const pseudoIdx = trimmed.indexOf("::");
704
- if (pseudoIdx >= 0) {
705
- return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
706
- }
707
- return `${trimmed}[${attr}]${delimiter}`;
708
- });
866
+ const scopedCSS = scopeStylesheet(safeCss, attr);
709
867
  if (typeof document !== "undefined") {
710
868
  const styleEl = document.createElement("style");
711
869
  styleEl.setAttribute("data-sibu-scope", id);
@@ -721,18 +879,10 @@ function withScopedStyle(css, component) {
721
879
  style = scopedStyle(css);
722
880
  }
723
881
  const el = component(props);
724
- applyScopeRecursive(el, style.attr);
882
+ el.setAttribute(style.attr, "");
725
883
  return el;
726
884
  };
727
885
  }
728
- function applyScopeRecursive(element, attr) {
729
- element.setAttribute(attr, "");
730
- for (const child of Array.from(element.children)) {
731
- if (child instanceof HTMLElement) {
732
- applyScopeRecursive(child, attr);
733
- }
734
- }
735
- }
736
886
  function removeScopedStyle(scopeId) {
737
887
  const el = document.head.querySelector(`style[data-sibu-scope="${scopeId}"]`);
738
888
  if (el) el.remove();
@@ -745,14 +895,8 @@ function bindAttrs(el, attrs) {
745
895
  if (typeof value === "function") {
746
896
  const teardown = bindAttribute(el, attr, value);
747
897
  teardowns.push(teardown);
748
- } else if (typeof value === "boolean") {
749
- if (value) {
750
- el.setAttribute(attr, "");
751
- } else {
752
- el.removeAttribute(attr);
753
- }
754
898
  } else {
755
- el.setAttribute(attr, String(value));
899
+ setSafeAttribute(el, attr, value, { label: "bindAttrs" });
756
900
  }
757
901
  }
758
902
  return () => {
@@ -763,11 +907,7 @@ function bindAttrs(el, attrs) {
763
907
  }
764
908
  function bindBoolAttr(el, attr, getter) {
765
909
  if (typeof getter !== "function") {
766
- if (getter) {
767
- el.setAttribute(attr, "");
768
- } else {
769
- el.removeAttribute(attr);
770
- }
910
+ setSafeAttribute(el, attr, getter, { label: "bindBoolAttr" });
771
911
  return () => {
772
912
  };
773
913
  }
@@ -779,11 +919,7 @@ function bindBoolAttr(el, attr, getter) {
779
919
  } catch {
780
920
  return;
781
921
  }
782
- if (value) {
783
- el.setAttribute(attr, "");
784
- } else {
785
- el.removeAttribute(attr);
786
- }
922
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
787
923
  }
788
924
  const teardown = track(commit);
789
925
  return teardown;
@@ -791,7 +927,7 @@ function bindBoolAttr(el, attr, getter) {
791
927
  function bindData(el, key, getter) {
792
928
  const dataAttr = `data-${key}`;
793
929
  if (typeof getter !== "function") {
794
- el.setAttribute(dataAttr, String(getter));
930
+ setSafeAttribute(el, dataAttr, getter, { label: "bindData" });
795
931
  return () => {
796
932
  };
797
933
  }
@@ -931,31 +1067,58 @@ function infiniteScroll(options) {
931
1067
  const sentinelRef = { current: null };
932
1068
  let observer = null;
933
1069
  let disposed = false;
1070
+ let generation = 0;
1071
+ function safeHasMore() {
1072
+ try {
1073
+ return hasMore();
1074
+ } catch (err) {
1075
+ reportError(err, { phase: "async", name: "infiniteScroll(hasMore)" });
1076
+ return false;
1077
+ }
1078
+ }
1079
+ function ownsAttachment(owner) {
1080
+ return !disposed && generation === owner.generation && observer === owner.observer && _current === owner.sentinel;
1081
+ }
934
1082
  function createObserver() {
935
1083
  if (typeof IntersectionObserver === "undefined") return;
936
- observer = new IntersectionObserver(
1084
+ let createdObserver;
1085
+ createdObserver = new IntersectionObserver(
937
1086
  (entries) => {
938
1087
  const entry = entries[0];
939
- if (entry?.isIntersecting && !loading() && hasMore() && !disposed) {
940
- loadMore();
941
- }
1088
+ if (!entry?.isIntersecting) return;
1089
+ const sentinel = _current;
1090
+ if (!sentinel) return;
1091
+ const owner = { generation, observer: createdObserver, sentinel };
1092
+ if (!ownsAttachment(owner) || loading()) return;
1093
+ const shouldLoad = safeHasMore();
1094
+ if (!shouldLoad || !ownsAttachment(owner)) return;
1095
+ void loadMore(owner);
942
1096
  },
943
1097
  { threshold }
944
1098
  );
1099
+ observer = createdObserver;
945
1100
  if (sentinelRef.current) {
946
- observer.observe(sentinelRef.current);
1101
+ createdObserver.observe(sentinelRef.current);
947
1102
  }
948
1103
  }
949
- async function loadMore() {
950
- setLoading(true);
1104
+ async function loadMore(owner) {
951
1105
  try {
952
- await onLoadMore();
953
- } finally {
954
- setLoading(false);
955
- if (!disposed && observer && _current && hasMore()) {
956
- observer.unobserve(_current);
957
- observer.observe(_current);
1106
+ setLoading(true);
1107
+ if (!ownsAttachment(owner)) return;
1108
+ try {
1109
+ await onLoadMore();
1110
+ } catch (err) {
1111
+ reportError(err, { phase: "async", name: "infiniteScroll(onLoadMore)" });
958
1112
  }
1113
+ if (!ownsAttachment(owner)) return;
1114
+ setLoading(false);
1115
+ if (!ownsAttachment(owner)) return;
1116
+ const shouldContinue = safeHasMore();
1117
+ if (!shouldContinue || !ownsAttachment(owner)) return;
1118
+ owner.observer.unobserve(owner.sentinel);
1119
+ owner.observer.observe(owner.sentinel);
1120
+ } catch (err) {
1121
+ reportError(err, { phase: "async", name: "infiniteScroll(loadMore)" });
959
1122
  }
960
1123
  }
961
1124
  const originalRef = sentinelRef;
@@ -965,7 +1128,10 @@ function infiniteScroll(options) {
965
1128
  return _current;
966
1129
  },
967
1130
  set(el) {
1131
+ if (el === _current) return;
968
1132
  _current = el;
1133
+ generation++;
1134
+ setLoading(false);
969
1135
  if (observer) {
970
1136
  observer.disconnect();
971
1137
  observer = null;
@@ -978,10 +1144,12 @@ function infiniteScroll(options) {
978
1144
  });
979
1145
  function dispose2() {
980
1146
  disposed = true;
1147
+ generation++;
981
1148
  if (observer) {
982
1149
  observer.disconnect();
983
1150
  observer = null;
984
1151
  }
1152
+ setLoading(false);
985
1153
  }
986
1154
  return { sentinelRef: originalRef, loading, dispose: dispose2 };
987
1155
  }
@@ -1057,6 +1225,7 @@ function eventBus() {
1057
1225
  }
1058
1226
 
1059
1227
  // src/platform/customElement.ts
1228
+ var SVG_NS = "http://www.w3.org/2000/svg";
1060
1229
  function defineElement(name, component, options = {}) {
1061
1230
  if (customElements.get(name)) return;
1062
1231
  const observed = options.observedAttributes || [];
@@ -1085,11 +1254,8 @@ function defineElement(name, component, options = {}) {
1085
1254
  }
1086
1255
  }
1087
1256
  _teardown() {
1088
- if (this._rendered) {
1089
- dispose(this._rendered);
1090
- this._rendered = null;
1091
- }
1092
- this._root.replaceChildren();
1257
+ this._rendered = null;
1258
+ replaceChildrenSafely(this._root);
1093
1259
  }
1094
1260
  _render() {
1095
1261
  this._teardown();
@@ -1114,14 +1280,13 @@ function defineElement(name, component, options = {}) {
1114
1280
  customElements.define(name, SibuElement);
1115
1281
  }
1116
1282
  function svgElement(tag, props = {}, ...nodes) {
1117
- const SVG_NS = "http://www.w3.org/2000/svg";
1118
1283
  const el = document.createElementNS(SVG_NS, tag);
1119
1284
  for (const [key, value] of Object.entries(props)) {
1120
1285
  if (key === "nodes") continue;
1121
- if (typeof value === "function" && key.startsWith("on")) {
1286
+ if (typeof value === "function" && isEventHandlerAttr(key)) {
1122
1287
  el.addEventListener(key.slice(2).toLowerCase(), value);
1123
1288
  } else if (value != null) {
1124
- el.setAttribute(key, String(value));
1289
+ setSafeAttribute(el, key, value, { label: "svgElement" });
1125
1290
  }
1126
1291
  }
1127
1292
  for (const child of nodes) {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  registerDisposer
3
- } from "./chunk-5VGSK6D2.js";
3
+ } from "./chunk-DHDZ7IVN.js";
4
4
  import {
5
5
  signal
6
- } from "./chunk-KZBEVFXZ.js";
6
+ } from "./chunk-W55YHPEP.js";
7
7
 
8
8
  // src/ui/transition.ts
9
9
  function transition(element, options = {}) {
@@ -335,7 +335,9 @@ function TransitionGroup(options) {
335
335
  // src/ui/viewTransition.ts
336
336
  function viewTransition(callback) {
337
337
  const [isTransitioning, setIsTransitioning] = signal(false);
338
+ let inflight = 0;
338
339
  async function start() {
340
+ inflight++;
339
341
  setIsTransitioning(true);
340
342
  try {
341
343
  if (typeof document !== "undefined" && "startViewTransition" in document && typeof document.startViewTransition === "function") {
@@ -345,7 +347,11 @@ function viewTransition(callback) {
345
347
  await callback();
346
348
  }
347
349
  } finally {
348
- setIsTransitioning(false);
350
+ inflight--;
351
+ if (inflight <= 0) {
352
+ inflight = 0;
353
+ setIsTransitioning(false);
354
+ }
349
355
  }
350
356
  }
351
357
  return { start, isTransitioning };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SVG_NS,
3
3
  tagFactory
4
- } from "./chunk-JNGGM2XE.js";
4
+ } from "./chunk-DQVUBVNX.js";
5
5
 
6
6
  // src/core/rendering/html.ts
7
7
  var html = tagFactory("html");
@@ -1,13 +1,19 @@
1
1
  import {
2
2
  isUnsafeKey
3
3
  } from "./chunk-H3SRKIYX.js";
4
+ import {
5
+ acquireTitle
6
+ } from "./chunk-52XFPGSN.js";
4
7
  import {
5
8
  effect
6
- } from "./chunk-BUKYCWHX.js";
9
+ } from "./chunk-PBQEW5VW.js";
7
10
  import {
8
11
  batch,
9
12
  signal
10
- } from "./chunk-KZBEVFXZ.js";
13
+ } from "./chunk-W55YHPEP.js";
14
+ import {
15
+ reportError
16
+ } from "./chunk-VPP2FONR.js";
11
17
 
12
18
  // src/browser/media.ts
13
19
  function media(query) {
@@ -280,6 +286,7 @@ function permissions(name) {
280
286
  };
281
287
  status.addEventListener("change", onChange);
282
288
  }).catch(() => {
289
+ if (disposed) return;
283
290
  setState("unsupported");
284
291
  });
285
292
  function dispose() {
@@ -298,20 +305,25 @@ function clipboard() {
298
305
  const [text, setText] = signal("");
299
306
  const [copied, setCopied] = signal(false);
300
307
  let copiedTimer = null;
308
+ let disposed = false;
301
309
  async function copy(value) {
310
+ if (disposed) return;
302
311
  if (typeof navigator === "undefined" || !navigator.clipboard) {
303
312
  return;
304
313
  }
305
314
  await navigator.clipboard.writeText(value);
315
+ if (disposed) return;
306
316
  setText(value);
307
317
  setCopied(true);
308
318
  if (copiedTimer !== null) clearTimeout(copiedTimer);
309
319
  copiedTimer = setTimeout(() => {
320
+ if (disposed) return;
310
321
  setCopied(false);
311
322
  copiedTimer = null;
312
323
  }, 2e3);
313
324
  }
314
325
  function dispose() {
326
+ disposed = true;
315
327
  if (copiedTimer !== null) {
316
328
  clearTimeout(copiedTimer);
317
329
  copiedTimer = null;
@@ -437,20 +449,21 @@ function title(value) {
437
449
  return () => {
438
450
  };
439
451
  }
440
- const previousTitle = document.title;
441
452
  if (typeof value === "function") {
453
+ let lease2 = null;
442
454
  const cleanup = effect(() => {
443
- document.title = value();
455
+ const next = value();
456
+ if (lease2) lease2.set(next);
457
+ else lease2 = acquireTitle(next);
444
458
  });
445
459
  return () => {
446
460
  cleanup();
447
- document.title = previousTitle;
461
+ lease2?.release();
462
+ lease2 = null;
448
463
  };
449
464
  }
450
- document.title = value;
451
- return () => {
452
- document.title = previousTitle;
453
- };
465
+ const lease = acquireTitle(value);
466
+ return () => lease.release();
454
467
  }
455
468
 
456
469
  // src/browser/colorScheme.ts
@@ -764,51 +777,112 @@ function wakeLock() {
764
777
  };
765
778
  }
766
779
  const api = navigator.wakeLock;
767
- let sentinel = null;
780
+ let current = null;
781
+ let desire = false;
782
+ let generation = 0;
783
+ let inflight = null;
768
784
  let disposed = false;
785
+ function publishActive(value) {
786
+ if (disposed) return;
787
+ setActive(value);
788
+ }
789
+ function syncReleasedState() {
790
+ if (current?.released) {
791
+ current = null;
792
+ publishActive(false);
793
+ }
794
+ }
795
+ function discard(sentinel) {
796
+ if (sentinel.released) return;
797
+ sentinel.release().catch((err) => {
798
+ reportError(err, { phase: "async", name: "wakeLock(discard)" });
799
+ });
800
+ }
769
801
  async function request() {
770
802
  if (disposed) return;
771
- try {
772
- const s = await api.request("screen");
773
- if (disposed) {
774
- s.release().catch(() => {
775
- });
776
- return;
777
- }
778
- sentinel = s;
779
- setActive(true);
780
- sentinel.addEventListener("release", () => {
781
- setActive(false);
782
- });
783
- } catch {
784
- setActive(false);
803
+ desire = true;
804
+ syncReleasedState();
805
+ if (current && !current.released) {
806
+ publishActive(true);
807
+ return;
785
808
  }
809
+ if (inflight) return inflight;
810
+ const myGeneration = ++generation;
811
+ const attempt = (async () => {
812
+ try {
813
+ const sentinel = await api.request("screen");
814
+ if (disposed || !desire || generation !== myGeneration) {
815
+ discard(sentinel);
816
+ return;
817
+ }
818
+ if (sentinel.released) {
819
+ publishActive(false);
820
+ return;
821
+ }
822
+ try {
823
+ sentinel.addEventListener("release", () => {
824
+ if (current !== sentinel) return;
825
+ current = null;
826
+ publishActive(false);
827
+ });
828
+ } catch (listenerError) {
829
+ discard(sentinel);
830
+ publishActive(false);
831
+ reportError(listenerError, { phase: "async", name: "wakeLock(listener registration)" });
832
+ return;
833
+ }
834
+ if (sentinel.released) {
835
+ publishActive(false);
836
+ return;
837
+ }
838
+ current = sentinel;
839
+ publishActive(true);
840
+ } catch (err) {
841
+ if (generation === myGeneration) publishActive(false);
842
+ reportError(err, { phase: "async", name: "wakeLock(request)" });
843
+ }
844
+ })();
845
+ inflight = attempt;
846
+ void attempt.finally(() => {
847
+ if (inflight === attempt) inflight = null;
848
+ }).catch(() => {
849
+ });
850
+ return attempt;
786
851
  }
787
852
  async function release() {
788
- if (sentinel && !sentinel.released) {
789
- await sentinel.release();
853
+ desire = false;
854
+ generation++;
855
+ inflight = null;
856
+ const held = current;
857
+ current = null;
858
+ publishActive(false);
859
+ if (held && !held.released) {
860
+ await held.release();
790
861
  }
791
- sentinel = null;
792
- setActive(false);
793
862
  }
794
863
  const onVisibility = () => {
795
- if (sentinel?.released && !document.hidden) {
796
- request().catch((err) => {
797
- if (typeof console !== "undefined") {
798
- console.warn("[SibuJS wakeLock] re-acquire failed:", err);
799
- }
800
- });
801
- }
864
+ syncReleasedState();
865
+ if (!desire || current || document.hidden) return;
866
+ request().catch((err) => {
867
+ reportError(err, { phase: "async", name: "wakeLock(visibility re-acquire)" });
868
+ });
802
869
  };
803
870
  document.addEventListener("visibilitychange", onVisibility);
804
871
  function dispose() {
805
- disposed = true;
872
+ if (disposed) return;
873
+ desire = false;
874
+ generation++;
875
+ inflight = null;
806
876
  document.removeEventListener("visibilitychange", onVisibility);
807
- release().catch((err) => {
808
- if (typeof console !== "undefined") {
809
- console.warn("[SibuJS wakeLock] release failed:", err);
810
- }
811
- });
877
+ const held = current;
878
+ current = null;
879
+ setActive(false);
880
+ disposed = true;
881
+ if (held && !held.released) {
882
+ held.release().catch((err) => {
883
+ reportError(err, { phase: "async", name: "wakeLock(dispose release)" });
884
+ });
885
+ }
812
886
  }
813
887
  return { active, request, release, dispose };
814
888
  }