sibujs 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +29 -25
  2. package/dist/browser.cjs +804 -2
  3. package/dist/browser.d.cts +591 -1
  4. package/dist/browser.d.ts +591 -1
  5. package/dist/browser.js +50 -8
  6. package/dist/build.cjs +706 -161
  7. package/dist/build.js +21 -12
  8. package/dist/cdn.global.js +188 -7
  9. package/dist/chunk-2BYQDGN3.js +742 -0
  10. package/dist/chunk-32DY64NT.js +282 -0
  11. package/dist/chunk-3AIRKM3B.js +1263 -0
  12. package/dist/chunk-3X2YG6YM.js +505 -0
  13. package/dist/chunk-5X6PP2UK.js +28 -0
  14. package/dist/chunk-77L6NL3X.js +1097 -0
  15. package/dist/chunk-B7SWRFUT.js +332 -0
  16. package/dist/chunk-BGN5ZMP4.js +26 -0
  17. package/dist/chunk-BTU3TJDS.js +365 -0
  18. package/dist/chunk-CHF5OHIA.js +61 -0
  19. package/dist/chunk-CMBFNA7L.js +27 -0
  20. package/dist/chunk-DAHRH4ON.js +331 -0
  21. package/dist/chunk-EBGIRKQY.js +616 -0
  22. package/dist/chunk-EUZND3CB.js +27 -0
  23. package/dist/chunk-F3FA4F32.js +292 -0
  24. package/dist/chunk-GCOK2LC3.js +282 -0
  25. package/dist/chunk-JAKHTMQU.js +1000 -0
  26. package/dist/chunk-JCI5M6U6.js +956 -0
  27. package/dist/chunk-KQPDEVVS.js +398 -0
  28. package/dist/chunk-NEKUBFPT.js +60 -0
  29. package/dist/chunk-NYVAC6P5.js +37 -0
  30. package/dist/chunk-OUZZEE4S.js +365 -0
  31. package/dist/chunk-P6W3STU4.js +2249 -0
  32. package/dist/chunk-PTQJDMRT.js +146 -0
  33. package/dist/chunk-QWZG56ET.js +2744 -0
  34. package/dist/chunk-TSOKIX5Z.js +654 -0
  35. package/dist/chunk-VMVDTCXB.js +712 -0
  36. package/dist/chunk-VRW3FULF.js +725 -0
  37. package/dist/chunk-WZSPOOER.js +84 -0
  38. package/dist/chunk-YT6HQ6AM.js +14 -0
  39. package/dist/chunk-ZD6OAMTH.js +277 -0
  40. package/dist/contracts-DDrwxvJ-.d.cts +245 -0
  41. package/dist/contracts-DDrwxvJ-.d.ts +245 -0
  42. package/dist/data.cjs +35 -2
  43. package/dist/data.d.cts +7 -0
  44. package/dist/data.d.ts +7 -0
  45. package/dist/data.js +9 -8
  46. package/dist/devtools.cjs +122 -0
  47. package/dist/devtools.d.cts +69 -461
  48. package/dist/devtools.d.ts +69 -461
  49. package/dist/devtools.js +127 -6
  50. package/dist/ecosystem.cjs +68 -23
  51. package/dist/ecosystem.d.cts +1 -1
  52. package/dist/ecosystem.d.ts +1 -1
  53. package/dist/ecosystem.js +10 -9
  54. package/dist/extras.cjs +1252 -82
  55. package/dist/extras.d.cts +5 -5
  56. package/dist/extras.d.ts +5 -5
  57. package/dist/extras.js +69 -24
  58. package/dist/index.cjs +708 -161
  59. package/dist/index.d.cts +397 -17
  60. package/dist/index.d.ts +397 -17
  61. package/dist/index.js +39 -17
  62. package/dist/introspect-BumjnBKr.d.cts +477 -0
  63. package/dist/introspect-CZrlcaYy.d.ts +477 -0
  64. package/dist/introspect-Cb0zgpi2.d.cts +477 -0
  65. package/dist/introspect-Y2xNXGSf.d.ts +477 -0
  66. package/dist/motion.js +4 -4
  67. package/dist/patterns.cjs +51 -2
  68. package/dist/patterns.d.cts +18 -8
  69. package/dist/patterns.d.ts +18 -8
  70. package/dist/patterns.js +7 -7
  71. package/dist/performance.js +4 -4
  72. package/dist/plugins.cjs +473 -98
  73. package/dist/plugins.d.cts +27 -4
  74. package/dist/plugins.d.ts +27 -4
  75. package/dist/plugins.js +156 -37
  76. package/dist/ssr-4PBXAOO3.js +40 -0
  77. package/dist/ssr-Do_SiVoL.d.cts +201 -0
  78. package/dist/ssr-Do_SiVoL.d.ts +201 -0
  79. package/dist/ssr.cjs +357 -77
  80. package/dist/ssr.d.cts +10 -1
  81. package/dist/ssr.d.ts +10 -1
  82. package/dist/ssr.js +13 -10
  83. package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
  84. package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
  85. package/dist/testing.cjs +233 -2
  86. package/dist/testing.d.cts +42 -1
  87. package/dist/testing.d.ts +42 -1
  88. package/dist/testing.js +129 -2
  89. package/dist/ui.cjs +374 -3
  90. package/dist/ui.d.cts +252 -2
  91. package/dist/ui.d.ts +252 -2
  92. package/dist/ui.js +328 -8
  93. package/dist/widgets.js +7 -7
  94. package/package.json +1 -1
package/dist/ssr.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { T as TrustedHTML, c as collectStream, d as deserializeState, h as hydrate, a as hydrateIslands, b as hydrateProgressively, i as island, r as renderToDocument, e as renderToReadableStream, f as renderToStream, g as renderToString, j as renderToSuspenseStream, k as resetSSRState, s as serializeState, l as ssrSuspense, m as suspenseSwapScript, t as trustHTML } from './ssr-BA6sxxUd.cjs';
1
+ export { H as HydrateOptions, a as HydrationMismatch, T as TrustedHTML, c as collectStream, d as deserializeState, e as escapeScriptJson, h as hydrate, b as hydrateIslands, f as hydrateProgressively, i as island, r as renderToDocument, g as renderToReadableStream, j as renderToStream, k as renderToString, l as renderToSuspenseStream, m as resetSSRState, s as serializeState, n as ssrSuspense, o as suspenseSwapScript, t as trustHTML } from './ssr-Do_SiVoL.cjs';
2
2
 
3
3
  interface HeadProps {
4
4
  title?: string | (() => string);
@@ -18,6 +18,15 @@ interface HeadProps {
18
18
  declare function Head(props: HeadProps): Comment;
19
19
  /**
20
20
  * Sets structured data (JSON-LD) for SEO.
21
+ *
22
+ * Security: the serialized JSON is passed through `escapeScriptJsonLocal`
23
+ * which unicode-escapes `<`, `>`, `&`, `U+2028`, and `U+2029`. This is
24
+ * defense-in-depth: when the element is inserted via `document.createElement`
25
+ * + `textContent` the browser will NOT re-parse the body, so `</script>`
26
+ * cannot break out of the tag at insertion time. However, tools that
27
+ * later serialize `document.head.innerHTML` DO re-parse, and the server
28
+ * side of any SSR roundtrip would see the raw text. Escaping here makes
29
+ * both paths safe.
21
30
  */
22
31
  declare function setStructuredData(data: Record<string, unknown>): void;
23
32
  /**
package/dist/ssr.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { T as TrustedHTML, c as collectStream, d as deserializeState, h as hydrate, a as hydrateIslands, b as hydrateProgressively, i as island, r as renderToDocument, e as renderToReadableStream, f as renderToStream, g as renderToString, j as renderToSuspenseStream, k as resetSSRState, s as serializeState, l as ssrSuspense, m as suspenseSwapScript, t as trustHTML } from './ssr-BA6sxxUd.js';
1
+ export { H as HydrateOptions, a as HydrationMismatch, T as TrustedHTML, c as collectStream, d as deserializeState, e as escapeScriptJson, h as hydrate, b as hydrateIslands, f as hydrateProgressively, i as island, r as renderToDocument, g as renderToReadableStream, j as renderToStream, k as renderToString, l as renderToSuspenseStream, m as resetSSRState, s as serializeState, n as ssrSuspense, o as suspenseSwapScript, t as trustHTML } from './ssr-Do_SiVoL.js';
2
2
 
3
3
  interface HeadProps {
4
4
  title?: string | (() => string);
@@ -18,6 +18,15 @@ interface HeadProps {
18
18
  declare function Head(props: HeadProps): Comment;
19
19
  /**
20
20
  * Sets structured data (JSON-LD) for SEO.
21
+ *
22
+ * Security: the serialized JSON is passed through `escapeScriptJsonLocal`
23
+ * which unicode-escapes `<`, `>`, `&`, `U+2028`, and `U+2029`. This is
24
+ * defense-in-depth: when the element is inserted via `document.createElement`
25
+ * + `textContent` the browser will NOT re-parse the body, so `</script>`
26
+ * cannot break out of the tag at insertion time. However, tools that
27
+ * later serialize `document.head.innerHTML` DO re-parse, and the server
28
+ * side of any SSR roundtrip would see the raw text. Escaping here makes
29
+ * both paths safe.
21
30
  */
22
31
  declare function setStructuredData(data: Record<string, unknown>): void;
23
32
  /**
package/dist/ssr.js CHANGED
@@ -22,10 +22,11 @@ import {
22
22
  wasm,
23
23
  worker,
24
24
  workerFn
25
- } from "./chunk-LA6KQEDU.js";
25
+ } from "./chunk-2BYQDGN3.js";
26
26
  import {
27
27
  collectStream,
28
28
  deserializeState,
29
+ escapeScriptJson,
29
30
  hydrate,
30
31
  hydrateIslands,
31
32
  hydrateProgressively,
@@ -40,15 +41,16 @@ import {
40
41
  ssrSuspense,
41
42
  suspenseSwapScript,
42
43
  trustHTML
43
- } from "./chunk-WUHJISPP.js";
44
- import "./chunk-NHUC2QWH.js";
45
- import "./chunk-WADYRCO2.js";
46
- import "./chunk-23VV7YD3.js";
47
- import "./chunk-6SA3QQES.js";
48
- import "./chunk-CHJ27IGK.js";
49
- import "./chunk-V2XTI523.js";
50
- import "./chunk-UNXCEF6S.js";
51
- import "./chunk-MLKGABMK.js";
44
+ } from "./chunk-3X2YG6YM.js";
45
+ import "./chunk-32DY64NT.js";
46
+ import "./chunk-F3FA4F32.js";
47
+ import "./chunk-PTQJDMRT.js";
48
+ import "./chunk-CMBFNA7L.js";
49
+ import "./chunk-CHF5OHIA.js";
50
+ import "./chunk-EUZND3CB.js";
51
+ import "./chunk-WZSPOOER.js";
52
+ import "./chunk-ZD6OAMTH.js";
53
+ import "./chunk-5X6PP2UK.js";
52
54
  export {
53
55
  Head,
54
56
  clearWasmCache,
@@ -63,6 +65,7 @@ export {
63
65
  createWorkerPool,
64
66
  defineRemoteComponent,
65
67
  deserializeState,
68
+ escapeScriptJson,
66
69
  generateStaticSite,
67
70
  hydrate,
68
71
  hydrateIslands,
@@ -0,0 +1,47 @@
1
+ type NodeChild = Node | Element | Text | Comment | string | number | boolean | (() => NodeChild) | null | undefined;
2
+ type NodeChildren = NodeChild | NodeChild[] | NodeChild[][] | (() => NodeChild | NodeChild[]);
3
+
4
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
5
+ interface TagProps {
6
+ id?: string;
7
+ class?: string | (() => string) | Record<string, boolean | (() => boolean)>;
8
+ style?: Record<string, string | number | (() => string | number)> | string | (() => string);
9
+ ref?: {
10
+ current: Element | null;
11
+ };
12
+ nodes?: NodeChildren;
13
+ on?: Record<string, (ev: Event) => void>;
14
+ /** Called with the element after creation — useful for imperative bindings */
15
+ onElement?: (el: HTMLElement) => void;
16
+ [attr: string]: unknown;
17
+ }
18
+ /**
19
+ * Factory for creating HTML or SVG elements with reactive props and nodes.
20
+ *
21
+ * Calling conventions:
22
+ *
23
+ * tag() empty element
24
+ * tag("text") element with text content
25
+ * tag(42) element with numeric text content
26
+ * tag([childA, childB]) element with children (array)
27
+ * tag(node) element wrapping a single existing node
28
+ * tag(getter) element with a reactive child
29
+ * tag("className", children) positional: class + children
30
+ * tag({ ...props }) full props object (children via props.nodes)
31
+ * tag({ ...props }, children) props + children (no need for `nodes:` key!)
32
+ *
33
+ * The last form is the "deeply-nested shorthand" the codebase favours:
34
+ *
35
+ * div({ class: "card" }, [
36
+ * h1({ class: "title" }, "Hello"),
37
+ * p({ class: "body" }, "World"),
38
+ * div({ class: "row" }, [
39
+ * span({ id: "x" }, "child"),
40
+ * ]),
41
+ * ])
42
+ *
43
+ * `children` overrides `props.nodes` when both are present.
44
+ */
45
+ declare const tagFactory: (tag: string, ns?: string) => (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
46
+
47
+ export { type NodeChildren as N, SVG_NS as S, type TagProps as T, type NodeChild as a, tagFactory as t };
@@ -0,0 +1,47 @@
1
+ type NodeChild = Node | Element | Text | Comment | string | number | boolean | (() => NodeChild) | null | undefined;
2
+ type NodeChildren = NodeChild | NodeChild[] | NodeChild[][] | (() => NodeChild | NodeChild[]);
3
+
4
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
5
+ interface TagProps {
6
+ id?: string;
7
+ class?: string | (() => string) | Record<string, boolean | (() => boolean)>;
8
+ style?: Record<string, string | number | (() => string | number)> | string | (() => string);
9
+ ref?: {
10
+ current: Element | null;
11
+ };
12
+ nodes?: NodeChildren;
13
+ on?: Record<string, (ev: Event) => void>;
14
+ /** Called with the element after creation — useful for imperative bindings */
15
+ onElement?: (el: HTMLElement) => void;
16
+ [attr: string]: unknown;
17
+ }
18
+ /**
19
+ * Factory for creating HTML or SVG elements with reactive props and nodes.
20
+ *
21
+ * Calling conventions:
22
+ *
23
+ * tag() empty element
24
+ * tag("text") element with text content
25
+ * tag(42) element with numeric text content
26
+ * tag([childA, childB]) element with children (array)
27
+ * tag(node) element wrapping a single existing node
28
+ * tag(getter) element with a reactive child
29
+ * tag("className", children) positional: class + children
30
+ * tag({ ...props }) full props object (children via props.nodes)
31
+ * tag({ ...props }, children) props + children (no need for `nodes:` key!)
32
+ *
33
+ * The last form is the "deeply-nested shorthand" the codebase favours:
34
+ *
35
+ * div({ class: "card" }, [
36
+ * h1({ class: "title" }, "Hello"),
37
+ * p({ class: "body" }, "World"),
38
+ * div({ class: "row" }, [
39
+ * span({ id: "x" }, "child"),
40
+ * ]),
41
+ * ])
42
+ *
43
+ * `children` overrides `props.nodes` when both are present.
44
+ */
45
+ declare const tagFactory: (tag: string, ns?: string) => (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
46
+
47
+ export { type NodeChildren as N, SVG_NS as S, type TagProps as T, type NodeChild as a, tagFactory as t };
package/dist/testing.cjs CHANGED
@@ -45,14 +45,23 @@ __export(testing_exports, {
45
45
  createTimerMock: () => createTimerMock,
46
46
  createUniversalAdapter: () => createUniversalAdapter,
47
47
  createVisualSuite: () => createVisualSuite,
48
+ findByRole: () => findByRole,
49
+ findByTestId: () => findByTestId,
50
+ findByText: () => findByText,
48
51
  fireEvent: () => fireEvent,
49
52
  matchSnapshot: () => matchSnapshot,
50
53
  mockRouter: () => mockRouter,
51
54
  mockStore: () => mockStore,
55
+ queryByLabel: () => queryByLabel,
56
+ queryByRole: () => queryByRole,
57
+ queryByTestId: () => queryByTestId,
58
+ queryByText: () => queryByText,
52
59
  render: () => render,
53
60
  snapshotComponent: () => snapshotComponent,
54
61
  testComponent: () => testComponent,
55
- waitFor: () => waitFor
62
+ type: () => type,
63
+ waitFor: () => waitFor,
64
+ waitForSignal: () => waitForSignal
56
65
  });
57
66
  module.exports = __toCommonJS(testing_exports);
58
67
 
@@ -1445,6 +1454,219 @@ function testComponent(component, options = {}) {
1445
1454
  };
1446
1455
  }
1447
1456
 
1457
+ // src/core/dev.ts
1458
+ function isDev() {
1459
+ return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1460
+ }
1461
+ var _isDev = isDev();
1462
+ function devAssert(condition, message) {
1463
+ if (_isDev && !condition) {
1464
+ throw new Error(`[Sibu] ${message}`);
1465
+ }
1466
+ }
1467
+
1468
+ // src/reactivity/track.ts
1469
+ var _isDev2 = isDev();
1470
+ var subscriberStack = new Array(32);
1471
+ var stackCapacity = 32;
1472
+ var stackTop = -1;
1473
+ var currentSubscriber = null;
1474
+ var SUBS = "__s";
1475
+ function track(effectFn, subscriber) {
1476
+ if (!subscriber) subscriber = effectFn;
1477
+ cleanup(subscriber);
1478
+ ++stackTop;
1479
+ if (stackTop >= stackCapacity) {
1480
+ stackCapacity *= 2;
1481
+ subscriberStack.length = stackCapacity;
1482
+ }
1483
+ subscriberStack[stackTop] = subscriber;
1484
+ currentSubscriber = subscriber;
1485
+ try {
1486
+ effectFn();
1487
+ } finally {
1488
+ stackTop--;
1489
+ currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
1490
+ }
1491
+ return () => cleanup(subscriber);
1492
+ }
1493
+ function cleanup(subscriber) {
1494
+ const sub = subscriber;
1495
+ const singleDep = sub._dep;
1496
+ if (singleDep !== void 0) {
1497
+ const subs = singleDep[SUBS];
1498
+ if (subs) {
1499
+ subs.delete(subscriber);
1500
+ if (singleDep.__f === subscriber) {
1501
+ singleDep.__f = void 0;
1502
+ }
1503
+ }
1504
+ sub._dep = void 0;
1505
+ return;
1506
+ }
1507
+ const deps = sub._deps;
1508
+ if (!deps || deps.size === 0) return;
1509
+ for (const signal of deps) {
1510
+ const subs = signal[SUBS];
1511
+ if (subs) {
1512
+ subs.delete(subscriber);
1513
+ if (signal.__f === subscriber) {
1514
+ signal.__f = void 0;
1515
+ }
1516
+ }
1517
+ }
1518
+ deps.clear();
1519
+ }
1520
+
1521
+ // src/core/ssr-context.ts
1522
+ var ssrMode = false;
1523
+ function isSSR() {
1524
+ return ssrMode;
1525
+ }
1526
+
1527
+ // src/core/signals/effect.ts
1528
+ var _g = globalThis;
1529
+ function effect(effectFn, options) {
1530
+ devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1531
+ if (isSSR()) return () => {
1532
+ };
1533
+ const onError = options?.onError;
1534
+ const wrappedFn = onError ? () => {
1535
+ try {
1536
+ effectFn();
1537
+ } catch (err) {
1538
+ onError(err);
1539
+ }
1540
+ } : effectFn;
1541
+ let cleanupHandle = () => {
1542
+ };
1543
+ const subscriber = () => {
1544
+ cleanupHandle();
1545
+ cleanupHandle = track(wrappedFn, subscriber);
1546
+ };
1547
+ cleanupHandle = track(wrappedFn, subscriber);
1548
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1549
+ if (hook) hook.emit("effect:create", { effectFn });
1550
+ return () => {
1551
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1552
+ if (h) h.emit("effect:destroy", { effectFn });
1553
+ cleanupHandle();
1554
+ };
1555
+ }
1556
+
1557
+ // src/testing/queries.ts
1558
+ function queryByText(container, text) {
1559
+ const walk = (node) => {
1560
+ if (node.childNodes.length === 1 && node.childNodes[0].nodeType === 3) {
1561
+ if (node.textContent?.includes(text)) return node;
1562
+ }
1563
+ for (const child of Array.from(node.children)) {
1564
+ const found = walk(child);
1565
+ if (found) return found;
1566
+ }
1567
+ return null;
1568
+ };
1569
+ return walk(container);
1570
+ }
1571
+ function queryByTestId(container, testId) {
1572
+ return container.querySelector(`[data-testid="${testId}"]`);
1573
+ }
1574
+ function queryByRole(container, role) {
1575
+ return container.querySelector(`[role="${role}"]`);
1576
+ }
1577
+ function cssEscape(value) {
1578
+ const g = globalThis;
1579
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
1580
+ return value.replace(/[^\w-]/g, (m) => `\\${m.charCodeAt(0).toString(16)} `);
1581
+ }
1582
+ function queryByLabel(container, labelText) {
1583
+ const labels = Array.from(container.querySelectorAll("label"));
1584
+ for (const label of labels) {
1585
+ if (label.textContent?.trim() === labelText) {
1586
+ const forId = label.getAttribute("for");
1587
+ if (forId) {
1588
+ const target = container.querySelector(`#${cssEscape(forId)}`);
1589
+ if (target) return target;
1590
+ }
1591
+ const child = label.querySelector("input, select, textarea, button");
1592
+ if (child) return child;
1593
+ }
1594
+ }
1595
+ return container.querySelector(`[aria-label="${labelText}"]`);
1596
+ }
1597
+ async function pollUntil(fn, timeout, interval, errorIfTimeout) {
1598
+ const start = Date.now();
1599
+ return new Promise((resolve, reject) => {
1600
+ const check = () => {
1601
+ const result = fn();
1602
+ if (result !== null) {
1603
+ resolve(result);
1604
+ return;
1605
+ }
1606
+ if (Date.now() - start >= timeout) {
1607
+ reject(new Error(errorIfTimeout));
1608
+ return;
1609
+ }
1610
+ setTimeout(check, interval);
1611
+ };
1612
+ check();
1613
+ });
1614
+ }
1615
+ function findByText(container, text, options = {}) {
1616
+ return pollUntil(
1617
+ () => queryByText(container, text),
1618
+ options.timeout ?? 1e3,
1619
+ options.interval ?? 50,
1620
+ `findByText: no element with text "${text}" after ${options.timeout ?? 1e3}ms`
1621
+ );
1622
+ }
1623
+ function findByTestId(container, testId, options = {}) {
1624
+ return pollUntil(
1625
+ () => queryByTestId(container, testId),
1626
+ options.timeout ?? 1e3,
1627
+ options.interval ?? 50,
1628
+ `findByTestId: no element with data-testid="${testId}" after ${options.timeout ?? 1e3}ms`
1629
+ );
1630
+ }
1631
+ function findByRole(container, role, options = {}) {
1632
+ return pollUntil(
1633
+ () => queryByRole(container, role),
1634
+ options.timeout ?? 1e3,
1635
+ options.interval ?? 50,
1636
+ `findByRole: no element with role="${role}" after ${options.timeout ?? 1e3}ms`
1637
+ );
1638
+ }
1639
+ function waitForSignal(getter, predicate, options = {}) {
1640
+ const timeoutMs = options.timeout ?? 1e3;
1641
+ return new Promise((resolve, reject) => {
1642
+ let resolved = false;
1643
+ const timer = setTimeout(() => {
1644
+ if (!resolved) {
1645
+ resolved = true;
1646
+ teardown();
1647
+ reject(new Error(`waitForSignal: predicate did not match within ${timeoutMs}ms`));
1648
+ }
1649
+ }, timeoutMs);
1650
+ const teardown = effect(() => {
1651
+ if (resolved) return;
1652
+ const value = getter();
1653
+ if (predicate(value)) {
1654
+ resolved = true;
1655
+ clearTimeout(timer);
1656
+ queueMicrotask(() => teardown());
1657
+ resolve(value);
1658
+ }
1659
+ });
1660
+ });
1661
+ }
1662
+ function type(element, text) {
1663
+ for (const char of text) {
1664
+ element.value += char;
1665
+ element.dispatchEvent(new InputEvent("input", { bubbles: true, data: char }));
1666
+ }
1667
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1668
+ }
1669
+
1448
1670
  // src/testing/snapshot.ts
1449
1671
  function serializeElement2(el, indent) {
1450
1672
  const pad = " ".repeat(indent);
@@ -1908,12 +2130,21 @@ function mockStore(initialState) {
1908
2130
  createTimerMock,
1909
2131
  createUniversalAdapter,
1910
2132
  createVisualSuite,
2133
+ findByRole,
2134
+ findByTestId,
2135
+ findByText,
1911
2136
  fireEvent,
1912
2137
  matchSnapshot,
1913
2138
  mockRouter,
1914
2139
  mockStore,
2140
+ queryByLabel,
2141
+ queryByRole,
2142
+ queryByTestId,
2143
+ queryByText,
1915
2144
  render,
1916
2145
  snapshotComponent,
1917
2146
  testComponent,
1918
- waitFor
2147
+ type,
2148
+ waitFor,
2149
+ waitForSignal
1919
2150
  });
@@ -315,6 +315,47 @@ declare function testComponent(component: (() => HTMLElement) | HTMLElement, opt
315
315
  destroy: () => void;
316
316
  };
317
317
 
318
+ /**
319
+ * Find an element by its exact or substring text content. Returns
320
+ * `null` if no match is found — unlike `getByText`, does not throw.
321
+ */
322
+ declare function queryByText(container: HTMLElement, text: string): HTMLElement | null;
323
+ declare function queryByTestId(container: HTMLElement, testId: string): HTMLElement | null;
324
+ declare function queryByRole(container: HTMLElement, role: string): HTMLElement | null;
325
+ declare function queryByLabel(container: HTMLElement, labelText: string): HTMLElement | null;
326
+ interface FindOptions {
327
+ timeout?: number;
328
+ interval?: number;
329
+ }
330
+ /**
331
+ * Resolve with the first element whose text matches, polling until
332
+ * `timeout` ms elapse. Useful for async content (data fetching,
333
+ * transitions, etc.) that appears after the initial render.
334
+ */
335
+ declare function findByText(container: HTMLElement, text: string, options?: FindOptions): Promise<HTMLElement>;
336
+ declare function findByTestId(container: HTMLElement, testId: string, options?: FindOptions): Promise<HTMLElement>;
337
+ declare function findByRole(container: HTMLElement, role: string, options?: FindOptions): Promise<HTMLElement>;
338
+ /**
339
+ * Wait until a reactive getter satisfies a predicate. Unlike `waitFor`,
340
+ * this subscribes to the getter so it reacts immediately on signal
341
+ * updates rather than polling. Falls back to a `timeout` rejection.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * await waitForSignal(() => loading(), (v) => v === false);
346
+ * ```
347
+ */
348
+ declare function waitForSignal<T>(getter: () => T, predicate: (value: T) => boolean, options?: {
349
+ timeout?: number;
350
+ }): Promise<T>;
351
+ /**
352
+ * Type a full string into an input, dispatching an input event after
353
+ * each character. This is closer to real user input than a single
354
+ * `fireEvent.input(el, value)` call and catches handlers that only
355
+ * run on specific event shapes.
356
+ */
357
+ declare function type(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
358
+
318
359
  /**
319
360
  * Snapshot testing utilities for SibuJS components.
320
361
  * Capture and compare component output over time.
@@ -488,4 +529,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
488
529
  reset: () => void;
489
530
  };
490
531
 
491
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, fireEvent, matchSnapshot, mockRouter, mockStore, render, snapshotComponent, testComponent, waitFor };
532
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };
package/dist/testing.d.ts CHANGED
@@ -315,6 +315,47 @@ declare function testComponent(component: (() => HTMLElement) | HTMLElement, opt
315
315
  destroy: () => void;
316
316
  };
317
317
 
318
+ /**
319
+ * Find an element by its exact or substring text content. Returns
320
+ * `null` if no match is found — unlike `getByText`, does not throw.
321
+ */
322
+ declare function queryByText(container: HTMLElement, text: string): HTMLElement | null;
323
+ declare function queryByTestId(container: HTMLElement, testId: string): HTMLElement | null;
324
+ declare function queryByRole(container: HTMLElement, role: string): HTMLElement | null;
325
+ declare function queryByLabel(container: HTMLElement, labelText: string): HTMLElement | null;
326
+ interface FindOptions {
327
+ timeout?: number;
328
+ interval?: number;
329
+ }
330
+ /**
331
+ * Resolve with the first element whose text matches, polling until
332
+ * `timeout` ms elapse. Useful for async content (data fetching,
333
+ * transitions, etc.) that appears after the initial render.
334
+ */
335
+ declare function findByText(container: HTMLElement, text: string, options?: FindOptions): Promise<HTMLElement>;
336
+ declare function findByTestId(container: HTMLElement, testId: string, options?: FindOptions): Promise<HTMLElement>;
337
+ declare function findByRole(container: HTMLElement, role: string, options?: FindOptions): Promise<HTMLElement>;
338
+ /**
339
+ * Wait until a reactive getter satisfies a predicate. Unlike `waitFor`,
340
+ * this subscribes to the getter so it reacts immediately on signal
341
+ * updates rather than polling. Falls back to a `timeout` rejection.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * await waitForSignal(() => loading(), (v) => v === false);
346
+ * ```
347
+ */
348
+ declare function waitForSignal<T>(getter: () => T, predicate: (value: T) => boolean, options?: {
349
+ timeout?: number;
350
+ }): Promise<T>;
351
+ /**
352
+ * Type a full string into an input, dispatching an input event after
353
+ * each character. This is closer to real user input than a single
354
+ * `fireEvent.input(el, value)` call and catches handlers that only
355
+ * run on specific event shapes.
356
+ */
357
+ declare function type(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
358
+
318
359
  /**
319
360
  * Snapshot testing utilities for SibuJS components.
320
361
  * Capture and compare component output over time.
@@ -488,4 +529,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
488
529
  reset: () => void;
489
530
  };
490
531
 
491
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, fireEvent, matchSnapshot, mockRouter, mockStore, render, snapshotComponent, testComponent, waitFor };
532
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };