sibujs 4.1.0 → 4.2.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 (80) hide show
  1. package/README.md +24 -0
  2. package/dist/browser.cjs +13 -21
  3. package/dist/browser.js +6 -6
  4. package/dist/build.cjs +278 -104
  5. package/dist/build.js +15 -15
  6. package/dist/cdn.dev.global.js +398 -0
  7. package/dist/cdn.global.js +10 -10
  8. package/dist/{chunk-DCDS2PQ6.js → chunk-2LOYNGBY.js} +2 -2
  9. package/dist/{chunk-VPP2FONR.js → chunk-2WLZ6757.js} +17 -10
  10. package/dist/{chunk-IM7N5WR4.js → chunk-3VTSPNC5.js} +2 -2
  11. package/dist/{chunk-TIRZCERI.js → chunk-5DXA2J44.js} +1 -1
  12. package/dist/{chunk-I3VI34DP.js → chunk-6ZGI3RBE.js} +149 -33
  13. package/dist/{chunk-D3SZ4LVS.js → chunk-7ALC7KGE.js} +5 -6
  14. package/dist/{chunk-PFTDGEPZ.js → chunk-7VEC3UD2.js} +3 -3
  15. package/dist/{chunk-KSOEJGFO.js → chunk-BMYYZWHX.js} +3 -3
  16. package/dist/{chunk-LOFZHP6V.js → chunk-BNKV4REK.js} +7 -8
  17. package/dist/{chunk-5GJ5RTHA.js → chunk-FOWBJZDL.js} +7 -8
  18. package/dist/{chunk-FB7KQXZI.js → chunk-GDUNQCCW.js} +4 -4
  19. package/dist/{chunk-UFSQEO7P.js → chunk-HECVW343.js} +7 -8
  20. package/dist/{chunk-SHDVWATN.js → chunk-HJEPKZ2O.js} +7 -8
  21. package/dist/{chunk-ANGSYNPX.js → chunk-HZEBO22J.js} +57 -37
  22. package/dist/{chunk-5INI7D2L.js → chunk-IXKSNWV5.js} +74 -11
  23. package/dist/{chunk-DAKLOMKS.js → chunk-KE4KWRDY.js} +3 -3
  24. package/dist/{chunk-AW6HPJ3L.js → chunk-KKLW7YWL.js} +1 -1
  25. package/dist/{chunk-BMSN6NWQ.js → chunk-NEYWS4EX.js} +6 -6
  26. package/dist/{chunk-HYCCIYNS.js → chunk-NIOYEGBQ.js} +13 -14
  27. package/dist/{chunk-4U7KD3RY.js → chunk-OUWAIXIK.js} +3 -3
  28. package/dist/{chunk-MRAMKDHQ.js → chunk-P3DHXBR6.js} +1 -1
  29. package/dist/{chunk-FMHIQNMP.js → chunk-QCFLDD7C.js} +5 -5
  30. package/dist/{chunk-DHDZ7IVN.js → chunk-QKRPLZ2V.js} +7 -8
  31. package/dist/{chunk-4RH6D7GR.js → chunk-QL4UREZO.js} +6 -6
  32. package/dist/{chunk-XIHMS2Y6.js → chunk-SF3TB4K6.js} +8 -8
  33. package/dist/{chunk-XSZRJW76.js → chunk-XZ2N525U.js} +6 -6
  34. package/dist/{customElement-CKLoO63w.d.ts → customElement-CNZxEB9G.d.cts} +77 -0
  35. package/dist/{customElement-CKLoO63w.d.cts → customElement-CNZxEB9G.d.ts} +77 -0
  36. package/dist/data.cjs +15 -24
  37. package/dist/data.js +9 -9
  38. package/dist/devtools.cjs +18 -22
  39. package/dist/devtools.d.cts +1 -1
  40. package/dist/devtools.d.ts +1 -1
  41. package/dist/devtools.js +8 -8
  42. package/dist/{dispose-4FZ6OFXR.js → dispose-46BOMMQJ.js} +2 -2
  43. package/dist/ecosystem.cjs +222 -67
  44. package/dist/ecosystem.js +11 -11
  45. package/dist/extras.cjs +243 -86
  46. package/dist/extras.d.cts +2 -2
  47. package/dist/extras.d.ts +2 -2
  48. package/dist/extras.js +25 -25
  49. package/dist/index.cjs +272 -98
  50. package/dist/index.d.cts +154 -33
  51. package/dist/index.d.ts +154 -33
  52. package/dist/index.js +15 -15
  53. package/dist/{introspect-RjLfIFpL.d.cts → introspect-D5xF-0Es.d.cts} +7 -0
  54. package/dist/{introspect-DOZfmC-4.d.ts → introspect-rSBIxEQi.d.ts} +7 -0
  55. package/dist/motion.cjs +13 -22
  56. package/dist/motion.js +5 -5
  57. package/dist/patterns.cjs +16 -24
  58. package/dist/patterns.d.cts +8 -0
  59. package/dist/patterns.d.ts +8 -0
  60. package/dist/patterns.js +7 -7
  61. package/dist/performance.cjs +16 -27
  62. package/dist/performance.js +9 -9
  63. package/dist/plugins.cjs +260 -86
  64. package/dist/plugins.d.cts +92 -0
  65. package/dist/plugins.d.ts +92 -0
  66. package/dist/plugins.js +18 -17
  67. package/dist/{ssr-JFP5535P.js → ssr-Y7XOEPEN.js} +5 -5
  68. package/dist/ssr.cjs +233 -79
  69. package/dist/ssr.js +12 -12
  70. package/dist/testing.cjs +11 -19
  71. package/dist/testing.js +5 -5
  72. package/dist/ui.cjs +98 -43
  73. package/dist/ui.d.cts +7 -1
  74. package/dist/ui.d.ts +7 -1
  75. package/dist/ui.js +10 -10
  76. package/dist/widgets.cjs +13 -21
  77. package/dist/widgets.d.cts +8 -0
  78. package/dist/widgets.d.ts +8 -0
  79. package/dist/widgets.js +9 -9
  80. package/package.json +5 -2
@@ -32,23 +32,23 @@ __export(ecosystem_exports, {
32
32
  module.exports = __toCommonJS(ecosystem_exports);
33
33
 
34
34
  // src/core/dev.ts
35
- function isDev() {
36
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
37
- // The bare `__SIBU_DEV__` is a bundler define that only exists in
38
- // production builds; under the test runner it is always undefined, so
39
- // this branch is unreachable here.
40
- /* v8 ignore next 2 */
41
- typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
42
- );
43
- }
44
- var _isDev = isDev();
35
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
36
+ // safe default: off in browser, on in test/dev Node
37
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
38
+ );
45
39
  function devAssert(condition, message) {
46
- if (_isDev && !condition) {
40
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
47
41
  throw new Error(`[SibuJS] ${message}`);
48
42
  }
49
43
  }
44
+ function devWarnLazy(build) {
45
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
46
+ const message = build();
47
+ if (message) console.warn(`[SibuJS] ${message}`);
48
+ }
49
+ }
50
50
  function devWarn(message) {
51
- if (_isDev) {
51
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
52
52
  console.warn(`[SibuJS] ${message}`);
53
53
  }
54
54
  }
@@ -597,17 +597,16 @@ function forEachSubscriber(signal2, visit) {
597
597
  }
598
598
 
599
599
  // src/reactivity/track.ts
600
- var _isDev2 = isDev();
601
- var _runtimeVersion = true ? "4.1.0" : "dev";
600
+ var _runtimeVersion = true ? "4.2.0" : "dev";
602
601
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
603
602
  function resolveReactiveApi() {
604
603
  const g = globalThis;
605
604
  const existing = g[REGISTRY_KEY];
606
605
  if (existing) {
607
- if (_isDev2 && !existing.__dupWarned) {
606
+ if (DEV && !existing.__dupWarned) {
608
607
  existing.__dupWarned = true;
609
608
  devWarn(
610
- `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
609
+ `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity is NOT broken and no bundler configuration is required: every copy routes through the first one via a global registry, so signal writes still reach subscribers registered by any copy. This is only a SIZE issue \u2014 the duplicate copy's bytes are downloaded and parsed for nothing. If you want them back, de-duplicate sibujs in your bundler (Vite: resolve.dedupe: ['sibujs']). Mismatched VERSIONS above are worth fixing regardless, since only the first one loaded is used.`
611
610
  );
612
611
  }
613
612
  return existing;
@@ -869,7 +868,6 @@ var isBatching = API2.isBatching;
869
868
 
870
869
  // src/core/signals/signal.ts
871
870
  var _g2 = globalThis;
872
- var _isDev3 = isDev();
873
871
  function signal(initial, options) {
874
872
  const state2 = {
875
873
  value: initial,
@@ -882,7 +880,7 @@ function signal(initial, options) {
882
880
  _d: false,
883
881
  _validate: void 0
884
882
  };
885
- const debugName = _isDev3 ? options?.name : void 0;
883
+ const debugName = DEV ? options?.name : void 0;
886
884
  const equalsFn = options?.equals;
887
885
  if (debugName) state2.__name = debugName;
888
886
  function get() {
@@ -899,7 +897,7 @@ function signal(initial, options) {
899
897
  if (equalsFn(prev, newValue)) return;
900
898
  state2.value = newValue;
901
899
  state2.__v++;
902
- if (_isDev3) {
900
+ if (DEV) {
903
901
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
904
902
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
905
903
  }
@@ -907,7 +905,7 @@ function signal(initial, options) {
907
905
  notifySubscribers2(state2);
908
906
  }
909
907
  };
910
- } else if (_isDev3) {
908
+ } else if (DEV) {
911
909
  set = (next) => {
912
910
  const prev = state2.value;
913
911
  const newValue = typeof next === "function" ? next(prev) : next;
@@ -932,7 +930,7 @@ function signal(initial, options) {
932
930
  }
933
931
  };
934
932
  }
935
- if (_isDev3) {
933
+ if (DEV) {
936
934
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
937
935
  if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
938
936
  }
@@ -1293,20 +1291,75 @@ function decodeCssEscapes(value) {
1293
1291
  }
1294
1292
  return out;
1295
1293
  }
1296
- function sanitizeCSSValue(value) {
1297
- if (!CSS_DANGER_GATE.test(value)) return value;
1294
+ var BLOCKED_CSS_NEEDLES = [
1295
+ "url(",
1296
+ "expression(",
1297
+ "javascript:",
1298
+ "vbscript:",
1299
+ "-moz-binding",
1300
+ "behavior:",
1301
+ "@import",
1302
+ "image-set(",
1303
+ "filter:progid"
1304
+ ];
1305
+ function describeElement(el) {
1306
+ if (!el) return "an element";
1307
+ let out = el.tagName.toLowerCase();
1308
+ const id = el.getAttribute("id");
1309
+ if (id) out += `#${id}`;
1310
+ const cls = el.getAttribute("class");
1311
+ if (cls) out += `.${cls.trim().split(/\s+/).slice(0, 3).join(".")}`;
1312
+ return `<${out}>`;
1313
+ }
1314
+ var warnedDeclarations = /* @__PURE__ */ new Set();
1315
+ var MAX_WARNED_DECLARATIONS = 100;
1316
+ var declarationCapAnnounced = false;
1317
+ function warnDroppedDeclaration(value, reason, ctx) {
1318
+ devWarnLazy(() => {
1319
+ const reasons = [
1320
+ "url() in an inline style is an exfiltration channel: the browser fetches it immediately and the URL's path or query can carry page data off-origin",
1321
+ "expression() executes arbitrary script in legacy engines",
1322
+ "the javascript: scheme executes script",
1323
+ "the vbscript: scheme executes script",
1324
+ "-moz-binding loads and runs remote XBL",
1325
+ "behavior: loads and runs a remote HTC script",
1326
+ "@import pulls in a remote stylesheet",
1327
+ "image-set() fetches remote images, the same exfiltration channel as url()",
1328
+ "filter:progid activates legacy scriptable filters"
1329
+ ];
1330
+ const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
1331
+ const where = describeElement(ctx?.element);
1332
+ const key = `${where}|${declaration}`;
1333
+ if (warnedDeclarations.has(key)) return "";
1334
+ if (warnedDeclarations.size >= MAX_WARNED_DECLARATIONS) {
1335
+ if (declarationCapAnnounced) return "";
1336
+ declarationCapAnnounced = true;
1337
+ return `${MAX_WARNED_DECLARATIONS} distinct dropped style declarations have been reported; suppressing further ones for the rest of this session so they cannot flood the console. The declarations are still dropped \u2014 only the reporting stops. Fix the reported ones and reload to see any that remain.`;
1338
+ }
1339
+ warnedDeclarations.add(key);
1340
+ return `style declaration "${declaration}" was dropped by the style sanitizer on ${where} \u2014 ${reasons[reason]}. The element renders without it, which usually looks like a missing image rather than an error. Sanctioned alternatives: for a content image use an <img> element (or a <picture>); for decoration, put the rule in a stylesheet and set a class instead \u2014 stylesheet CSS is not sanitized, only inline style attributes are. If this declaration is not yours, it came from untrusted data and the drop just prevented an exfiltration.`;
1341
+ });
1342
+ }
1343
+ function blockedCssReason(value) {
1344
+ if (!CSS_DANGER_GATE.test(value)) return -1;
1298
1345
  const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
1299
1346
  const lower = normalized.toLowerCase().replace(/\s+/g, "");
1300
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
1301
- return "";
1347
+ for (let i = 0; i < BLOCKED_CSS_NEEDLES.length; i++) {
1348
+ if (lower.includes(BLOCKED_CSS_NEEDLES[i])) return i;
1302
1349
  }
1303
- return value;
1350
+ return -1;
1304
1351
  }
1305
- function sanitizeStyleAttribute(cssText) {
1352
+ function sanitizeCSSValue(value, context) {
1353
+ const reason = blockedCssReason(value);
1354
+ if (reason === -1) return value;
1355
+ warnDroppedDeclaration(value, reason, context);
1356
+ return "";
1357
+ }
1358
+ function sanitizeStyleAttribute(cssText, context) {
1306
1359
  const input = String(cssText);
1307
1360
  if (input.trim() === "") return "";
1308
1361
  if (typeof document === "undefined") {
1309
- return sanitizeCSSValue(input) === "" ? "" : input;
1362
+ return sanitizeCSSValue(input, context) === "" ? "" : input;
1310
1363
  }
1311
1364
  const probe = document.createElement("div");
1312
1365
  try {
@@ -1318,8 +1371,12 @@ function sanitizeStyleAttribute(cssText) {
1318
1371
  for (let i = 0; i < probe.style.length; i++) {
1319
1372
  const property = probe.style[i];
1320
1373
  const value = probe.style.getPropertyValue(property);
1321
- if (sanitizeCSSValue(value) === "") continue;
1322
- if (sanitizeCSSValue(`${property}:${value}`) === "") continue;
1374
+ let reason = blockedCssReason(value);
1375
+ if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
1376
+ if (reason !== -1) {
1377
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
1378
+ continue;
1379
+ }
1323
1380
  const priority = probe.style.getPropertyPriority(property);
1324
1381
  declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
1325
1382
  }
@@ -1347,16 +1404,15 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
1347
1404
  function isHtmlContentAttribute(name) {
1348
1405
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
1349
1406
  }
1350
- function sanitizeAttributeString(attr, value) {
1407
+ function sanitizeAttributeString(attr, value, context) {
1351
1408
  const lower = attr.toLowerCase();
1352
1409
  if (lower === "srcset") return sanitizeSrcset(value);
1353
1410
  if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
1354
- if (lower === "style") return sanitizeStyleAttribute(value);
1411
+ if (lower === "style") return sanitizeStyleAttribute(value, context);
1355
1412
  return value;
1356
1413
  }
1357
1414
 
1358
1415
  // src/utils/setSafeAttribute.ts
1359
- var _isDev4 = isDev();
1360
1416
  var HTML_NS = "http://www.w3.org/1999/xhtml";
1361
1417
  var SVG_NS = "http://www.w3.org/2000/svg";
1362
1418
  var XLINK_NS = "http://www.w3.org/1999/xlink";
@@ -1379,7 +1435,7 @@ function setSafeAttribute(el, name, value, options = {}) {
1379
1435
  const ns = namespaceFor(el, name);
1380
1436
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
1381
1437
  if (isEventHandlerAttr(name)) {
1382
- if (_isDev4) {
1438
+ if (DEV) {
1383
1439
  devWarn(
1384
1440
  `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
1385
1441
  );
@@ -1394,7 +1450,7 @@ function setSafeAttribute(el, name, value, options = {}) {
1394
1450
  return true;
1395
1451
  }
1396
1452
  if (isHtmlContentAttribute(name)) {
1397
- if (_isDev4) {
1453
+ if (DEV) {
1398
1454
  devWarn(
1399
1455
  `${options.label ?? "setSafeAttribute"}: refusing to set "${name}". The browser parses this attribute as a nested HTML document, so a generic string value cannot be made safe \u2014 attribute escaping is undone before the parse. Build the frame's content another way.`
1400
1456
  );
@@ -1423,7 +1479,7 @@ function setSafeAttribute(el, name, value, options = {}) {
1423
1479
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
1424
1480
  return true;
1425
1481
  }
1426
- const safe = sanitizeAttributeString(name, str);
1482
+ const safe = sanitizeAttributeString(name, str, { element: el });
1427
1483
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1428
1484
  if (current === safe) return true;
1429
1485
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -1461,7 +1517,6 @@ function reportDrainRunaway(label, executed, remaining) {
1461
1517
  { executed, remaining }
1462
1518
  );
1463
1519
  }
1464
- var _isDev5 = isDev();
1465
1520
  var activeBindingCount = 0;
1466
1521
  function registerDisposer(node, teardown) {
1467
1522
  let disposers = elementDisposers.get(node);
@@ -1470,7 +1525,7 @@ function registerDisposer(node, teardown) {
1470
1525
  elementDisposers.set(node, disposers);
1471
1526
  }
1472
1527
  disposers.push(teardown);
1473
- if (_isDev5) activeBindingCount++;
1528
+ if (DEV) activeBindingCount++;
1474
1529
  }
1475
1530
  function dispose(node) {
1476
1531
  const stack = [node];
@@ -1493,13 +1548,13 @@ function dispose(node) {
1493
1548
  if (!pending || pending.length === 0) break;
1494
1549
  const snapshot = pending.slice();
1495
1550
  elementDisposers.delete(current);
1496
- if (_isDev5) activeBindingCount -= snapshot.length;
1551
+ if (DEV) activeBindingCount -= snapshot.length;
1497
1552
  for (let i2 = 0; i2 < snapshot.length; i2++) {
1498
1553
  if (executed >= MAX_DRAIN_TEARDOWNS) {
1499
1554
  const rest = snapshot.slice(i2);
1500
1555
  const added = elementDisposers.get(current);
1501
1556
  elementDisposers.set(current, added ? rest.concat(added) : rest);
1502
- if (_isDev5) activeBindingCount += rest.length;
1557
+ if (DEV) activeBindingCount += rest.length;
1503
1558
  reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
1504
1559
  runaway = true;
1505
1560
  break;
@@ -1516,8 +1571,101 @@ function dispose(node) {
1516
1571
  }
1517
1572
  }
1518
1573
 
1574
+ // src/core/rendering/focusPreservation.ts
1575
+ var IDENTITY_ATTRS = ["data-focus-key", "id", "name"];
1576
+ var SELECTABLE_TYPES = /* @__PURE__ */ new Set(["text", "search", "url", "tel", "password", ""]);
1577
+ function selectionOf(el) {
1578
+ const tag = el.tagName;
1579
+ const selectable = tag === "TEXTAREA" || tag === "INPUT" && SELECTABLE_TYPES.has(el.type.toLowerCase());
1580
+ if (!selectable) return { start: null, end: null, direction: null };
1581
+ const field = el;
1582
+ return { start: field.selectionStart, end: field.selectionEnd, direction: field.selectionDirection };
1583
+ }
1584
+ function captureFocusWithin(nodes) {
1585
+ if (nodes.length === 0 || typeof document === "undefined") return null;
1586
+ const active = document.activeElement;
1587
+ if (!active || active === document.body) return null;
1588
+ let inside = false;
1589
+ for (let i = 0; i < nodes.length; i++) {
1590
+ const n = nodes[i];
1591
+ if (n === active || n.nodeType === 1 && n.contains(active)) {
1592
+ inside = true;
1593
+ break;
1594
+ }
1595
+ }
1596
+ if (!inside) return null;
1597
+ let attr = null;
1598
+ let value = null;
1599
+ for (let i = 0; i < IDENTITY_ATTRS.length; i++) {
1600
+ const candidate = active.getAttribute(IDENTITY_ATTRS[i]);
1601
+ if (candidate) {
1602
+ attr = IDENTITY_ATTRS[i];
1603
+ value = candidate;
1604
+ break;
1605
+ }
1606
+ }
1607
+ return { el: active, attr, value, tag: active.tagName, ...selectionOf(active) };
1608
+ }
1609
+ function quote(value) {
1610
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1611
+ }
1612
+ function findUniqueByIdentity(nodes, selector) {
1613
+ let el = null;
1614
+ let count = 0;
1615
+ for (let i = 0; i < nodes.length; i++) {
1616
+ const n = nodes[i];
1617
+ if (n.nodeType !== 1) continue;
1618
+ const root = n;
1619
+ if (root.matches(selector)) {
1620
+ count++;
1621
+ el ?? (el = root);
1622
+ }
1623
+ const inner = root.querySelectorAll(selector);
1624
+ for (let j = 0; j < inner.length; j++) {
1625
+ count++;
1626
+ el ?? (el = inner[j]);
1627
+ }
1628
+ if (count > 1) return { el: null, count };
1629
+ }
1630
+ return count === 1 ? { el, count } : { el: null, count };
1631
+ }
1632
+ function refocus(el, snapshot) {
1633
+ el.focus();
1634
+ if (snapshot.start !== null && typeof el.setSelectionRange === "function") {
1635
+ const field = el;
1636
+ const max = field.value.length;
1637
+ const start = Math.min(snapshot.start, max);
1638
+ const end = Math.min(snapshot.end ?? start, max);
1639
+ field.setSelectionRange(start, end, snapshot.direction ?? "none");
1640
+ }
1641
+ }
1642
+ function restoreFocusWithin(snapshot, nodes, where) {
1643
+ if (!snapshot) return;
1644
+ if (typeof document === "undefined") return;
1645
+ const active = document.activeElement;
1646
+ if (active === snapshot.el) return;
1647
+ if (active && active !== document.body && active !== document.documentElement) return;
1648
+ if (snapshot.el.isConnected) {
1649
+ refocus(snapshot.el, snapshot);
1650
+ return;
1651
+ }
1652
+ let ambiguous = false;
1653
+ if (snapshot.attr && snapshot.value) {
1654
+ const { el: replacement, count } = findUniqueByIdentity(nodes, `[${snapshot.attr}=${quote(snapshot.value)}]`);
1655
+ if (replacement && replacement.tagName === snapshot.tag) {
1656
+ refocus(replacement, snapshot);
1657
+ return;
1658
+ }
1659
+ ambiguous = count > 1;
1660
+ }
1661
+ devWarnLazy(() => {
1662
+ const id = snapshot.attr ? ` (${snapshot.attr}="${snapshot.value}")` : "";
1663
+ const why = ambiguous ? `several elements in the rebuilt subtree share ${snapshot.attr}="${snapshot.value}", so there is no way to tell which one the user was in \u2014 a shared name (a radio group) identifies a GROUP, not a control. Add a unique id or data-focus-key to each` : "no element in the rebuilt subtree carries the same identity. Give it a stable id, name, or data-focus-key that the rebuild reproduces";
1664
+ return `${where}: a reactive rebuild discarded the focused element <${snapshot.tag.toLowerCase()}>${id} and could not re-establish it, so focus fell back to <body>. An in-progress edit ends here, and an in-progress IME composition is lost outright \u2014 a composition belongs to the DOM node, so it cannot survive the node being destroyed. ${why}, or stop the rebuild from happening: key the subtree with match(() => shapeKey(), { \u2026 }) so it is re-created only when the shape actually changes, and drive the rest with reactive attributes and text children, which mutate nodes in place instead of replacing them.`;
1665
+ });
1666
+ }
1667
+
1519
1668
  // src/reactivity/bindChildNode.ts
1520
- var _isDev6 = isDev();
1521
1669
  function bindChildNode(placeholder, getter) {
1522
1670
  let lastNodes = [];
1523
1671
  function commit() {
@@ -1528,6 +1676,7 @@ function bindChildNode(placeholder, getter) {
1528
1676
  reportError(err, { phase: "binding", name: "bindChildNode", node: placeholder });
1529
1677
  return;
1530
1678
  }
1679
+ const focused = captureFocusWithin(lastNodes);
1531
1680
  if (result == null || typeof result === "boolean") {
1532
1681
  for (let i = 0; i < lastNodes.length; i++) {
1533
1682
  const node = lastNodes[i];
@@ -1535,6 +1684,7 @@ function bindChildNode(placeholder, getter) {
1535
1684
  if (node.parentNode) node.parentNode.removeChild(node);
1536
1685
  }
1537
1686
  lastNodes.length = 0;
1687
+ restoreFocusWithin(focused, lastNodes, "bindChildNode");
1538
1688
  return;
1539
1689
  }
1540
1690
  const parent = placeholder.parentNode;
@@ -1551,8 +1701,7 @@ function bindChildNode(placeholder, getter) {
1551
1701
  if (item == null || typeof item === "boolean") continue;
1552
1702
  const node = item instanceof Node ? item : document.createTextNode(String(item));
1553
1703
  if (seen.has(node)) {
1554
- if (_isDev6)
1555
- devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1704
+ if (DEV) devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1556
1705
  continue;
1557
1706
  }
1558
1707
  seen.add(node);
@@ -1585,12 +1734,12 @@ function bindChildNode(placeholder, getter) {
1585
1734
  prev = node;
1586
1735
  }
1587
1736
  lastNodes = newNodes;
1737
+ restoreFocusWithin(focused, newNodes, "bindChildNode");
1588
1738
  }
1589
1739
  return reactiveBinding2(commit, placeholder);
1590
1740
  }
1591
1741
 
1592
1742
  // src/core/rendering/tagFactory.ts
1593
- var _isDev7 = isDev();
1594
1743
  var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1595
1744
  function isBlockedTag(tag) {
1596
1745
  return BLOCKED_TAGS.has(tag.toLowerCase());
@@ -1608,18 +1757,9 @@ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1608
1757
  "self",
1609
1758
  "frames"
1610
1759
  ]);
1611
- function looksLikeClassList(s) {
1612
- const t = s.trim();
1613
- if (!t) return false;
1614
- const tokens = t.split(/\s+/);
1615
- let sawClassish = false;
1616
- for (let i = 0; i < tokens.length; i++) {
1617
- const tok = tokens[i];
1618
- if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return false;
1619
- if (/[-:/0-9]/.test(tok)) sawClassish = true;
1620
- }
1621
- return sawClassish;
1622
- }
1760
+ var warnedLoneStrings = /* @__PURE__ */ new Set();
1761
+ var MAX_WARNED_LONE_STRINGS = 100;
1762
+ var loneStringCapAnnounced = false;
1623
1763
  var kebabCache = /* @__PURE__ */ new Map();
1624
1764
  function toKebab(prop) {
1625
1765
  let cached = kebabCache.get(prop);
@@ -1631,13 +1771,13 @@ function toKebab(prop) {
1631
1771
  function applyStyle(el, style) {
1632
1772
  if (typeof style === "function") {
1633
1773
  const teardown = reactiveBinding2(() => {
1634
- el.setAttribute("style", sanitizeStyleAttribute(String(style())));
1774
+ el.setAttribute("style", sanitizeStyleAttribute(String(style()), { element: el }));
1635
1775
  }, el);
1636
1776
  registerDisposer(el, teardown);
1637
1777
  return;
1638
1778
  }
1639
1779
  if (typeof style === "string") {
1640
- el.setAttribute("style", sanitizeStyleAttribute(style));
1780
+ el.setAttribute("style", sanitizeStyleAttribute(style, { element: el }));
1641
1781
  return;
1642
1782
  }
1643
1783
  const htmlEl = el;
@@ -1647,11 +1787,11 @@ function applyStyle(el, style) {
1647
1787
  if (typeof val === "function") {
1648
1788
  const getter = val;
1649
1789
  const teardown = reactiveBinding2(() => {
1650
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter())));
1790
+ htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
1651
1791
  }, el);
1652
1792
  registerDisposer(el, teardown);
1653
1793
  } else {
1654
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val)));
1794
+ htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
1655
1795
  }
1656
1796
  }
1657
1797
  }
@@ -1758,11 +1898,26 @@ var tagFactory = (tag, ns) => {
1758
1898
  appendChildren(el, second);
1759
1899
  return el;
1760
1900
  }
1761
- if (_isDev7 && looksLikeClassList(first)) {
1762
- devWarn(
1763
- `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`
1764
- );
1765
- }
1901
+ devWarnLazy(() => {
1902
+ const tokens = first.trim().split(/\s+/);
1903
+ if (tokens.length < 2) return "";
1904
+ let utilityTokens = 0;
1905
+ for (let i = 0; i < tokens.length; i++) {
1906
+ const tok = tokens[i];
1907
+ if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return "";
1908
+ if (/[-:/0-9]/.test(tok)) utilityTokens++;
1909
+ }
1910
+ if (utilityTokens < 2) return "";
1911
+ const key = `${tag}|${first}`;
1912
+ if (warnedLoneStrings.has(key)) return "";
1913
+ if (warnedLoneStrings.size >= MAX_WARNED_LONE_STRINGS) {
1914
+ if (loneStringCapAnnounced) return "";
1915
+ loneStringCapAnnounced = true;
1916
+ return `tagFactory: ${MAX_WARNED_LONE_STRINGS} distinct lone-string class warnings have been reported; suppressing further ones for the rest of this session so they cannot flood the console. Fix the reported ones and reload to see any that remain.`;
1917
+ }
1918
+ warnedLoneStrings.add(key);
1919
+ return `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`;
1920
+ });
1766
1921
  el.textContent = first;
1767
1922
  return el;
1768
1923
  }
@@ -1779,7 +1934,7 @@ var tagFactory = (tag, ns) => {
1779
1934
  if (pClass != null) applyClass(el, pClass);
1780
1935
  const pId = props.id;
1781
1936
  if (pId != null) {
1782
- if (_isDev7 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1937
+ if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1783
1938
  devWarn(
1784
1939
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1785
1940
  );
@@ -1794,7 +1949,7 @@ var tagFactory = (tag, ns) => {
1794
1949
  const handler = pOn[ev];
1795
1950
  if (typeof handler === "function") {
1796
1951
  el.addEventListener(ev, handler);
1797
- } else if (_isDev7) {
1952
+ } else if (DEV) {
1798
1953
  devWarn(
1799
1954
  `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1800
1955
  );
package/dist/ecosystem.js CHANGED
@@ -7,19 +7,19 @@ import {
7
7
  mobXAdapter,
8
8
  reduxAdapter,
9
9
  zustandAdapter
10
- } from "./chunk-FB7KQXZI.js";
10
+ } from "./chunk-GDUNQCCW.js";
11
11
  import "./chunk-WWV3SJ3L.js";
12
- import "./chunk-I3VI34DP.js";
13
- import "./chunk-SHDVWATN.js";
14
- import "./chunk-DCDS2PQ6.js";
15
- import "./chunk-5INI7D2L.js";
12
+ import "./chunk-6ZGI3RBE.js";
13
+ import "./chunk-HJEPKZ2O.js";
14
+ import "./chunk-2LOYNGBY.js";
15
+ import "./chunk-IXKSNWV5.js";
16
16
  import "./chunk-7ZHH77QA.js";
17
- import "./chunk-PFTDGEPZ.js";
18
- import "./chunk-TIRZCERI.js";
19
- import "./chunk-DHDZ7IVN.js";
20
- import "./chunk-UFSQEO7P.js";
21
- import "./chunk-D3SZ4LVS.js";
22
- import "./chunk-VPP2FONR.js";
17
+ import "./chunk-7VEC3UD2.js";
18
+ import "./chunk-5DXA2J44.js";
19
+ import "./chunk-QKRPLZ2V.js";
20
+ import "./chunk-HECVW343.js";
21
+ import "./chunk-7ALC7KGE.js";
22
+ import "./chunk-2WLZ6757.js";
23
23
  export {
24
24
  antdAdapter,
25
25
  chakraAdapter,