elements-kit 0.27.2 → 0.27.3

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.
@@ -1,8 +1,8 @@
1
1
  import { t as createElement } from "../element-DgQvp-49.mjs";
2
2
  import { render } from "../render.mjs";
3
3
  import { t as hydrate } from "../hydrate-ICXw1FIY.mjs";
4
- import { withSlotProps } from "./astro-slots.mjs";
5
4
  import { HMR_SLOT } from "./hmr-slot.mjs";
5
+ import { withSlotProps } from "./astro-slots.mjs";
6
6
  //#region src/integrations/astro-client.ts
7
7
  /**
8
8
  * Astro client entrypoint: hydrate an elements-kit island.
@@ -1,7 +1,7 @@
1
1
  import { y as setInertEffects } from "../lib-DYypKhxk.mjs";
2
2
  import { i as setRenderer, t as createElement } from "../element-DgQvp-49.mjs";
3
+ import { i as serverJsx, n as renderToString, r as SNode } from "../server-CoEm-C0I.mjs";
3
4
  import { withSlotProps } from "./astro-slots.mjs";
4
- import { i as serverJsx, n as renderToString, r as SNode } from "../server-Qm9JTZ-J.mjs";
5
5
  //#region src/integrations/astro-server.ts
6
6
  /**
7
7
  * Decide whether `Component` is an elements-kit component. Invoked by Astro
@@ -1,7 +1,7 @@
1
1
  import { t as createElement } from "../element-DgQvp-49.mjs";
2
2
  import { render } from "../render.mjs";
3
3
  import { HMR_SLOT } from "./hmr-slot.mjs";
4
- import { n as updateCells } from "../hot-Zl0s17uq.mjs";
4
+ import { n as updateCells } from "../hot-eP6QROpU.mjs";
5
5
  //#region src/integrations/hmr-runtime.ts
6
6
  const slot = globalThis;
7
7
  const records = /* @__PURE__ */ new Set();
@@ -8,8 +8,17 @@ import { t as Computed } from "../index-B1FHAd5T.mjs";
8
8
  * `Computed<T>`. Using `() => T` instead of `Computed<T>` prevents TypeScript from
9
9
  * picking the write overload of `Signal<T>` during type inference.
10
10
  *
11
+ * **Server rendering:** pass `getServerSnapshot` for any component React renders
12
+ * on the server — React requires it there and throws without one. It runs on
13
+ * the server *and* again for the hydration render on the client, so it must
14
+ * return the same value both times: whatever the server put in the HTML. A
15
+ * signal reading browser state (`matchMedia`, `localStorage`, `location`) does
16
+ * not qualify, since it answers differently in the browser.
17
+ *
11
18
  * @template T - The type of the signal value.
12
19
  * @param value - A writable `Signal<T>` or a derived `Computed<T>`.
20
+ * @param getServerSnapshot - Returns the value the server rendered. Required
21
+ * for server-rendered components; omit only for client-only ones.
13
22
  * @returns The current value, updated on every signal change.
14
23
  *
15
24
  * @example
@@ -23,8 +32,15 @@ import { t as Computed } from "../index-B1FHAd5T.mjs";
23
32
  * return <div>{countValue} × 2 = {doubleValue}</div>;
24
33
  * }
25
34
  * ```
35
+ *
36
+ * @example
37
+ * Server-rendered component reading a browser-only signal:
38
+ * ```tsx
39
+ * // `theme` resolves to "light" on the server — say so, or hydration mismatches.
40
+ * const mode = useSignal(theme, () => "light");
41
+ * ```
26
42
  */
27
- declare function useSignal<T>(value: () => T): T;
43
+ declare function useSignal<T>(value: () => T, getServerSnapshot?: () => T): T;
28
44
  /**
29
45
  * Create a signal effect scope tied to a React component's lifetime.
30
46
  *
@@ -10,8 +10,17 @@ import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
10
10
  * `Computed<T>`. Using `() => T` instead of `Computed<T>` prevents TypeScript from
11
11
  * picking the write overload of `Signal<T>` during type inference.
12
12
  *
13
+ * **Server rendering:** pass `getServerSnapshot` for any component React renders
14
+ * on the server — React requires it there and throws without one. It runs on
15
+ * the server *and* again for the hydration render on the client, so it must
16
+ * return the same value both times: whatever the server put in the HTML. A
17
+ * signal reading browser state (`matchMedia`, `localStorage`, `location`) does
18
+ * not qualify, since it answers differently in the browser.
19
+ *
13
20
  * @template T - The type of the signal value.
14
21
  * @param value - A writable `Signal<T>` or a derived `Computed<T>`.
22
+ * @param getServerSnapshot - Returns the value the server rendered. Required
23
+ * for server-rendered components; omit only for client-only ones.
15
24
  * @returns The current value, updated on every signal change.
16
25
  *
17
26
  * @example
@@ -25,12 +34,19 @@ import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
25
34
  * return <div>{countValue} × 2 = {doubleValue}</div>;
26
35
  * }
27
36
  * ```
37
+ *
38
+ * @example
39
+ * Server-rendered component reading a browser-only signal:
40
+ * ```tsx
41
+ * // `theme` resolves to "light" on the server — say so, or hydration mismatches.
42
+ * const mode = useSignal(theme, () => "light");
43
+ * ```
28
44
  */
29
- function useSignal(value) {
45
+ function useSignal(value, getServerSnapshot) {
30
46
  return useSyncExternalStore((callback) => effect(() => {
31
47
  value();
32
48
  callback();
33
- }), () => value(), () => value());
49
+ }), value, getServerSnapshot);
34
50
  }
35
51
  /**
36
52
  * Create a signal effect scope tied to a React component's lifetime.
@@ -1,4 +1,4 @@
1
1
  import { t as Fragment } from "../fragment-BJfcQdsL.mjs";
2
- import { t as createHotElement } from "../hot-Zl0s17uq.mjs";
3
2
  import "./index.mjs";
3
+ import { t as createHotElement } from "../hot-eP6QROpU.mjs";
4
4
  export { Fragment, createHotElement as h, createHotElement as jsx, createHotElement as jsxDEV, createHotElement as jsxs };
@@ -1,2 +1,2 @@
1
- import { n as renderToString, t as renderToStream } from "../server-Qm9JTZ-J.mjs";
1
+ import { n as renderToString, t as renderToStream } from "../server-CoEm-C0I.mjs";
2
2
  export { renderToStream, renderToString };
@@ -522,7 +522,6 @@
522
522
  flex-flow: wrap;
523
523
  justify-content: center;
524
524
  align-items: center;
525
- gap: 1rem 1.5rem;
526
525
  width: auto;
527
526
  }
528
527
  }
@@ -1,3 +1,5 @@
1
+ import { t as HTMLElementBase } from "../environment-c2ftMYT0.mjs";
2
+
1
3
  //#region src/utilities/dom-lifecycle.d.ts
2
4
  type LifecycleCallback = (self: DomLifecycleElement) => void;
3
5
  type AdoptedCallback = (oldDocument: Document, newDocument: Document) => void;
@@ -59,7 +61,7 @@ type AdoptedCallback = (oldDocument: Document, newDocument: Document) => void;
59
61
  * </div>
60
62
  * ```
61
63
  */
62
- declare class DomLifecycleElement extends HTMLElement {
64
+ declare class DomLifecycleElement extends HTMLElementBase {
63
65
  #private;
64
66
  set onConnect(fn: LifecycleCallback | null);
65
67
  get onConnect(): LifecycleCallback | null;
@@ -1,6 +1,6 @@
1
1
  import { d as effectScope } from "../lib-DYypKhxk.mjs";
2
2
  import "../signals/index.mjs";
3
- import { isBrowser } from "./environment.mjs";
3
+ import { HTMLElementBase, isBrowser } from "./environment.mjs";
4
4
  //#region src/utilities/dom-lifecycle.ts
5
5
  /**
6
6
  * Custom element backing the `<dom-lifecycle>` JSX tag. Place inside any
@@ -60,7 +60,7 @@ import { isBrowser } from "./environment.mjs";
60
60
  * </div>
61
61
  * ```
62
62
  */
63
- var DomLifecycleElement = class extends HTMLElement {
63
+ var DomLifecycleElement = class extends HTMLElementBase {
64
64
  #onConnect = null;
65
65
  #onDisconnect = null;
66
66
  #onMove = null;
@@ -11,6 +11,9 @@ type StorageOptions<T> = {
11
11
  * Changes made in other tabs/windows are synchronised automatically via
12
12
  * the `StorageEvent`.
13
13
  *
14
+ * Outside a browser — or when `localStorage` is unavailable — the signal is
15
+ * plain in-memory state seeded with `initialValue`; nothing is persisted.
16
+ *
14
17
  * @example
15
18
  * ```ts
16
19
  * import { createLocalStorage } from "elements-kit/utilities/storage";
@@ -26,6 +29,9 @@ declare function createLocalStorage<T>(key: string, initialValue: T, options?: S
26
29
  *
27
30
  * Session storage is scoped to the current tab — no cross-tab sync.
28
31
  *
32
+ * Outside a browser — or when `sessionStorage` is unavailable — the signal is
33
+ * plain in-memory state seeded with `initialValue`; nothing is persisted.
34
+ *
29
35
  * @example
30
36
  * ```ts
31
37
  * import { createSessionStorage } from "elements-kit/utilities/storage";
@@ -1,5 +1,20 @@
1
+ import { x as signal } from "../lib-DYypKhxk.mjs";
2
+ import "../signals/index.mjs";
1
3
  import { sync } from "./event-driven.mjs";
4
+ import { isBrowser } from "./environment.mjs";
2
5
  //#region src/utilities/storage.ts
6
+ /**
7
+ * The requested `Storage`, or `null` outside a browser and when access throws
8
+ * (sandboxed iframes, blocked site data).
9
+ */
10
+ function getStorage(area) {
11
+ if (!isBrowser) return null;
12
+ try {
13
+ return area === "local" ? localStorage : sessionStorage;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
3
18
  function readOrDefault(storage, key, initialValue, deserialise) {
4
19
  try {
5
20
  const item = storage.getItem(key);
@@ -7,28 +22,17 @@ function readOrDefault(storage, key, initialValue, deserialise) {
7
22
  } catch {}
8
23
  return initialValue;
9
24
  }
10
- /**
11
- * Returns a `Signal` persisted to `localStorage`.
12
- *
13
- * Changes made in other tabs/windows are synchronised automatically via
14
- * the `StorageEvent`.
15
- *
16
- * @example
17
- * ```ts
18
- * import { createLocalStorage } from "elements-kit/utilities/storage";
19
- *
20
- * const theme = createLocalStorage<"light" | "dark">("theme", "light");
21
- * theme(); // read current
22
- * theme("dark"); // write — persists and notifies
23
- * ```
24
- */
25
- function createLocalStorage(key, initialValue, options) {
25
+ function createStorageSignal(area, key, initialValue, options, crossTab) {
26
+ const storage = getStorage(area);
27
+ if (!storage) return signal(initialValue);
26
28
  const serialise = options?.serialise ?? ((v) => JSON.stringify(v));
27
29
  const deserialise = options?.deserialise ?? ((raw) => JSON.parse(raw));
28
- const storage = localStorage;
29
30
  let notify;
30
31
  const subscribe = (cb) => {
31
32
  notify = cb;
33
+ if (!crossTab) return () => {
34
+ notify = void 0;
35
+ };
32
36
  const handler = (e) => {
33
37
  if (e.key === key && e.storageArea === storage) cb();
34
38
  };
@@ -47,10 +51,34 @@ function createLocalStorage(key, initialValue, options) {
47
51
  return s;
48
52
  }
49
53
  /**
54
+ * Returns a `Signal` persisted to `localStorage`.
55
+ *
56
+ * Changes made in other tabs/windows are synchronised automatically via
57
+ * the `StorageEvent`.
58
+ *
59
+ * Outside a browser — or when `localStorage` is unavailable — the signal is
60
+ * plain in-memory state seeded with `initialValue`; nothing is persisted.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { createLocalStorage } from "elements-kit/utilities/storage";
65
+ *
66
+ * const theme = createLocalStorage<"light" | "dark">("theme", "light");
67
+ * theme(); // read current
68
+ * theme("dark"); // write — persists and notifies
69
+ * ```
70
+ */
71
+ function createLocalStorage(key, initialValue, options) {
72
+ return createStorageSignal("local", key, initialValue, options, true);
73
+ }
74
+ /**
50
75
  * Returns a `Signal` persisted to `sessionStorage`.
51
76
  *
52
77
  * Session storage is scoped to the current tab — no cross-tab sync.
53
78
  *
79
+ * Outside a browser — or when `sessionStorage` is unavailable — the signal is
80
+ * plain in-memory state seeded with `initialValue`; nothing is persisted.
81
+ *
54
82
  * @example
55
83
  * ```ts
56
84
  * import { createSessionStorage } from "elements-kit/utilities/storage";
@@ -60,23 +88,7 @@ function createLocalStorage(key, initialValue, options) {
60
88
  * ```
61
89
  */
62
90
  function createSessionStorage(key, initialValue, options) {
63
- const serialise = options?.serialise ?? ((v) => JSON.stringify(v));
64
- const deserialise = options?.deserialise ?? ((raw) => JSON.parse(raw));
65
- const storage = sessionStorage;
66
- let notify;
67
- const subscribe = (cb) => {
68
- notify = cb;
69
- return () => {
70
- notify = void 0;
71
- };
72
- };
73
- const [s] = sync(subscribe, () => readOrDefault(storage, key, initialValue, deserialise), (v) => {
74
- try {
75
- storage.setItem(key, serialise(v));
76
- } catch {}
77
- notify?.();
78
- });
79
- return s;
91
+ return createStorageSignal("session", key, initialValue, options, false);
80
92
  }
81
93
  //#endregion
82
94
  export { createLocalStorage, createSessionStorage };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elements-kit",
3
3
  "type": "module",
4
- "version": "0.27.2",
4
+ "version": "0.27.3",
5
5
  "description": "A lightweight reactive UI library that transforms native HTMLElements into reactive components with signals. Ideal for framework-agnostic applications and web components.",
6
6
  "keywords": [
7
7
  "webcomponents",
@@ -124,7 +124,8 @@
124
124
  "storybook": "pnpm --filter elements-kit-storybook dev",
125
125
  "watch": "tsdown -w",
126
126
  "test": "vitest run",
127
- "test:watch": "vitest"
127
+ "test:watch": "vitest",
128
+ "test:engines": "node scripts/engine-matrix.mjs"
128
129
  },
129
130
  "packageManager": "pnpm@10.33.0",
130
131
  "devDependencies": {
@@ -138,7 +139,8 @@
138
139
  "react-dom": "^19.2.5",
139
140
  "tsdown": "0.22.0",
140
141
  "typescript": "^6.0.3",
141
- "vitest": "^4.1.2"
142
+ "vitest": "^4.1.2",
143
+ "puppeteer-core": "^24.0.0"
142
144
  },
143
145
  "dependencies": {
144
146
  "@floating-ui/dom": "^1.7.6",
File without changes