elements-kit 0.1.0 → 0.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.
- package/dist/attributes.d.mts +1 -1
- package/dist/benchmark.CX_oY03V-COK1VBpH.mjs +5176 -0
- package/dist/custom-elements.d.mts +1 -1
- package/dist/{element-w1GCIMVp.mjs → element-BjpyY6qv.mjs} +17 -35
- package/dist/for.d.mts +1 -1
- package/dist/for.mjs +2 -2
- package/dist/{infer-DuFY-y2b.d.mts → infer-BUUWMoG9.d.mts} +3 -3
- package/dist/integrations/react.d.mts +1 -1
- package/dist/integrations/react.mjs +2 -2
- package/dist/jsx-runtime/index.d.mts +1 -1
- package/dist/jsx-runtime/index.mjs +1 -1
- package/dist/render.mjs +1 -1
- package/dist/{scope-DM2gzOkb.mjs → scope-CwVlgN8b.mjs} +1 -1
- package/dist/signals/index.d.mts +1 -1
- package/dist/signals/index.mjs +2 -2
- package/dist/{slot-CKtUoy2X.mjs → slot-BnzxFBfO.mjs} +1 -1
- package/dist/slot.d.mts +1 -1
- package/dist/slot.mjs +1 -1
- package/dist/{test.BmQO5GaM-BeO5pvCo.mjs → test.BmQO5GaM-C8YqghE0.mjs} +9 -5156
- package/dist/utilities/_observe.mjs +1 -1
- package/dist/utilities/active-element.d.mts +1 -1
- package/dist/utilities/active-element.mjs +1 -1
- package/dist/utilities/active-element.test.mjs +2 -1
- package/dist/utilities/async.d.mts +1 -1
- package/dist/utilities/async.mjs +1 -1
- package/dist/utilities/async.test.mjs +3 -2
- package/dist/utilities/context.d.mts +78 -0
- package/dist/utilities/context.mjs +115 -0
- package/dist/utilities/context.test.d.mts +1 -0
- package/dist/utilities/context.test.mjs +171 -0
- package/dist/utilities/debounced.d.mts +1 -1
- package/dist/utilities/debounced.mjs +1 -1
- package/dist/utilities/debounced.test.mjs +3 -2
- package/dist/utilities/dom-lifecycle.bench.d.mts +1 -0
- package/dist/utilities/dom-lifecycle.bench.mjs +90 -0
- package/dist/utilities/dom-lifecycle.d.mts +88 -0
- package/dist/utilities/dom-lifecycle.mjs +113 -0
- package/dist/utilities/dom-lifecycle.test.d.mts +1 -0
- package/dist/utilities/dom-lifecycle.test.mjs +227 -0
- package/dist/utilities/element-rect.d.mts +1 -1
- package/dist/utilities/element-rect.mjs +1 -1
- package/dist/utilities/element-rect.test.mjs +3 -2
- package/dist/utilities/element-scroll.d.mts +1 -1
- package/dist/utilities/element-scroll.test.mjs +3 -2
- package/dist/utilities/event-driven.d.mts +1 -1
- package/dist/utilities/event-driven.mjs +1 -1
- package/dist/utilities/event-listener.d.mts +1 -1
- package/dist/utilities/event-listener.mjs +1 -1
- package/dist/utilities/event-listener.test.mjs +3 -2
- package/dist/utilities/focus-within.d.mts +1 -1
- package/dist/utilities/focus-within.mjs +1 -1
- package/dist/utilities/focus-within.test.mjs +3 -2
- package/dist/utilities/hover.d.mts +1 -1
- package/dist/utilities/hover.mjs +1 -1
- package/dist/utilities/hover.test.mjs +3 -2
- package/dist/utilities/intersection-observer.test.mjs +3 -2
- package/dist/utilities/interval.d.mts +1 -1
- package/dist/utilities/interval.mjs +1 -1
- package/dist/utilities/interval.test.mjs +3 -2
- package/dist/utilities/location.d.mts +1 -1
- package/dist/utilities/location.mjs +1 -1
- package/dist/utilities/location.test.mjs +2 -1
- package/dist/utilities/long-press.test.mjs +3 -2
- package/dist/utilities/media-devices.d.mts +1 -1
- package/dist/utilities/media-devices.mjs +1 -1
- package/dist/utilities/media-devices.test.mjs +3 -2
- package/dist/utilities/media-player.d.mts +1 -1
- package/dist/utilities/media-player.test.mjs +3 -2
- package/dist/utilities/media-query.d.mts +1 -1
- package/dist/utilities/media-query.mjs +1 -1
- package/dist/utilities/mutation-observer.test.mjs +3 -2
- package/dist/utilities/network.d.mts +1 -1
- package/dist/utilities/network.mjs +1 -1
- package/dist/utilities/network.test.mjs +2 -1
- package/dist/utilities/on-click-outside.test.mjs +3 -2
- package/dist/utilities/orientation.d.mts +1 -1
- package/dist/utilities/orientation.mjs +1 -1
- package/dist/utilities/previous.d.mts +1 -1
- package/dist/utilities/previous.mjs +1 -1
- package/dist/utilities/previous.test.mjs +3 -2
- package/dist/utilities/promise.d.mts +1 -1
- package/dist/utilities/promise.mjs +1 -1
- package/dist/utilities/promise.test.mjs +3 -2
- package/dist/utilities/retry.mjs +1 -1
- package/dist/utilities/retry.test.mjs +3 -2
- package/dist/utilities/routing.d.mts +1 -1
- package/dist/utilities/routing.mjs +1 -1
- package/dist/utilities/routing.test.mjs +2 -1
- package/dist/utilities/search-params.d.mts +1 -1
- package/dist/utilities/search-params.test.mjs +3 -2
- package/dist/utilities/ssr.test.mjs +2 -1
- package/dist/utilities/storage.d.mts +1 -1
- package/dist/utilities/storage.test.mjs +3 -2
- package/dist/utilities/throttled.d.mts +1 -1
- package/dist/utilities/throttled.mjs +1 -1
- package/dist/utilities/throttled.test.mjs +3 -2
- package/dist/utilities/timeout.d.mts +1 -1
- package/dist/utilities/timeout.mjs +1 -1
- package/dist/utilities/timeout.test.mjs +3 -2
- package/dist/utilities/window-focus.d.mts +1 -1
- package/dist/utilities/window-focus.mjs +1 -1
- package/dist/utilities/window-size.d.mts +1 -1
- package/dist/utilities/window-size.mjs +1 -1
- package/dist/utilities/window-size.test.mjs +2 -1
- package/package.json +13 -2
- /package/dist/{attributes-DILeh3-s.d.mts → attributes-3r7Diua4.d.mts} +0 -0
- /package/dist/{custom-elements-D5_NMNyD.d.mts → custom-elements-CBuenqVD.d.mts} +0 -0
- /package/dist/{lib-D6duEs38.mjs → lib-CVfOddra.mjs} +0 -0
- /package/dist/{magic-string.es-cTgJnTCj.mjs → magic-string.es-CXefOmkB.mjs} +0 -0
- /package/dist/{polyfill-B1lNNcum.mjs → polyfill-CdZVCxdo.mjs} +0 -0
- /package/dist/{slot-D5iBUSAm.d.mts → slot-CfafCBOW.d.mts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as it, l as describe } from "../benchmark.CX_oY03V-COK1VBpH.mjs";
|
|
2
|
+
import { t as globalExpect } from "../test.BmQO5GaM-C8YqghE0.mjs";
|
|
2
3
|
import { activeElement } from "./active-element.mjs";
|
|
3
4
|
//#region src/utilities/active-element.test.ts
|
|
4
5
|
describe("activeElement (singleton)", () => {
|
package/dist/utilities/async.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as untracked, m as signal, s as effect } from "../lib-
|
|
1
|
+
import { g as untracked, m as signal, s as effect } from "../lib-CVfOddra.mjs";
|
|
2
2
|
import { resolve } from "../signals/index.mjs";
|
|
3
3
|
import { promise } from "./promise.mjs";
|
|
4
4
|
//#region src/utilities/async.ts
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { m as signal, p as onCleanup, s as effect } from "../lib-
|
|
1
|
+
import { m as signal, p as onCleanup, s as effect } from "../lib-CVfOddra.mjs";
|
|
2
2
|
import "../signals/index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { f as it, l as describe } from "../benchmark.CX_oY03V-COK1VBpH.mjs";
|
|
4
|
+
import { n as vi, t as globalExpect } from "../test.BmQO5GaM-C8YqghE0.mjs";
|
|
4
5
|
import { Async, async } from "./async.mjs";
|
|
5
6
|
import { createInterval } from "./interval.mjs";
|
|
6
7
|
//#region src/utilities/async.test.ts
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/utilities/context.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Register `value` under `key` on `host`. Calls to {@link getContext} from
|
|
4
|
+
* any descendant of `host` (in the DOM tree, including across open shadow
|
|
5
|
+
* roots) resolve to this value, unless an inner provider with the same key
|
|
6
|
+
* shadows it. Returns `host` so the call can be used inline.
|
|
7
|
+
*
|
|
8
|
+
* Must be called inside an `effect` / `effectScope` (or a wrapped
|
|
9
|
+
* `connectedCallback`). The entry is auto-removed via `onCleanup` when the
|
|
10
|
+
* surrounding scope disposes. Passes `null` through unchanged.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* Inline form — `setContext` returns the host so it composes directly:
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { signal } from "elements-kit/signals";
|
|
16
|
+
* import { setContext } from "elements-kit/utilities/context";
|
|
17
|
+
*
|
|
18
|
+
* const THEME = Symbol("theme");
|
|
19
|
+
*
|
|
20
|
+
* function ThemeProvider({ children }: { children: JSX.Element }) {
|
|
21
|
+
* const theme = signal<"light" | "dark">("dark");
|
|
22
|
+
* return setContext(<div>{children}</div>, THEME, theme);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* Equivalent `ref` form:
|
|
28
|
+
* ```tsx
|
|
29
|
+
* function ThemeProvider({ children }: { children: JSX.Element }) {
|
|
30
|
+
* const theme = signal<"light" | "dark">("dark");
|
|
31
|
+
* return (
|
|
32
|
+
* <div ref={(el) => setContext(el, THEME, theme)}>
|
|
33
|
+
* {children}
|
|
34
|
+
* </div>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function setContext<H extends EventTarget | null, T>(host: H, key: PropertyKey, value: T): H;
|
|
40
|
+
/**
|
|
41
|
+
* Walk up from `consumer` (across open shadow boundaries via
|
|
42
|
+
* `getRootNode().host`) and return the first registered value for `key`,
|
|
43
|
+
* or `undefined`.
|
|
44
|
+
*
|
|
45
|
+
* One-shot — does not subscribe to anything. If `value` is a
|
|
46
|
+
* `Signal`/`Computed`, the caller reads it inside their own `effect` for
|
|
47
|
+
* reactivity.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* Inside a custom element's `connectedCallback`:
|
|
51
|
+
* ```ts
|
|
52
|
+
* const theme = getContext<() => "light" | "dark">(this, THEME);
|
|
53
|
+
* effect(() => { this.dataset.theme = theme?.() ?? "light"; });
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* From a JSX component — defer the lookup with `<dom-lifecycle>`, since `ref`
|
|
58
|
+
* runs before the element is inserted:
|
|
59
|
+
* ```tsx
|
|
60
|
+
* import { signal, type Signal } from "elements-kit/signals";
|
|
61
|
+
* import "elements-kit/utilities/dom-lifecycle";
|
|
62
|
+
*
|
|
63
|
+
* function ThemeConsumer() {
|
|
64
|
+
* const theme = signal<Signal<"light" | "dark"> | undefined>(undefined);
|
|
65
|
+
* return (
|
|
66
|
+
* <div>
|
|
67
|
+
* <dom-lifecycle
|
|
68
|
+
* onConnect={(el) => theme(getContext(el, THEME))}
|
|
69
|
+
* />
|
|
70
|
+
* {() => theme()?.() ?? "light"}
|
|
71
|
+
* </div>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare function getContext<T>(consumer: Element, key: PropertyKey): T | undefined;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { getContext, setContext };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { m as signal, p as onCleanup } from "../lib-CVfOddra.mjs";
|
|
2
|
+
import "../signals/index.mjs";
|
|
3
|
+
//#region src/utilities/context.ts
|
|
4
|
+
const registry = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
const versions = /* @__PURE__ */ new Map();
|
|
6
|
+
function version(key) {
|
|
7
|
+
let v = versions.get(key);
|
|
8
|
+
if (!v) versions.set(key, v = signal(0));
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register `value` under `key` on `host`. Calls to {@link getContext} from
|
|
13
|
+
* any descendant of `host` (in the DOM tree, including across open shadow
|
|
14
|
+
* roots) resolve to this value, unless an inner provider with the same key
|
|
15
|
+
* shadows it. Returns `host` so the call can be used inline.
|
|
16
|
+
*
|
|
17
|
+
* Must be called inside an `effect` / `effectScope` (or a wrapped
|
|
18
|
+
* `connectedCallback`). The entry is auto-removed via `onCleanup` when the
|
|
19
|
+
* surrounding scope disposes. Passes `null` through unchanged.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* Inline form — `setContext` returns the host so it composes directly:
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { signal } from "elements-kit/signals";
|
|
25
|
+
* import { setContext } from "elements-kit/utilities/context";
|
|
26
|
+
*
|
|
27
|
+
* const THEME = Symbol("theme");
|
|
28
|
+
*
|
|
29
|
+
* function ThemeProvider({ children }: { children: JSX.Element }) {
|
|
30
|
+
* const theme = signal<"light" | "dark">("dark");
|
|
31
|
+
* return setContext(<div>{children}</div>, THEME, theme);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* Equivalent `ref` form:
|
|
37
|
+
* ```tsx
|
|
38
|
+
* function ThemeProvider({ children }: { children: JSX.Element }) {
|
|
39
|
+
* const theme = signal<"light" | "dark">("dark");
|
|
40
|
+
* return (
|
|
41
|
+
* <div ref={(el) => setContext(el, THEME, theme)}>
|
|
42
|
+
* {children}
|
|
43
|
+
* </div>
|
|
44
|
+
* );
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function setContext(host, key, value) {
|
|
49
|
+
if (host == null) return host;
|
|
50
|
+
let map = registry.get(host);
|
|
51
|
+
if (!map) registry.set(host, map = /* @__PURE__ */ new Map());
|
|
52
|
+
map.set(key, value);
|
|
53
|
+
const owned = map;
|
|
54
|
+
const v = version(key);
|
|
55
|
+
v(v() + 1);
|
|
56
|
+
onCleanup(() => {
|
|
57
|
+
owned.delete(key);
|
|
58
|
+
if (owned.size === 0) registry.delete(host);
|
|
59
|
+
v(v() + 1);
|
|
60
|
+
});
|
|
61
|
+
return host;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Walk up from `consumer` (across open shadow boundaries via
|
|
65
|
+
* `getRootNode().host`) and return the first registered value for `key`,
|
|
66
|
+
* or `undefined`.
|
|
67
|
+
*
|
|
68
|
+
* One-shot — does not subscribe to anything. If `value` is a
|
|
69
|
+
* `Signal`/`Computed`, the caller reads it inside their own `effect` for
|
|
70
|
+
* reactivity.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* Inside a custom element's `connectedCallback`:
|
|
74
|
+
* ```ts
|
|
75
|
+
* const theme = getContext<() => "light" | "dark">(this, THEME);
|
|
76
|
+
* effect(() => { this.dataset.theme = theme?.() ?? "light"; });
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* From a JSX component — defer the lookup with `<dom-lifecycle>`, since `ref`
|
|
81
|
+
* runs before the element is inserted:
|
|
82
|
+
* ```tsx
|
|
83
|
+
* import { signal, type Signal } from "elements-kit/signals";
|
|
84
|
+
* import "elements-kit/utilities/dom-lifecycle";
|
|
85
|
+
*
|
|
86
|
+
* function ThemeConsumer() {
|
|
87
|
+
* const theme = signal<Signal<"light" | "dark"> | undefined>(undefined);
|
|
88
|
+
* return (
|
|
89
|
+
* <div>
|
|
90
|
+
* <dom-lifecycle
|
|
91
|
+
* onConnect={(el) => theme(getContext(el, THEME))}
|
|
92
|
+
* />
|
|
93
|
+
* {() => theme()?.() ?? "light"}
|
|
94
|
+
* </div>
|
|
95
|
+
* );
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function getContext(consumer, key) {
|
|
100
|
+
version(key)();
|
|
101
|
+
let node = consumer;
|
|
102
|
+
while (node) {
|
|
103
|
+
const map = registry.get(node);
|
|
104
|
+
if (map?.has(key)) return map.get(key);
|
|
105
|
+
const parent = node.parentNode;
|
|
106
|
+
if (parent) {
|
|
107
|
+
node = parent;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const root = node.getRootNode();
|
|
111
|
+
node = root instanceof ShadowRoot ? root.host : null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { getContext, setContext };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { c as effectScope, m as signal, s as effect } from "../lib-CVfOddra.mjs";
|
|
2
|
+
import "../signals/index.mjs";
|
|
3
|
+
import { f as it, l as describe, o as afterEach } from "../benchmark.CX_oY03V-COK1VBpH.mjs";
|
|
4
|
+
import { n as vi, t as globalExpect } from "../test.BmQO5GaM-C8YqghE0.mjs";
|
|
5
|
+
import { getContext, setContext } from "./context.mjs";
|
|
6
|
+
//#region src/utilities/context.test.ts
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
document.body.innerHTML = "";
|
|
9
|
+
});
|
|
10
|
+
describe("context", () => {
|
|
11
|
+
it("returns undefined when no provider exists above the consumer", () => {
|
|
12
|
+
const el = document.createElement("div");
|
|
13
|
+
document.body.appendChild(el);
|
|
14
|
+
globalExpect(getContext(el, "missing")).toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
it("resolves a value across one parent boundary", () => {
|
|
17
|
+
const host = document.createElement("section");
|
|
18
|
+
const child = document.createElement("span");
|
|
19
|
+
host.appendChild(child);
|
|
20
|
+
document.body.appendChild(host);
|
|
21
|
+
effectScope(() => {
|
|
22
|
+
setContext(host, "k", 42);
|
|
23
|
+
});
|
|
24
|
+
globalExpect(getContext(child, "k")).toBe(42);
|
|
25
|
+
});
|
|
26
|
+
it("propagates updates when the value is a Signal", () => {
|
|
27
|
+
const host = document.createElement("section");
|
|
28
|
+
const child = document.createElement("span");
|
|
29
|
+
host.appendChild(child);
|
|
30
|
+
document.body.appendChild(host);
|
|
31
|
+
const value = signal(1);
|
|
32
|
+
const seen = vi.fn();
|
|
33
|
+
effectScope(() => {
|
|
34
|
+
setContext(host, "n", value);
|
|
35
|
+
});
|
|
36
|
+
effect(() => {
|
|
37
|
+
seen(getContext(child, "n")?.());
|
|
38
|
+
});
|
|
39
|
+
globalExpect(seen).toHaveBeenLastCalledWith(1);
|
|
40
|
+
value(2);
|
|
41
|
+
globalExpect(seen).toHaveBeenLastCalledWith(2);
|
|
42
|
+
});
|
|
43
|
+
it("innermost provider wins when nested with the same key", () => {
|
|
44
|
+
const outer = document.createElement("section");
|
|
45
|
+
const inner = document.createElement("section");
|
|
46
|
+
const leaf = document.createElement("span");
|
|
47
|
+
outer.appendChild(inner);
|
|
48
|
+
inner.appendChild(leaf);
|
|
49
|
+
document.body.appendChild(outer);
|
|
50
|
+
effectScope(() => {
|
|
51
|
+
setContext(outer, "k", "outer");
|
|
52
|
+
setContext(inner, "k", "inner");
|
|
53
|
+
});
|
|
54
|
+
globalExpect(getContext(leaf, "k")).toBe("inner");
|
|
55
|
+
});
|
|
56
|
+
it("disposing the provider's scope removes the entry and falls through", () => {
|
|
57
|
+
const outer = document.createElement("section");
|
|
58
|
+
const inner = document.createElement("section");
|
|
59
|
+
const leaf = document.createElement("span");
|
|
60
|
+
outer.appendChild(inner);
|
|
61
|
+
inner.appendChild(leaf);
|
|
62
|
+
document.body.appendChild(outer);
|
|
63
|
+
const stopOuter = effectScope(() => {
|
|
64
|
+
setContext(outer, "k", "outer");
|
|
65
|
+
});
|
|
66
|
+
const stopInner = effectScope(() => {
|
|
67
|
+
setContext(inner, "k", "inner");
|
|
68
|
+
});
|
|
69
|
+
globalExpect(getContext(leaf, "k")).toBe("inner");
|
|
70
|
+
stopInner();
|
|
71
|
+
globalExpect(getContext(leaf, "k")).toBe("outer");
|
|
72
|
+
stopOuter();
|
|
73
|
+
globalExpect(getContext(leaf, "k")).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
it("distinct keys do not cross-talk", () => {
|
|
76
|
+
const host = document.createElement("section");
|
|
77
|
+
const child = document.createElement("span");
|
|
78
|
+
host.appendChild(child);
|
|
79
|
+
document.body.appendChild(host);
|
|
80
|
+
const A = Symbol("a");
|
|
81
|
+
const B = Symbol("b");
|
|
82
|
+
effectScope(() => {
|
|
83
|
+
setContext(host, A, "value-a");
|
|
84
|
+
setContext(host, B, "value-b");
|
|
85
|
+
});
|
|
86
|
+
globalExpect(getContext(child, A)).toBe("value-a");
|
|
87
|
+
globalExpect(getContext(child, B)).toBe("value-b");
|
|
88
|
+
globalExpect(getContext(child, "c")).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
it("crosses an open shadow root via getRootNode().host", () => {
|
|
91
|
+
const host = document.createElement("section");
|
|
92
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
93
|
+
const inner = document.createElement("span");
|
|
94
|
+
shadow.appendChild(inner);
|
|
95
|
+
document.body.appendChild(host);
|
|
96
|
+
effectScope(() => {
|
|
97
|
+
setContext(host, "k", "from-host");
|
|
98
|
+
});
|
|
99
|
+
globalExpect(getContext(inner, "k")).toBe("from-host");
|
|
100
|
+
});
|
|
101
|
+
it("moving the consumer picks up the new ancestor on next get", () => {
|
|
102
|
+
const a = document.createElement("section");
|
|
103
|
+
const b = document.createElement("section");
|
|
104
|
+
const leaf = document.createElement("span");
|
|
105
|
+
a.appendChild(leaf);
|
|
106
|
+
document.body.append(a, b);
|
|
107
|
+
effectScope(() => {
|
|
108
|
+
setContext(a, "k", "a");
|
|
109
|
+
setContext(b, "k", "b");
|
|
110
|
+
});
|
|
111
|
+
globalExpect(getContext(leaf, "k")).toBe("a");
|
|
112
|
+
b.appendChild(leaf);
|
|
113
|
+
globalExpect(getContext(leaf, "k")).toBe("b");
|
|
114
|
+
});
|
|
115
|
+
it("returns undefined for an orphan element not in any tree", () => {
|
|
116
|
+
globalExpect(getContext(document.createElement("div"), "k")).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
it("two providers with different keys in the same subtree both resolve", () => {
|
|
119
|
+
const host = document.createElement("section");
|
|
120
|
+
const leaf = document.createElement("span");
|
|
121
|
+
host.appendChild(leaf);
|
|
122
|
+
document.body.appendChild(host);
|
|
123
|
+
effectScope(() => {
|
|
124
|
+
setContext(host, "x", 1);
|
|
125
|
+
setContext(host, "y", 2);
|
|
126
|
+
});
|
|
127
|
+
globalExpect(getContext(leaf, "x")).toBe(1);
|
|
128
|
+
globalExpect(getContext(leaf, "y")).toBe(2);
|
|
129
|
+
});
|
|
130
|
+
describe("with <dom-lifecycle> as wrapper", () => {
|
|
131
|
+
it("descendants resolve a context set on the lifecycle wrapper itself", () => {
|
|
132
|
+
const wrapper = document.createElement("dom-lifecycle");
|
|
133
|
+
const consumer = document.createElement("button");
|
|
134
|
+
wrapper.appendChild(consumer);
|
|
135
|
+
document.body.appendChild(wrapper);
|
|
136
|
+
effectScope(() => {
|
|
137
|
+
setContext(wrapper, "theme", "dark");
|
|
138
|
+
});
|
|
139
|
+
globalExpect(getContext(consumer, "theme")).toBe("dark");
|
|
140
|
+
});
|
|
141
|
+
it("the wrapper is transparent in the ancestor walk — outer providers still resolve", () => {
|
|
142
|
+
const provider = document.createElement("section");
|
|
143
|
+
document.body.appendChild(provider);
|
|
144
|
+
effectScope(() => {
|
|
145
|
+
setContext(provider, "theme", "outer");
|
|
146
|
+
});
|
|
147
|
+
const wrapper = document.createElement("dom-lifecycle");
|
|
148
|
+
const consumer = document.createElement("button");
|
|
149
|
+
wrapper.appendChild(consumer);
|
|
150
|
+
provider.appendChild(wrapper);
|
|
151
|
+
globalExpect(getContext(consumer, "theme")).toBe("outer");
|
|
152
|
+
});
|
|
153
|
+
it("the wrapper shadows an outer provider when both register the same key", () => {
|
|
154
|
+
const provider = document.createElement("section");
|
|
155
|
+
document.body.appendChild(provider);
|
|
156
|
+
effectScope(() => {
|
|
157
|
+
setContext(provider, "theme", "outer");
|
|
158
|
+
});
|
|
159
|
+
const wrapper = document.createElement("dom-lifecycle");
|
|
160
|
+
const consumer = document.createElement("button");
|
|
161
|
+
wrapper.appendChild(consumer);
|
|
162
|
+
provider.appendChild(wrapper);
|
|
163
|
+
effectScope(() => {
|
|
164
|
+
setContext(wrapper, "theme", "inner");
|
|
165
|
+
});
|
|
166
|
+
globalExpect(getContext(consumer, "theme")).toBe("inner");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
//#endregion
|
|
171
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { c as effectScope, m as signal } from "../lib-
|
|
1
|
+
import { c as effectScope, m as signal } from "../lib-CVfOddra.mjs";
|
|
2
2
|
import "../signals/index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { c as beforeEach, f as it, l as describe, o as afterEach } from "../benchmark.CX_oY03V-COK1VBpH.mjs";
|
|
4
|
+
import { n as vi, t as globalExpect } from "../test.BmQO5GaM-C8YqghE0.mjs";
|
|
4
5
|
import { createDebounced } from "./debounced.mjs";
|
|
5
6
|
//#region src/utilities/debounced.test.ts
|
|
6
7
|
beforeEach(() => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { l as describe, t as bench } from "../benchmark.CX_oY03V-COK1VBpH.mjs";
|
|
2
|
+
//#region src/utilities/dom-lifecycle.bench.ts
|
|
3
|
+
describe("dom-lifecycle — registration cost", () => {
|
|
4
|
+
bench("register 100 entries on already-connected elements", () => {
|
|
5
|
+
document.body.innerHTML = "";
|
|
6
|
+
for (let i = 0; i < 100; i++) {
|
|
7
|
+
const el = document.createElement("div");
|
|
8
|
+
document.body.appendChild(el);
|
|
9
|
+
const probe = document.createElement("dom-lifecycle");
|
|
10
|
+
probe.onConnect = () => {};
|
|
11
|
+
el.appendChild(probe);
|
|
12
|
+
}
|
|
13
|
+
}, { iterations: 50 });
|
|
14
|
+
});
|
|
15
|
+
describe("dom-lifecycle — connect+disconnect cycle over N pre-staged entries", () => {
|
|
16
|
+
for (const N of [
|
|
17
|
+
10,
|
|
18
|
+
100,
|
|
19
|
+
1e3,
|
|
20
|
+
1e4
|
|
21
|
+
]) {
|
|
22
|
+
let wrapper;
|
|
23
|
+
bench(`cycle ${N} entries`, () => {
|
|
24
|
+
document.body.appendChild(wrapper);
|
|
25
|
+
wrapper.remove();
|
|
26
|
+
}, {
|
|
27
|
+
iterations: N >= 1e4 ? 30 : 50,
|
|
28
|
+
setup: () => {
|
|
29
|
+
document.body.innerHTML = "";
|
|
30
|
+
wrapper = document.createElement("div");
|
|
31
|
+
for (let i = 0; i < N; i++) {
|
|
32
|
+
const el = document.createElement("div");
|
|
33
|
+
wrapper.appendChild(el);
|
|
34
|
+
const probe = document.createElement("dom-lifecycle");
|
|
35
|
+
probe.onConnect = () => {};
|
|
36
|
+
probe.onDisconnect = () => {};
|
|
37
|
+
el.appendChild(probe);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
teardown: () => {
|
|
41
|
+
document.body.innerHTML = "";
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
describe("dom-lifecycle — single mutation, N inert page entries", () => {
|
|
47
|
+
for (const N of [
|
|
48
|
+
0,
|
|
49
|
+
100,
|
|
50
|
+
1e3,
|
|
51
|
+
1e4
|
|
52
|
+
]) {
|
|
53
|
+
let probe;
|
|
54
|
+
bench(`single mutation with ${N} other entries`, () => {
|
|
55
|
+
document.body.appendChild(probe);
|
|
56
|
+
probe.remove();
|
|
57
|
+
}, {
|
|
58
|
+
iterations: 50,
|
|
59
|
+
setup: () => {
|
|
60
|
+
document.body.innerHTML = "";
|
|
61
|
+
for (let i = 0; i < N; i++) {
|
|
62
|
+
const el = document.createElement("div");
|
|
63
|
+
document.body.appendChild(el);
|
|
64
|
+
const p = document.createElement("dom-lifecycle");
|
|
65
|
+
p.onConnect = () => {};
|
|
66
|
+
el.appendChild(p);
|
|
67
|
+
}
|
|
68
|
+
probe = document.createElement("span");
|
|
69
|
+
},
|
|
70
|
+
teardown: () => {
|
|
71
|
+
document.body.innerHTML = "";
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
describe("dom-lifecycle — single-entry connect/disconnect cycle", () => {
|
|
77
|
+
bench("create → append → remove → append (single entry)", () => {
|
|
78
|
+
document.body.innerHTML = "";
|
|
79
|
+
const el = document.createElement("div");
|
|
80
|
+
const probe = document.createElement("dom-lifecycle");
|
|
81
|
+
probe.onConnect = () => {};
|
|
82
|
+
probe.onDisconnect = () => {};
|
|
83
|
+
el.appendChild(probe);
|
|
84
|
+
document.body.appendChild(el);
|
|
85
|
+
el.remove();
|
|
86
|
+
document.body.appendChild(el);
|
|
87
|
+
}, { iterations: 50 });
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/utilities/dom-lifecycle.d.ts
|
|
2
|
+
type LifecycleCallback = (self: DomLifecycleElement) => void;
|
|
3
|
+
type AdoptedCallback = (oldDocument: Document, newDocument: Document) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Custom element backing the `<dom-lifecycle>` JSX tag. Place inside any
|
|
6
|
+
* element to receive lifecycle notifications for that surrounding subtree —
|
|
7
|
+
* `onConnect` / `onDisconnect` mirror `connectedCallback` /
|
|
8
|
+
* `disconnectedCallback`, `onMove` mirrors the upcoming `connectedMoveCallback`
|
|
9
|
+
* (fires when the element is moved with `Node.moveBefore()` instead of
|
|
10
|
+
* disconnect+connect), and `onAdopted` mirrors `adoptedCallback`.
|
|
11
|
+
*
|
|
12
|
+
* Position-tracking callbacks (`onConnect` / `onDisconnect` / `onMove`)
|
|
13
|
+
* receive the lifecycle element itself. Read `self.parentElement` for the
|
|
14
|
+
* surrounding element, `self.firstElementChild` / `self.children` for a
|
|
15
|
+
* wrapped subtree, or `self.getRootNode()` to walk through a shadow root.
|
|
16
|
+
* `self` is always the live element — non-null even when the lifecycle
|
|
17
|
+
* element is the root of a shadow tree (where `parentElement` is `null`).
|
|
18
|
+
*
|
|
19
|
+
* If you need the connect-time parent inside `onDisconnect` (the spec sets
|
|
20
|
+
* `parentElement` to `null` before `disconnectedCallback` runs), capture it
|
|
21
|
+
* yourself in `onConnect`.
|
|
22
|
+
*
|
|
23
|
+
* `onConnect` runs inside an `effectScope`, so any `onCleanup` registered
|
|
24
|
+
* inside it (directly or via factories like `setContext`, `on`, observers,
|
|
25
|
+
* etc.) fires on disconnect — before `onDisconnect` is invoked. The scope
|
|
26
|
+
* persists across moves (`onMove`); a fresh scope is created on every
|
|
27
|
+
* reconnection.
|
|
28
|
+
*
|
|
29
|
+
* Layout / a11y inert by default: `display: contents` removes the box so the
|
|
30
|
+
* element doesn't affect its parent's layout, and `role="none"` strips its
|
|
31
|
+
* implicit role from the accessibility tree. Children still participate in
|
|
32
|
+
* both layout and a11y. (CSS structural selectors like `:empty`,
|
|
33
|
+
* `:first-child`, and `:nth-child` still see the element in the DOM tree —
|
|
34
|
+
* place the lifecycle element where those selectors don't reach if needed.)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <div>
|
|
39
|
+
* <dom-lifecycle
|
|
40
|
+
* onConnect={(el) => el.parentElement?.classList.add("ready")}
|
|
41
|
+
* onDisconnect={(el) => {
|
|
42
|
+
* // el.parentElement is null here per spec — stash from onConnect if needed
|
|
43
|
+
* }}
|
|
44
|
+
* />
|
|
45
|
+
* </div>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* Wrap children — read the wrapped subtree via `firstElementChild`:
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <section>
|
|
52
|
+
* <dom-lifecycle onConnect={(el) => measure(el.firstElementChild)}>
|
|
53
|
+
* <h1>Title</h1>
|
|
54
|
+
* <p>Body</p>
|
|
55
|
+
* </dom-lifecycle>
|
|
56
|
+
* </section>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare class DomLifecycleElement extends HTMLElement {
|
|
60
|
+
#private;
|
|
61
|
+
constructor();
|
|
62
|
+
set onConnect(fn: LifecycleCallback | null);
|
|
63
|
+
get onConnect(): LifecycleCallback | null;
|
|
64
|
+
set onDisconnect(fn: LifecycleCallback | null);
|
|
65
|
+
get onDisconnect(): LifecycleCallback | null;
|
|
66
|
+
set onMove(fn: LifecycleCallback | null);
|
|
67
|
+
get onMove(): LifecycleCallback | null;
|
|
68
|
+
set onAdopted(fn: AdoptedCallback | null);
|
|
69
|
+
get onAdopted(): AdoptedCallback | null;
|
|
70
|
+
connectedCallback(): void;
|
|
71
|
+
disconnectedCallback(): void;
|
|
72
|
+
connectedMoveCallback(): void;
|
|
73
|
+
adoptedCallback(oldDocument: Document, newDocument: Document): void;
|
|
74
|
+
}
|
|
75
|
+
declare global {
|
|
76
|
+
namespace JSX {
|
|
77
|
+
interface IntrinsicElements {
|
|
78
|
+
"dom-lifecycle": {
|
|
79
|
+
onConnect?: LifecycleCallback;
|
|
80
|
+
onDisconnect?: LifecycleCallback;
|
|
81
|
+
onMove?: LifecycleCallback;
|
|
82
|
+
onAdopted?: AdoptedCallback;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { DomLifecycleElement };
|