sibujs 4.13.0 → 4.14.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 (107) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +15 -2
  3. package/dist/browser.js +9 -7
  4. package/dist/build.cjs +880 -281
  5. package/dist/build.d.cts +68 -3
  6. package/dist/build.d.ts +68 -3
  7. package/dist/build.js +88 -30
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +13 -13
  10. package/dist/cdn.full.global.js +12 -12
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-7K7W4AA5.js → chunk-2TTRW2TE.js} +6 -6
  13. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  14. package/dist/chunk-3HZOSMPX.js +90 -0
  15. package/dist/{chunk-GNSRXK34.js → chunk-4FJSAUKB.js} +11 -3
  16. package/dist/{chunk-IV74C2YX.js → chunk-4TQDTWWN.js} +6 -5
  17. package/dist/{chunk-L3QHS4O4.js → chunk-7ILEHTVX.js} +1 -1
  18. package/dist/{chunk-CK5F2IR5.js → chunk-A454RAEK.js} +4 -4
  19. package/dist/{chunk-BRRRVLHQ.js → chunk-AE32RGWX.js} +10 -6
  20. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  21. package/dist/{chunk-F4W7WYXY.js → chunk-BKZ5N6NK.js} +8 -6
  22. package/dist/{chunk-MNEITZ27.js → chunk-BNXRWZZV.js} +49 -8
  23. package/dist/chunk-BOYKGGVD.js +2015 -0
  24. package/dist/{chunk-G6366I2A.js → chunk-CCKUNHVU.js} +2 -2
  25. package/dist/{chunk-346BYZL2.js → chunk-CJ7KS3DF.js} +16 -10
  26. package/dist/chunk-CNPT6NKV.js +1859 -0
  27. package/dist/{chunk-ENFRHCMK.js → chunk-F67F77IA.js} +2 -2
  28. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  29. package/dist/chunk-GVAMIU6K.js +195 -0
  30. package/dist/{chunk-HDKDBC6V.js → chunk-HQIAL7F2.js} +8 -6
  31. package/dist/{chunk-ZE5LF44R.js → chunk-JTFQ7IL4.js} +8 -8
  32. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  33. package/dist/{chunk-HVVRXRGO.js → chunk-MIRKTVH4.js} +7 -14
  34. package/dist/{chunk-YJ57VR6Y.js → chunk-N7A6FKWW.js} +5 -3
  35. package/dist/chunk-NT3ZYB5S.js +167 -0
  36. package/dist/{chunk-Z3EZX6DZ.js → chunk-QS6GMWDD.js} +151 -86
  37. package/dist/chunk-R5U7XKVJ.js +16 -0
  38. package/dist/{chunk-3C56VNCK.js → chunk-RVR5NPFL.js} +3 -3
  39. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  40. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  41. package/dist/{chunk-QV5WEOFD.js → chunk-SVULV7O4.js} +119 -26
  42. package/dist/{chunk-C2FAO6VQ.js → chunk-TLS4NVIB.js} +5 -5
  43. package/dist/{chunk-M25RIH6Y.js → chunk-UUIKJJIK.js} +10 -8
  44. package/dist/chunk-UYTYNCMZ.js +199 -0
  45. package/dist/chunk-VM2U5LEL.js +2596 -0
  46. package/dist/{chunk-24L5RDPS.js → chunk-Y4LUM2XS.js} +8 -6
  47. package/dist/data.cjs +265 -35
  48. package/dist/data.d.cts +31 -1
  49. package/dist/data.d.ts +31 -1
  50. package/dist/data.js +16 -10
  51. package/dist/devtools.cjs +15 -2
  52. package/dist/devtools.js +9 -7
  53. package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
  54. package/dist/ecosystem.cjs +62 -23
  55. package/dist/ecosystem.js +14 -12
  56. package/dist/extras.cjs +3520 -270
  57. package/dist/extras.d.cts +3 -3
  58. package/dist/extras.d.ts +3 -3
  59. package/dist/extras.js +39 -29
  60. package/dist/index.cjs +442 -253
  61. package/dist/index.d.cts +93 -283
  62. package/dist/index.d.ts +93 -283
  63. package/dist/index.js +26 -16
  64. package/dist/motion.cjs +12 -1
  65. package/dist/motion.js +6 -4
  66. package/dist/patterns.cjs +15 -2
  67. package/dist/patterns.js +10 -8
  68. package/dist/performance.cjs +36 -2
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +14 -10
  72. package/dist/plugins.cjs +5225 -1807
  73. package/dist/plugins.d.cts +8 -834
  74. package/dist/plugins.d.ts +8 -834
  75. package/dist/plugins.js +64 -2329
  76. package/dist/prerenderServer-MXJEVWA3.js +350 -0
  77. package/dist/renderRoute-DP20XwMS.d.cts +86 -0
  78. package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
  79. package/dist/routeHead-CE9SV-cH.d.cts +995 -0
  80. package/dist/routeHead-DzVxPviY.d.ts +995 -0
  81. package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
  82. package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
  83. package/dist/ssr-JpGpHnpC.d.cts +841 -0
  84. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  85. package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
  86. package/dist/ssr-dom.cjs +1862 -0
  87. package/dist/ssr-dom.d.cts +341 -0
  88. package/dist/ssr-dom.d.ts +341 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7630 -2064
  91. package/dist/ssr.d.cts +63 -5
  92. package/dist/ssr.d.ts +63 -5
  93. package/dist/ssr.js +136 -14
  94. package/dist/testing.cjs +6543 -87
  95. package/dist/testing.d.cts +80 -1
  96. package/dist/testing.d.ts +80 -1
  97. package/dist/testing.js +117 -4
  98. package/dist/ui.cjs +36 -2
  99. package/dist/ui.js +17 -14
  100. package/dist/widgets.cjs +14 -1
  101. package/dist/widgets.js +11 -9
  102. package/package.json +9 -2
  103. package/dist/chunk-2F4PSJ5Z.js +0 -765
  104. package/dist/chunk-7XHATCIH.js +0 -121
  105. package/dist/chunk-B36ADEIR.js +0 -21
  106. package/dist/ssr-Db0Sgdu3.d.cts +0 -245
  107. package/dist/ssr-Db0Sgdu3.d.ts +0 -245
@@ -1,3 +1,8 @@
1
+ import { w as AdoptMismatch, y as AdoptResult, R as RenderResult } from './ssr-JpGpHnpC.cjs';
2
+ import { R as RenderRouteOptions, a as RouteRenderResult } from './renderRoute-DP20XwMS.cjs';
3
+ import { a as RouteDef } from './routeHead-CE9SV-cH.cjs';
4
+ import './types-CJFViL6Q.cjs';
5
+
1
6
  /**
2
7
  * Listener tracking for accessibility checks.
3
8
  *
@@ -470,6 +475,80 @@ declare function createSnapshotMatcher(store: ReturnType<typeof createSnapshotSt
470
475
  updateAll: () => void;
471
476
  };
472
477
 
478
+ /** Query helpers over rendered HTML, parsed into a detached element. */
479
+ interface HtmlQueries {
480
+ /** The parsed markup (a detached `<div>`). */
481
+ container: HTMLElement;
482
+ /** First element matching `selector`, or `null`. */
483
+ query<E extends Element = HTMLElement>(selector: string): E | null;
484
+ /** Every element matching `selector`. */
485
+ queryAll<E extends Element = HTMLElement>(selector: string): E[];
486
+ /** Trimmed `textContent` of the first match, or `null`. */
487
+ text(selector: string): string | null;
488
+ /** The first element whose own trimmed text is `text`. */
489
+ getByText(text: string): HTMLElement | null;
490
+ /** `content` of `<meta name|property="key">` in the head, or `null`. */
491
+ meta(key: string): string | null;
492
+ /** The head's `<title>` text, or `null`. */
493
+ title(): string | null;
494
+ }
495
+ /** Result of {@link renderOnServer}. */
496
+ type ServerRender = (RenderResult | RouteRenderResult) & HtmlQueries & {
497
+ location: string | null;
498
+ };
499
+ /** Options of {@link renderOnServer}. */
500
+ interface RenderOnServerOptions extends RenderRouteOptions {
501
+ /** The URL to render when the first argument is a route table. Default `"/"`. */
502
+ url?: string;
503
+ }
504
+ /**
505
+ * Render on the server, as a test sees it: a route table through the router
506
+ * (`renderRoute`, with HTTP status and redirects) or a component
507
+ * (`renderToStringAsync`), awaited until settled, plus query helpers over the
508
+ * resulting HTML and head.
509
+ *
510
+ * @param app A route table, or a function building the page.
511
+ * @param options `url` for a route table, plus any `renderRoute` option.
512
+ * @returns `{ html, head, status, state, stateData, location, diagnostics }` and the query helpers.
513
+ */
514
+ declare function renderOnServer(app: RouteDef[] | (() => Node), options?: RenderOnServerOptions): Promise<ServerRender>;
515
+ /** Options of {@link hydrateInTest}. */
516
+ interface HydrateInTestOptions {
517
+ /** `"replace"` (the default `hydrate()`) or `"adopt"`. */
518
+ strategy?: "replace" | "adopt";
519
+ /** The server's transfer script (`result.state`): run first, as the browser would. */
520
+ state?: string;
521
+ /** Mount the container in `document.body` (default `true`; adoption of focus/scroll needs it). */
522
+ attach?: boolean;
523
+ }
524
+ /** Result of {@link hydrateInTest}. */
525
+ interface HydrationReport {
526
+ /** The container holding the hydrated page. */
527
+ container: HTMLElement;
528
+ /** Elements created by the hydration that ended up in the page (0 for a perfect adoption). */
529
+ created: number;
530
+ /** Elements the adoption reused (adopt only; 0 for replace). */
531
+ adopted: number;
532
+ /** Divergences the adoption found (adopt only). */
533
+ mismatches: AdoptMismatch[];
534
+ /** The full adoption result (adopt only). */
535
+ result: AdoptResult | null;
536
+ /** Remove the container. */
537
+ cleanup(): void;
538
+ }
539
+ /**
540
+ * Take over server HTML in a test: put it in a container, restore the
541
+ * transferred state, hydrate, and report what happened — how many elements
542
+ * were created (by spying on `createElement` / `createElementNS` while the
543
+ * hydration runs) and, for adoption, the mismatches.
544
+ *
545
+ * @param html The server markup (`result.html`).
546
+ * @param app The same root the server rendered.
547
+ * @param options Strategy, transfer script, attachment.
548
+ * @returns Counts, mismatches, the container and a cleanup.
549
+ */
550
+ declare function hydrateInTest(html: string, app: () => Node, options?: HydrateInTestOptions): Promise<HydrationReport>;
551
+
473
552
  /**
474
553
  * Visual regression testing utilities for SibuJS.
475
554
  * Provides structural comparison of component output for detecting UI regressions.
@@ -593,4 +672,4 @@ declare function mockStore<T extends object>(initialState: T): {
593
672
  reset: () => void;
594
673
  };
595
674
 
596
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type KeyboardAccessOptions, 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, enableListenerTracking, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryAllByAttribute, queryByAttribute, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };
675
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type HtmlQueries, type HydrateInTestOptions, type HydrationReport, type KeyboardAccessOptions, type MockResponse, type MockRoute, type RenderOnServerOptions, type ServerRender, 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, enableListenerTracking, findByRole, findByTestId, findByText, fireEvent, hydrateInTest, matchSnapshot, mockRouter, mockStore, queryAllByAttribute, queryByAttribute, queryByLabel, queryByRole, queryByTestId, queryByText, render, renderOnServer, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { w as AdoptMismatch, y as AdoptResult, R as RenderResult } from './ssr-JpGpHnpC.js';
2
+ import { R as RenderRouteOptions, a as RouteRenderResult } from './renderRoute-fxgsm2Ir.js';
3
+ import { a as RouteDef } from './routeHead-DzVxPviY.js';
4
+ import './types-CJFViL6Q.js';
5
+
1
6
  /**
2
7
  * Listener tracking for accessibility checks.
3
8
  *
@@ -470,6 +475,80 @@ declare function createSnapshotMatcher(store: ReturnType<typeof createSnapshotSt
470
475
  updateAll: () => void;
471
476
  };
472
477
 
478
+ /** Query helpers over rendered HTML, parsed into a detached element. */
479
+ interface HtmlQueries {
480
+ /** The parsed markup (a detached `<div>`). */
481
+ container: HTMLElement;
482
+ /** First element matching `selector`, or `null`. */
483
+ query<E extends Element = HTMLElement>(selector: string): E | null;
484
+ /** Every element matching `selector`. */
485
+ queryAll<E extends Element = HTMLElement>(selector: string): E[];
486
+ /** Trimmed `textContent` of the first match, or `null`. */
487
+ text(selector: string): string | null;
488
+ /** The first element whose own trimmed text is `text`. */
489
+ getByText(text: string): HTMLElement | null;
490
+ /** `content` of `<meta name|property="key">` in the head, or `null`. */
491
+ meta(key: string): string | null;
492
+ /** The head's `<title>` text, or `null`. */
493
+ title(): string | null;
494
+ }
495
+ /** Result of {@link renderOnServer}. */
496
+ type ServerRender = (RenderResult | RouteRenderResult) & HtmlQueries & {
497
+ location: string | null;
498
+ };
499
+ /** Options of {@link renderOnServer}. */
500
+ interface RenderOnServerOptions extends RenderRouteOptions {
501
+ /** The URL to render when the first argument is a route table. Default `"/"`. */
502
+ url?: string;
503
+ }
504
+ /**
505
+ * Render on the server, as a test sees it: a route table through the router
506
+ * (`renderRoute`, with HTTP status and redirects) or a component
507
+ * (`renderToStringAsync`), awaited until settled, plus query helpers over the
508
+ * resulting HTML and head.
509
+ *
510
+ * @param app A route table, or a function building the page.
511
+ * @param options `url` for a route table, plus any `renderRoute` option.
512
+ * @returns `{ html, head, status, state, stateData, location, diagnostics }` and the query helpers.
513
+ */
514
+ declare function renderOnServer(app: RouteDef[] | (() => Node), options?: RenderOnServerOptions): Promise<ServerRender>;
515
+ /** Options of {@link hydrateInTest}. */
516
+ interface HydrateInTestOptions {
517
+ /** `"replace"` (the default `hydrate()`) or `"adopt"`. */
518
+ strategy?: "replace" | "adopt";
519
+ /** The server's transfer script (`result.state`): run first, as the browser would. */
520
+ state?: string;
521
+ /** Mount the container in `document.body` (default `true`; adoption of focus/scroll needs it). */
522
+ attach?: boolean;
523
+ }
524
+ /** Result of {@link hydrateInTest}. */
525
+ interface HydrationReport {
526
+ /** The container holding the hydrated page. */
527
+ container: HTMLElement;
528
+ /** Elements created by the hydration that ended up in the page (0 for a perfect adoption). */
529
+ created: number;
530
+ /** Elements the adoption reused (adopt only; 0 for replace). */
531
+ adopted: number;
532
+ /** Divergences the adoption found (adopt only). */
533
+ mismatches: AdoptMismatch[];
534
+ /** The full adoption result (adopt only). */
535
+ result: AdoptResult | null;
536
+ /** Remove the container. */
537
+ cleanup(): void;
538
+ }
539
+ /**
540
+ * Take over server HTML in a test: put it in a container, restore the
541
+ * transferred state, hydrate, and report what happened — how many elements
542
+ * were created (by spying on `createElement` / `createElementNS` while the
543
+ * hydration runs) and, for adoption, the mismatches.
544
+ *
545
+ * @param html The server markup (`result.html`).
546
+ * @param app The same root the server rendered.
547
+ * @param options Strategy, transfer script, attachment.
548
+ * @returns Counts, mismatches, the container and a cleanup.
549
+ */
550
+ declare function hydrateInTest(html: string, app: () => Node, options?: HydrateInTestOptions): Promise<HydrationReport>;
551
+
473
552
  /**
474
553
  * Visual regression testing utilities for SibuJS.
475
554
  * Provides structural comparison of component output for detecting UI regressions.
@@ -593,4 +672,4 @@ declare function mockStore<T extends object>(initialState: T): {
593
672
  reset: () => void;
594
673
  };
595
674
 
596
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type KeyboardAccessOptions, 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, enableListenerTracking, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryAllByAttribute, queryByAttribute, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };
675
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type HtmlQueries, type HydrateInTestOptions, type HydrationReport, type KeyboardAccessOptions, type MockResponse, type MockRoute, type RenderOnServerOptions, type ServerRender, 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, enableListenerTracking, findByRole, findByTestId, findByText, fireEvent, hydrateInTest, matchSnapshot, mockRouter, mockStore, queryAllByAttribute, queryByAttribute, queryByLabel, queryByRole, queryByTestId, queryByText, render, renderOnServer, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };
package/dist/testing.js CHANGED
@@ -1,16 +1,38 @@
1
+ import {
2
+ renderRoute
3
+ } from "./chunk-GVAMIU6K.js";
4
+ import "./chunk-VM2U5LEL.js";
5
+ import "./chunk-52XFPGSN.js";
6
+ import {
7
+ hydrate,
8
+ renderToStringAsync
9
+ } from "./chunk-BOYKGGVD.js";
10
+ import "./chunk-CNPT6NKV.js";
11
+ import "./chunk-LJMX4SQ7.js";
12
+ import {
13
+ __resetTransfer
14
+ } from "./chunk-3HZOSMPX.js";
15
+ import "./chunk-62BDYGAG.js";
16
+ import "./chunk-CJ7KS3DF.js";
17
+ import "./chunk-HQIAL7F2.js";
18
+ import "./chunk-RVWJGWCL.js";
1
19
  import {
2
20
  globalSingleton
3
21
  } from "./chunk-7ZHH77QA.js";
4
22
  import {
5
23
  effect
6
- } from "./chunk-BRRRVLHQ.js";
7
- import "./chunk-7XHATCIH.js";
8
- import "./chunk-HVVRXRGO.js";
24
+ } from "./chunk-AE32RGWX.js";
25
+ import "./chunk-B3HOT4TK.js";
26
+ import "./chunk-UYTYNCMZ.js";
27
+ import "./chunk-4FJSAUKB.js";
28
+ import "./chunk-MIRKTVH4.js";
9
29
  import {
10
30
  dispose,
11
31
  replaceChildrenSafely,
12
32
  reportError
13
- } from "./chunk-NYNYSPK7.js";
33
+ } from "./chunk-S37PZD6I.js";
34
+ import "./chunk-NT3ZYB5S.js";
35
+ import "./chunk-R5U7XKVJ.js";
14
36
 
15
37
  // src/testing/queries.ts
16
38
  function queryByText(container, text) {
@@ -1925,6 +1947,95 @@ ${result.diff ?? ""}`);
1925
1947
  };
1926
1948
  }
1927
1949
 
1950
+ // src/testing/ssr.ts
1951
+ function parse(html) {
1952
+ if (typeof document === "undefined") {
1953
+ throw new Error(
1954
+ "[SibuJS testing] parsing server HTML needs a DOM with an HTML parser (run the test under jsdom or happy-dom)"
1955
+ );
1956
+ }
1957
+ const container = document.createElement("div");
1958
+ try {
1959
+ container.innerHTML = html;
1960
+ } catch (err) {
1961
+ throw new Error(
1962
+ "[SibuJS testing] the registered DOM cannot parse HTML (the SibuJS server DOM has no parser); run this test under jsdom or happy-dom",
1963
+ { cause: err }
1964
+ );
1965
+ }
1966
+ return container;
1967
+ }
1968
+ function queriesFor(html, head) {
1969
+ const container = parse(html);
1970
+ const headEl = parse(head);
1971
+ return {
1972
+ container,
1973
+ query: (selector) => container.querySelector(selector),
1974
+ queryAll: (selector) => Array.from(container.querySelectorAll(selector)),
1975
+ text: (selector) => container.querySelector(selector)?.textContent?.trim() ?? null,
1976
+ getByText: (text) => {
1977
+ for (const el of Array.from(container.querySelectorAll("*"))) {
1978
+ let own = "";
1979
+ for (const n of Array.from(el.childNodes)) if (n.nodeType === 3) own += n.textContent;
1980
+ if (own.trim() === text) return el;
1981
+ }
1982
+ return null;
1983
+ },
1984
+ meta: (key) => headEl.querySelector(`meta[name="${key}"], meta[property="${key}"]`)?.getAttribute("content") ?? container.querySelector(`meta[name="${key}"], meta[property="${key}"]`)?.getAttribute("content") ?? null,
1985
+ title: () => headEl.querySelector("title")?.textContent ?? container.querySelector("title")?.textContent ?? null
1986
+ };
1987
+ }
1988
+ async function renderOnServer(app, options = {}) {
1989
+ const { url = "/", ...rest } = options;
1990
+ const result = Array.isArray(app) ? await renderRoute(app, url, rest) : await renderToStringAsync(app, rest);
1991
+ const location = "location" in result ? result.location : null;
1992
+ return { ...result, location, ...queriesFor(result.html, result.head) };
1993
+ }
1994
+ async function hydrateInTest(html, app, options = {}) {
1995
+ const container = document.createElement("div");
1996
+ container.innerHTML = html;
1997
+ if (options.attach !== false) document.body.appendChild(container);
1998
+ if (options.state) {
1999
+ const body = /<script[^>]*>([\s\S]*)<\/script>/.exec(options.state)?.[1];
2000
+ if (body) new Function(body)();
2001
+ __resetTransfer();
2002
+ }
2003
+ const created = [];
2004
+ const doc = document;
2005
+ const createElement = doc.createElement;
2006
+ const createElementNS = doc.createElementNS;
2007
+ doc.createElement = function(...args) {
2008
+ const el = createElement.apply(this, args);
2009
+ created.push(el);
2010
+ return el;
2011
+ };
2012
+ doc.createElementNS = function(...args) {
2013
+ const el = createElementNS.apply(this, args);
2014
+ created.push(el);
2015
+ return el;
2016
+ };
2017
+ let result = null;
2018
+ try {
2019
+ if (options.strategy === "adopt") {
2020
+ result = await hydrate(app, container, { strategy: "adopt" });
2021
+ } else {
2022
+ hydrate(app, container);
2023
+ await new Promise((r) => setTimeout(r, 0));
2024
+ }
2025
+ } finally {
2026
+ doc.createElement = createElement;
2027
+ doc.createElementNS = createElementNS;
2028
+ }
2029
+ return {
2030
+ container,
2031
+ created: created.filter((el) => container.contains(el)).length,
2032
+ adopted: result?.adopted ?? 0,
2033
+ mismatches: result?.mismatches ?? [],
2034
+ result,
2035
+ cleanup: () => container.remove()
2036
+ };
2037
+ }
2038
+
1928
2039
  // src/testing/visualRegression.ts
1929
2040
  function djb2Hash(str) {
1930
2041
  let hash = 5381;
@@ -2314,6 +2425,7 @@ export {
2314
2425
  findByTestId,
2315
2426
  findByText,
2316
2427
  fireEvent,
2428
+ hydrateInTest,
2317
2429
  matchSnapshot,
2318
2430
  mockRouter,
2319
2431
  mockStore,
@@ -2324,6 +2436,7 @@ export {
2324
2436
  queryByTestId,
2325
2437
  queryByText,
2326
2438
  render,
2439
+ renderOnServer,
2327
2440
  snapshotComponent,
2328
2441
  testComponent,
2329
2442
  type,
package/dist/ui.cjs CHANGED
@@ -152,10 +152,21 @@ function defaultReport(error, context) {
152
152
  if (typeof console === "undefined") return;
153
153
  console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
154
154
  }
155
+ function requestErrorSink() {
156
+ const shared = globalThis[/* @__PURE__ */ Symbol.for("sibujs.ssr.v1")];
157
+ if (!shared) return void 0;
158
+ const store = shared.als ? shared.als.getStore() : shared.fallbackDepth > 0 ? shared.fallbackStore : void 0;
159
+ return store?.errors;
160
+ }
155
161
  function reportError(error, context) {
156
162
  const err = normalizeError(error);
157
163
  untrackedRun(() => {
158
164
  if (claimedByBoundary(err, context)) return;
165
+ const sink = requestErrorSink();
166
+ if (sink) {
167
+ sink.push({ error: err, context });
168
+ return;
169
+ }
159
170
  const s = state();
160
171
  const handler = s.handler;
161
172
  if (handler && s.handlerDepth === 0) {
@@ -198,6 +209,13 @@ function devWarn(message) {
198
209
  }
199
210
  }
200
211
 
212
+ // src/core/rendering/hydrationHooks.ts
213
+ var _a;
214
+ var slot = globalThis[_a = /* @__PURE__ */ Symbol.for("sibujs.hydration.v2")] ?? (globalThis[_a] = { a: /* @__PURE__ */ new Set(), c: null });
215
+ function isHydrating() {
216
+ return slot.a.size > 0;
217
+ }
218
+
201
219
  // src/core/rendering/dispose.ts
202
220
  var elementDisposers = /* @__PURE__ */ new WeakMap();
203
221
  var MAX_DRAIN_TEARDOWNS = 1e4;
@@ -335,6 +353,20 @@ function disposeNodeOwn(node) {
335
353
  }
336
354
  }
337
355
  function replaceChildrenSafely(parent, ...next) {
356
+ if (isHydrating() && next.length > 0 && next[0] === parent.firstChild) {
357
+ let ok = true;
358
+ for (let i = 1; i < next.length && ok; i++) ok = next[i - 1].nextSibling === next[i];
359
+ if (ok) {
360
+ let rest = next[next.length - 1].nextSibling;
361
+ while (rest) {
362
+ const following = rest.nextSibling;
363
+ dispose(rest);
364
+ parent.removeChild(rest);
365
+ rest = following;
366
+ }
367
+ return;
368
+ }
369
+ }
338
370
  for (let i = 0; i < next.length; i++) {
339
371
  const node = next[i];
340
372
  node.parentNode?.removeChild(node);
@@ -914,7 +946,7 @@ function forEachSubscriber(signal2, visit) {
914
946
  }
915
947
 
916
948
  // src/reactivity/track.ts
917
- var _runtimeVersion = true ? "4.13.0" : "dev";
949
+ var _runtimeVersion = true ? "4.14.0" : "dev";
918
950
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
919
951
  function resolveReactiveApi() {
920
952
  const g = globalThis;
@@ -1232,6 +1264,8 @@ function detectSSRShared() {
1232
1264
  }
1233
1265
  if (mod) detected = new mod.AsyncLocalStorage();
1234
1266
  }
1267
+ const globalAls = globalThis.AsyncLocalStorage;
1268
+ if (!detected && typeof globalAls === "function") detected = new globalAls();
1235
1269
  } catch {
1236
1270
  detected = null;
1237
1271
  }
@@ -3820,7 +3854,7 @@ function disposeEffect(ctx) {
3820
3854
  }
3821
3855
  function effect(effectFn, options) {
3822
3856
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
3823
- if (isSSR()) return () => {
3857
+ if (isSSR() && !options?.server) return () => {
3824
3858
  };
3825
3859
  const ctx = {
3826
3860
  fn: effectFn,
package/dist/ui.js CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  toast,
39
39
  withScopedStyle,
40
40
  zipMask
41
- } from "./chunk-ZE5LF44R.js";
41
+ } from "./chunk-JTFQ7IL4.js";
42
42
  import {
43
43
  RenderProp,
44
44
  assertType,
@@ -54,7 +54,7 @@ import {
54
54
  withDefaults,
55
55
  withProps,
56
56
  withWrapper
57
- } from "./chunk-TUCPL2HB.js";
57
+ } from "./chunk-GIJ33UJZ.js";
58
58
  import "./chunk-N5TBYXHW.js";
59
59
  import {
60
60
  DEFAULT_EMBED_REFERRER_POLICY,
@@ -66,36 +66,39 @@ import {
66
66
  recordEmbedFrame,
67
67
  resolveEmbedSrc,
68
68
  setEmbedPolicy
69
- } from "./chunk-AM6DZLBM.js";
69
+ } from "./chunk-LJMX4SQ7.js";
70
70
  import {
71
71
  createId
72
- } from "./chunk-G6366I2A.js";
72
+ } from "./chunk-CCKUNHVU.js";
73
73
  import {
74
74
  noDocument
75
- } from "./chunk-HDKDBC6V.js";
76
- import "./chunk-F2UWOZ6J.js";
77
- import "./chunk-YJ57VR6Y.js";
75
+ } from "./chunk-HQIAL7F2.js";
76
+ import "./chunk-RVWJGWCL.js";
77
+ import "./chunk-N7A6FKWW.js";
78
78
  import {
79
79
  globalSingleton
80
80
  } from "./chunk-7ZHH77QA.js";
81
81
  import {
82
82
  effect
83
- } from "./chunk-BRRRVLHQ.js";
84
- import "./chunk-EXXSN3BW.js";
85
- import "./chunk-7XHATCIH.js";
83
+ } from "./chunk-AE32RGWX.js";
84
+ import "./chunk-B3HOT4TK.js";
85
+ import "./chunk-UYTYNCMZ.js";
86
86
  import "./chunk-5HZXGZ6T.js";
87
87
  import {
88
88
  batch,
89
89
  signal
90
- } from "./chunk-GNSRXK34.js";
90
+ } from "./chunk-4FJSAUKB.js";
91
91
  import {
92
92
  reactiveBinding
93
- } from "./chunk-HVVRXRGO.js";
93
+ } from "./chunk-MIRKTVH4.js";
94
94
  import {
95
- devWarnLazy,
96
95
  registerDisposer,
97
96
  unregisterDisposer
98
- } from "./chunk-NYNYSPK7.js";
97
+ } from "./chunk-S37PZD6I.js";
98
+ import {
99
+ devWarnLazy
100
+ } from "./chunk-NT3ZYB5S.js";
101
+ import "./chunk-R5U7XKVJ.js";
99
102
 
100
103
  // src/ui/formAction.ts
101
104
  function formAction(fn) {
package/dist/widgets.cjs CHANGED
@@ -66,6 +66,8 @@ function detectSSRShared() {
66
66
  }
67
67
  if (mod) detected = new mod.AsyncLocalStorage();
68
68
  }
69
+ const globalAls = globalThis.AsyncLocalStorage;
70
+ if (!detected && typeof globalAls === "function") detected = new globalAls();
69
71
  } catch {
70
72
  detected = null;
71
73
  }
@@ -178,10 +180,21 @@ function defaultReport(error, context) {
178
180
  if (typeof console === "undefined") return;
179
181
  console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
180
182
  }
183
+ function requestErrorSink() {
184
+ const shared = globalThis[/* @__PURE__ */ Symbol.for("sibujs.ssr.v1")];
185
+ if (!shared) return void 0;
186
+ const store = shared.als ? shared.als.getStore() : shared.fallbackDepth > 0 ? shared.fallbackStore : void 0;
187
+ return store?.errors;
188
+ }
181
189
  function reportError(error, context) {
182
190
  const err = normalizeError(error);
183
191
  untrackedRun(() => {
184
192
  if (claimedByBoundary(err, context)) return;
193
+ const sink = requestErrorSink();
194
+ if (sink) {
195
+ sink.push({ error: err, context });
196
+ return;
197
+ }
185
198
  const s = state();
186
199
  const handler = s.handler;
187
200
  if (handler && s.handlerDepth === 0) {
@@ -789,7 +802,7 @@ function forEachSubscriber(signal2, visit) {
789
802
  }
790
803
 
791
804
  // src/reactivity/track.ts
792
- var _runtimeVersion = true ? "4.13.0" : "dev";
805
+ var _runtimeVersion = true ? "4.14.0" : "dev";
793
806
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
794
807
  function resolveReactiveApi() {
795
808
  const g = globalThis;
package/dist/widgets.js CHANGED
@@ -9,28 +9,30 @@ import {
9
9
  snapshotAttributes,
10
10
  tabs,
11
11
  tooltip
12
- } from "./chunk-C2FAO6VQ.js";
12
+ } from "./chunk-TLS4NVIB.js";
13
13
  import {
14
14
  claimWidgetKey,
15
15
  isWidgetKeyEvent,
16
16
  widgetKeyOwner
17
17
  } from "./chunk-N5TBYXHW.js";
18
- import "./chunk-3C56VNCK.js";
18
+ import "./chunk-RVR5NPFL.js";
19
19
  import {
20
20
  createId,
21
21
  domBinding
22
- } from "./chunk-G6366I2A.js";
23
- import "./chunk-YJ57VR6Y.js";
24
- import "./chunk-EXXSN3BW.js";
25
- import "./chunk-7XHATCIH.js";
22
+ } from "./chunk-CCKUNHVU.js";
23
+ import "./chunk-N7A6FKWW.js";
24
+ import "./chunk-B3HOT4TK.js";
25
+ import "./chunk-UYTYNCMZ.js";
26
26
  import {
27
27
  batch,
28
28
  signal
29
- } from "./chunk-GNSRXK34.js";
29
+ } from "./chunk-4FJSAUKB.js";
30
30
  import {
31
31
  untracked
32
- } from "./chunk-HVVRXRGO.js";
33
- import "./chunk-NYNYSPK7.js";
32
+ } from "./chunk-MIRKTVH4.js";
33
+ import "./chunk-S37PZD6I.js";
34
+ import "./chunk-NT3ZYB5S.js";
35
+ import "./chunk-R5U7XKVJ.js";
34
36
 
35
37
  // src/widgets/selectMenu.ts
36
38
  var boundMenus = /* @__PURE__ */ new WeakMap();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sibujs",
3
- "version": "4.13.0",
3
+ "version": "4.14.0",
4
4
  "description": "A function-based frontend framework with fine-grained reactivity and no virtual DOM. Tag factories return live DOM nodes you can hold and mutate, signals update only what changed, and nothing needs a compiler — it runs from a script tag or a bundler.",
5
5
  "keywords": [
6
6
  "frontend",
@@ -41,12 +41,13 @@
41
41
  "typecheck:tests": "tsc -p tsconfig.test.json",
42
42
  "hooks:install": "node scripts/hooks/install.mjs",
43
43
  "certify:rc": "node scripts/certify/run.mjs",
44
- "build": "tsup index.ts data.ts browser.ts patterns.ts motion.ts ui.ts widgets.ts ssr.ts devtools.ts performance.ts ecosystem.ts plugins.ts build.ts testing.ts extras.ts --dts --format esm,cjs --out-dir dist --clean && tsup --config tsup.cdn.config.ts",
44
+ "build": "tsup index.ts data.ts browser.ts patterns.ts motion.ts ui.ts widgets.ts ssr.ts ssr-dom.ts devtools.ts performance.ts ecosystem.ts plugins.ts build.ts testing.ts extras.ts --dts --format esm,cjs --out-dir dist --clean && tsup --config tsup.cdn.config.ts",
45
45
  "example:chess:build": "node scripts/build-example-chess.mjs",
46
46
  "example:serve": "node tests-browser/server.mjs",
47
47
  "bench": "node bench.mjs",
48
48
  "bench:islands-dx": "node bench/islands-dx.mjs",
49
49
  "bench:islands-size": "node bench/islands-size.mjs",
50
+ "bench:ssr": "node --expose-gc bench/ssr-dom.mjs 1000 && node bench/ssr-takeover.mjs 200",
50
51
  "bench:save": "node bench.mjs --save",
51
52
  "bench:check": "node bench.mjs --compare",
52
53
  "publish:npm": "node publish.mjs",
@@ -95,6 +96,11 @@
95
96
  "import": "./dist/ssr.js",
96
97
  "require": "./dist/ssr.cjs"
97
98
  },
99
+ "./ssr/dom": {
100
+ "types": "./dist/ssr-dom.d.ts",
101
+ "import": "./dist/ssr-dom.js",
102
+ "require": "./dist/ssr-dom.cjs"
103
+ },
98
104
  "./devtools": {
99
105
  "types": "./dist/devtools.d.ts",
100
106
  "import": "./dist/devtools.js",
@@ -172,6 +178,7 @@
172
178
  "chess.js": "^1.4.0",
173
179
  "esbuild": "^0.27.7",
174
180
  "fake-indexeddb": "^6.2.5",
181
+ "happy-dom": "^20.14.6",
175
182
  "jsdom": "^26.1.0",
176
183
  "tsup": "^8.5.1",
177
184
  "typescript": "^5.8.3",