elements-kit 0.22.0 → 0.22.1

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/README.md CHANGED
@@ -52,7 +52,7 @@ ElementsKit is a library of reactive primitives, not a framework. Each piece is
52
52
 
53
53
  - **Designed for the AI age.** Code is cheap; maintenance still isn't. Primitives compose into higher-level blocks. Swap one block at a time instead of maintaining long lines of code.
54
54
 
55
- - **Bundler-friendly.** Every primitive is its own subpath — `elements-kit/signals`, `elements-kit/utilities/*`, `elements-kit/integrations/*`. Import only what you need.
55
+ - **Bundler-friendly.** Every primitive is its own subpath — `elements-kit/signals`, `elements-kit/utilities/*`, `elements-kit/ui/*`, `elements-kit/integrations/*`. Import only what you need.
56
56
 
57
57
  ## Packages
58
58
 
@@ -73,6 +73,7 @@ Every feature is a separate subpath export — import only what you use.
73
73
  | `elements-kit/integrations/react` | `useSignal`, `useScope` React bridge hooks |
74
74
  | `elements-kit/integrations/astro` | `elementsKit()` Astro integration — elements-kit components as server-rendered, hydrated islands *(experimental)* |
75
75
  | `elements-kit/utilities/*` | Reactive browser-API utilities — see [src/utilities/README.md](src/utilities/README.md) |
76
+ | `elements-kit/ui/*` | Accessible UI component styles (CSS) + an optional base theme (`elements-kit/ui/styles.css`); plus the `otp-input` custom element and `overlay` positioning/interaction behaviors. Catalog in [src/ui/README.md](src/ui/README.md) |
76
77
 
77
78
  ## Signals
78
79
 
@@ -340,7 +341,7 @@ await op; // awaitable (delegates to .then/.catch/.finally via .raw)
340
341
  op.stop(); // halt reruns + fire registered cleanup
341
342
  ```
342
343
 
343
- Reactive state getters: `.state`, `.value`, `.reason`, `.result`, `.pending`, `.raw` (the underlying `ComputedPromise`).
344
+ Reactive state getters: `.state` (`"idle" | "pending" | "fulfilled" | "rejected"` — `idle` before the first run, so `.pending` is `false` until you trigger one), `.value`, `.reason`, `.result`, `.pending`, `.raw` (the underlying `ComputedPromise`).
344
345
 
345
346
  One-shot mutation (no tracking):
346
347
 
package/dist/await.mjs CHANGED
@@ -22,7 +22,7 @@ function Await(props) {
22
22
  if (awaitables.length === 0) return children;
23
23
  if (effectsInert()) return promise(Promise.all(awaitables).then(() => () => children));
24
24
  const fallback = props.fallback;
25
- const pending = () => awaitables.some((a) => a.state === "pending");
25
+ const pending = () => awaitables.some((a) => a.state !== "fulfilled" && a.state !== "rejected");
26
26
  const region = () => pending() ? read(fallback) : children;
27
27
  region[ASYNC_REGION] = {
28
28
  ids: 1 + fromChildren.length,
@@ -0,0 +1,19 @@
1
+ //#region src/utilities/environment.d.ts
2
+ /** `true` when running in a browser (both `window` and `document` exist). */
3
+ declare const isBrowser: boolean;
4
+ /**
5
+ * `HTMLElement` in the browser; an empty stand-in on the server.
6
+ *
7
+ * A custom-element class evaluates its `extends` clause at module load, but
8
+ * `HTMLElement` is undefined in Node — so importing an element module during
9
+ * SSR (e.g. an Astro `client:load` island) throws `HTMLElement is not defined`
10
+ * before any `isBrowser` guard can run. Elements are never *constructed* on the
11
+ * server (the streaming renderer emits plain tags), so a stub base is enough to
12
+ * let the class definition load; the guarded `customElements.define` still only
13
+ * registers in a real browser.
14
+ */
15
+ declare const HTMLElementBase: typeof HTMLElement;
16
+ /** A shared `Disposable` whose `Symbol.dispose` is a no-op — useful as a safe default. */
17
+ declare const noopDisposable: Disposable;
18
+ //#endregion
19
+ export { isBrowser as n, noopDisposable as r, HTMLElementBase as t };
@@ -1,2 +1,2 @@
1
- import { t as hydrate } from "../hydrate-CHomBRY9.mjs";
1
+ import { t as hydrate } from "../hydrate-DvWdnV-e.mjs";
2
2
  export { hydrate };
@@ -158,7 +158,7 @@ function claimAsync(cur, p, om) {
158
158
  }
159
159
  const slot = claimSlot(cur, om);
160
160
  effect(() => {
161
- if (p.state === "pending") return;
161
+ if (p.state !== "fulfilled" && p.state !== "rejected") return;
162
162
  slot.set(resolveChild(p.result));
163
163
  });
164
164
  onCleanup(() => slot.clear());
@@ -1,6 +1,6 @@
1
1
  import { t as createElement } from "../element-Di0PYFX1.mjs";
2
2
  import { render } from "../render.mjs";
3
- import { t as hydrate } from "../hydrate-CHomBRY9.mjs";
3
+ import { t as hydrate } from "../hydrate-DvWdnV-e.mjs";
4
4
  import { withSlotProps } from "./astro-slots.mjs";
5
5
  //#region src/integrations/astro-client.ts
6
6
  /**
@@ -1,4 +1,5 @@
1
1
  import { n as AttrChangeHandler, t as ATTRIBUTES } from "../../attributes-DILeh3-s.mjs";
2
+ import { t as HTMLElementBase } from "../../environment-CTAGxqz_.mjs";
2
3
 
3
4
  //#region src/ui/otp-input/otp-input.d.ts
4
5
  /**
@@ -30,7 +31,7 @@ import { n as AttrChangeHandler, t as ATTRIBUTES } from "../../attributes-DILeh3
30
31
  * </x-otp-input>
31
32
  * ```
32
33
  */
33
- declare class XOtpInput extends HTMLElement {
34
+ declare class XOtpInput extends HTMLElementBase {
34
35
  #private;
35
36
  static formAssociated: boolean;
36
37
  static [ATTRIBUTES]: {
@@ -82,7 +83,7 @@ declare global {
82
83
  * `<x-otp-input>` paints its character and active / caret / disabled state by
83
84
  * index; all styling lives in the companion CSS.
84
85
  */
85
- declare class XOtpSlot extends HTMLElement {
86
+ declare class XOtpSlot extends HTMLElementBase {
86
87
  static [ATTRIBUTES]: {
87
88
  index: AttrChangeHandler<XOtpSlot>;
88
89
  };
@@ -102,7 +103,7 @@ declare global {
102
103
  * `<x-otp-slot>`s into one attached group. All layout lives in CSS; this only
103
104
  * applies a `group` role so the visual grouping is exposed to assistive tech.
104
105
  */
105
- declare class XOtpGroup extends HTMLElement {
106
+ declare class XOtpGroup extends HTMLElementBase {
106
107
  connectedCallback(): void;
107
108
  }
108
109
  declare global {
@@ -118,7 +119,7 @@ declare global {
118
119
  * `<x-otp-separator>` — decorative divider authored between OTP groups (e.g. a
119
120
  * dash between a 3-3 split). Purely visual: `aria-hidden`, glyph drawn in CSS.
120
121
  */
121
- declare class XOtpSeparator extends HTMLElement {
122
+ declare class XOtpSeparator extends HTMLElementBase {
122
123
  connectedCallback(): void;
123
124
  }
124
125
  declare global {
@@ -3,7 +3,7 @@ import { l as computed, u as effect, x as signal } from "../../lib-DYypKhxk.mjs"
3
3
  import "../../signals/index.mjs";
4
4
  import { on } from "../../utilities/event-listener.mjs";
5
5
  import { render } from "../../render.mjs";
6
- import { isBrowser } from "../../utilities/environment.mjs";
6
+ import { HTMLElementBase, isBrowser } from "../../utilities/environment.mjs";
7
7
  //#region src/ui/otp-input/otp-input.shadow.css?inline
8
8
  var otp_input_shadow_default = ".x-otp-input__field {\n color: #0000;\n caret-color: #0000;\n width: 100%;\n height: 100%;\n font: inherit;\n letter-spacing: inherit;\n pointer-events: auto;\n background: none;\n border: 0;\n outline: none;\n margin: 0;\n padding: 0;\n position: absolute;\n inset: 0;\n}\n\n.x-otp-input__field::selection {\n color: #0000;\n background-color: #0000;\n}\n";
9
9
  //#endregion
@@ -43,7 +43,7 @@ SHADOW_SHEET?.replaceSync(otp_input_shadow_default);
43
43
  * </x-otp-input>
44
44
  * ```
45
45
  */
46
- var XOtpInput = class XOtpInput extends HTMLElement {
46
+ var XOtpInput = class XOtpInput extends HTMLElementBase {
47
47
  static formAssociated = true;
48
48
  static [ATTRIBUTES] = {
49
49
  value(value) {
@@ -293,7 +293,7 @@ if (isBrowser && !customElements.get("x-otp-input")) customElements.define("x-ot
293
293
  * `<x-otp-input>` paints its character and active / caret / disabled state by
294
294
  * index; all styling lives in the companion CSS.
295
295
  */
296
- var XOtpSlot = class extends HTMLElement {
296
+ var XOtpSlot = class extends HTMLElementBase {
297
297
  connectedCallback() {
298
298
  this.setAttribute("aria-hidden", "true");
299
299
  }
@@ -306,7 +306,7 @@ if (isBrowser && !customElements.get("x-otp-slot")) customElements.define("x-otp
306
306
  * `<x-otp-slot>`s into one attached group. All layout lives in CSS; this only
307
307
  * applies a `group` role so the visual grouping is exposed to assistive tech.
308
308
  */
309
- var XOtpGroup = class extends HTMLElement {
309
+ var XOtpGroup = class extends HTMLElementBase {
310
310
  connectedCallback() {
311
311
  if (!this.hasAttribute("role")) this.setAttribute("role", "group");
312
312
  }
@@ -318,7 +318,7 @@ if (isBrowser && !customElements.get("x-otp-group")) customElements.define("x-ot
318
318
  * `<x-otp-separator>` — decorative divider authored between OTP groups (e.g. a
319
319
  * dash between a 3-3 split). Purely visual: `aria-hidden`, glyph drawn in CSS.
320
320
  */
321
- var XOtpSeparator = class extends HTMLElement {
321
+ var XOtpSeparator = class extends HTMLElementBase {
322
322
  connectedCallback() {
323
323
  this.setAttribute("aria-hidden", "true");
324
324
  }
@@ -38,7 +38,7 @@ declare class Async<TInput = undefined, TOutput = unknown> {
38
38
  set fn(fn: MaybeReactive<Fn<TInput, TOutput>>);
39
39
  get raw(): ComputedPromise<TOutput | undefined>;
40
40
  get pending(): boolean;
41
- get state(): "pending" | "fulfilled" | "rejected";
41
+ get state(): "idle" | "pending" | "fulfilled" | "rejected";
42
42
  get value(): TOutput | undefined;
43
43
  get reason(): unknown;
44
44
  get result(): unknown;
@@ -9,6 +9,7 @@ function flushDeferredAsyncRuns() {
9
9
  deferredRuns.clear();
10
10
  for (const run of runs) run();
11
11
  }
12
+ const IDLE = promise(() => {});
12
13
  /**
13
14
  * Reactive wrapper around an async function. Exposes the current run as
14
15
  * reactive signals (`state`, `value`, `reason`, `result`, `pending`) and
@@ -44,15 +45,16 @@ var Async = class {
44
45
  set fn(fn) {
45
46
  this.#fn(resolve(fn));
46
47
  }
47
- #current = signal(promise(() => {}));
48
+ #current = signal(IDLE);
48
49
  get raw() {
49
50
  return this.#current();
50
51
  }
51
52
  get pending() {
52
- return this.raw.state === "pending";
53
+ return this.state === "pending";
53
54
  }
54
55
  get state() {
55
- return this.raw.state;
56
+ const current = this.#current();
57
+ return current === IDLE ? "idle" : current.state;
56
58
  }
57
59
  get value() {
58
60
  return this.raw.value;
@@ -77,7 +79,12 @@ var Async = class {
77
79
  * `ReactivePromise`. See `ReactivePromise[SEED]`.
78
80
  */
79
81
  [SEED](value) {
80
- const seed = this.raw[SEED];
82
+ let current = this.#current();
83
+ if (current === IDLE) {
84
+ current = promise(() => {});
85
+ this.#current(current);
86
+ }
87
+ const seed = current[SEED];
81
88
  seed?.(value);
82
89
  }
83
90
  /**
@@ -89,7 +96,8 @@ var Async = class {
89
96
  const pendingRun = deferredRuns.get(this);
90
97
  deferredRuns.delete(this);
91
98
  if (record) {
92
- if (untracked(() => this.state) === "pending") this[SEED](record.value);
99
+ const state = untracked(() => this.state);
100
+ if (state !== "fulfilled" && state !== "rejected") this[SEED](record.value);
93
101
  return;
94
102
  }
95
103
  pendingRun?.();
@@ -1,7 +1,2 @@
1
- //#region src/utilities/environment.d.ts
2
- /** `true` when running in a browser (both `window` and `document` exist). */
3
- declare const isBrowser: boolean;
4
- /** A shared `Disposable` whose `Symbol.dispose` is a no-op — useful as a safe default. */
5
- declare const noopDisposable: Disposable;
6
- //#endregion
7
- export { isBrowser, noopDisposable };
1
+ import { n as isBrowser, r as noopDisposable, t as HTMLElementBase } from "../environment-CTAGxqz_.mjs";
2
+ export { HTMLElementBase, isBrowser, noopDisposable };
@@ -1,7 +1,19 @@
1
1
  //#region src/utilities/environment.ts
2
2
  /** `true` when running in a browser (both `window` and `document` exist). */
3
3
  const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
4
+ /**
5
+ * `HTMLElement` in the browser; an empty stand-in on the server.
6
+ *
7
+ * A custom-element class evaluates its `extends` clause at module load, but
8
+ * `HTMLElement` is undefined in Node — so importing an element module during
9
+ * SSR (e.g. an Astro `client:load` island) throws `HTMLElement is not defined`
10
+ * before any `isBrowser` guard can run. Elements are never *constructed* on the
11
+ * server (the streaming renderer emits plain tags), so a stub base is enough to
12
+ * let the class definition load; the guarded `customElements.define` still only
13
+ * registers in a real browser.
14
+ */
15
+ const HTMLElementBase = isBrowser ? HTMLElement : class {};
4
16
  /** A shared `Disposable` whose `Symbol.dispose` is a no-op — useful as a safe default. */
5
17
  const noopDisposable = { [Symbol.dispose]() {} };
6
18
  //#endregion
7
- export { isBrowser, noopDisposable };
19
+ export { HTMLElementBase, isBrowser, noopDisposable };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elements-kit",
3
3
  "type": "module",
4
- "version": "0.22.0",
4
+ "version": "0.22.1",
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",