@pyreon/ui-core 0.3.0 → 0.11.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/lib/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import * as _pyreon_styler0 from "@pyreon/styler";
2
2
  import { StyledFunction, css, keyframes, styled } from "@pyreon/styler";
3
3
  import * as _pyreon_core0 from "@pyreon/core";
4
4
  import { ComponentFn, VNodeChild } from "@pyreon/core";
5
+ import { PyreonTheme } from "@pyreon/unistyle";
5
6
 
6
7
  //#region src/compose.d.ts
7
8
  type ArityOneFn = (arg: any) => any;
@@ -216,6 +217,50 @@ declare const isEmpty: IsEmpty;
216
217
  //#region src/isEqual.d.ts
217
218
  declare const isEqual: (a: unknown, b: unknown) => boolean;
218
219
  //#endregion
220
+ //#region src/PyreonUI.d.ts
221
+ type ThemeMode = "light" | "dark";
222
+ type ThemeModeInput = ThemeMode | "system";
223
+ interface PyreonUIProps {
224
+ /** Theme object with breakpoints, rootSize, and custom keys. */
225
+ theme: PyreonTheme;
226
+ /**
227
+ * Color mode: "light", "dark", or "system" (follows OS preference).
228
+ * @default "light"
229
+ */
230
+ mode?: ThemeModeInput | undefined;
231
+ /** Flip mode for a nested section (e.g. dark sidebar in light app). */
232
+ inversed?: boolean | undefined;
233
+ children?: VNodeChild;
234
+ }
235
+ /**
236
+ * Read the resolved color mode ("light" | "dark") from the nearest PyreonUI.
237
+ * Reactive — updates when the mode prop changes or when OS preference changes
238
+ * (if mode="system").
239
+ *
240
+ * @example
241
+ * const mode = useMode() // "light" | "dark"
242
+ */
243
+ declare function useMode(): ThemeMode;
244
+ /**
245
+ * Unified provider for the Pyreon UI system.
246
+ *
247
+ * Replaces the need for separate UnistyleProvider, RocketstyleProvider,
248
+ * and ThemeProvider — one component, zero init.
249
+ *
250
+ * @example
251
+ * ```tsx
252
+ * <PyreonUI theme={{ rootSize: 16, breakpoints: { xs: 0, sm: 576, md: 768 } }} mode="system">
253
+ * <App />
254
+ * </PyreonUI>
255
+ * ```
256
+ */
257
+ declare function PyreonUI({
258
+ theme,
259
+ mode,
260
+ inversed,
261
+ children
262
+ }: PyreonUIProps): VNodeChild;
263
+ //#endregion
219
264
  //#region src/render.d.ts
220
265
  type RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild;
221
266
  /**
@@ -253,5 +298,5 @@ declare const throttle: <T extends (...args: any[]) => any>(fn: T, wait?: number
253
298
  };
254
299
  declare const merge: <T extends Record<string, any>>(target: T, ...sources: Record<string, any>[]) => T;
255
300
  //#endregion
256
- export { type BreakpointKeys, type Breakpoints, type CSSEngineConnector, type HTMLElementAttrs, type HTMLTagAttrsByTag, type HTMLTags, type HTMLTextTags, HTML_TAGS, HTML_TEXT_TAGS, type IsEmpty, Provider, type Render, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useStableValue };
301
+ export { type BreakpointKeys, type Breakpoints, type CSSEngineConnector, type HTMLElementAttrs, type HTMLTagAttrsByTag, type HTMLTags, type HTMLTextTags, HTML_TAGS, HTML_TEXT_TAGS, type IsEmpty, Provider, PyreonUI, type PyreonUIProps, type Render, type ThemeMode, type ThemeModeInput, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useMode, useStableValue };
257
302
  //# sourceMappingURL=index2.d.ts.map
package/lib/index.js CHANGED
@@ -1,5 +1,7 @@
1
- import { css, keyframes, styled } from "@pyreon/styler";
2
- import { createContext, h, provide } from "@pyreon/core";
1
+ import { ThemeContext, css, keyframes, styled } from "@pyreon/styler";
2
+ import { createContext, h, provide, useContext } from "@pyreon/core";
3
+ import { signal } from "@pyreon/reactivity";
4
+ import { enrichTheme } from "@pyreon/unistyle";
3
5
 
4
6
  //#region src/compose.ts
5
7
  const compose = (...fns) => (p) => fns.reduceRight((acc, cur) => cur(acc), p);
@@ -273,166 +275,87 @@ const isEqual = (a, b) => {
273
275
  };
274
276
 
275
277
  //#endregion
276
- //#region src/render.tsx
277
- const render = (content, attachProps) => {
278
- if (!content) return null;
279
- const t = typeof content;
280
- if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return content;
281
- if (Array.isArray(content)) return content;
282
- if (typeof content === "function") return h(content, attachProps ?? {});
283
- if (typeof content === "object" && content !== null) return content;
284
- return content;
285
- };
286
-
287
- //#endregion
288
- //#region ../../node_modules/.bun/@pyreon+reactivity@0.7.12/node_modules/@pyreon/reactivity/lib/index.js
289
- let batchDepth = 0;
290
- const setA = /* @__PURE__ */ new Set();
291
- let pendingNotifications = setA;
292
- function isBatching() {
293
- return batchDepth > 0;
294
- }
295
- function enqueuePendingNotification(notify) {
296
- pendingNotifications.add(notify);
297
- }
298
- let activeEffect = null;
299
- const effectDeps = /* @__PURE__ */ new WeakMap();
300
- let _depsCollector = null;
301
- let _skipDepsCollection = false;
302
- /**
303
- * Register the active effect as a subscriber of the given reactive source.
304
- * The subscriber Set is created lazily on the host — sources read only outside
305
- * effects never allocate a Set.
306
- */
307
- function trackSubscriber(host) {
308
- if (activeEffect) {
309
- if (!host._s) host._s = /* @__PURE__ */ new Set();
310
- host._s.add(activeEffect);
311
- if (_skipDepsCollection) return;
312
- if (_depsCollector) _depsCollector.push(host._s);
313
- else {
314
- let deps = effectDeps.get(activeEffect);
315
- if (!deps) {
316
- deps = /* @__PURE__ */ new Set();
317
- effectDeps.set(activeEffect, deps);
318
- }
319
- deps.add(host._s);
320
- }
321
- }
322
- }
323
- function notifySubscribers(subscribers) {
324
- if (subscribers.size === 0) return;
325
- if (subscribers.size === 1) {
326
- const sub = subscribers.values().next().value;
327
- if (isBatching()) enqueuePendingNotification(sub);
328
- else sub();
329
- return;
330
- }
331
- if (isBatching()) for (const sub of subscribers) enqueuePendingNotification(sub);
332
- else {
333
- const originalSize = subscribers.size;
334
- let i = 0;
335
- for (const sub of subscribers) {
336
- if (i >= originalSize) break;
337
- sub();
338
- i++;
339
- }
340
- }
341
- }
342
- let _traceListeners = null;
343
- /** @internal — called from signal.set() when tracing is active */
344
- function _notifyTraceListeners(sig, prev, next) {
345
- if (!_traceListeners) return;
346
- const event = {
347
- signal: sig,
348
- name: sig.label,
349
- prev,
350
- next,
351
- stack: (/* @__PURE__ */ new Error()).stack ?? "",
352
- timestamp: performance.now()
353
- };
354
- for (const l of _traceListeners) l(event);
355
- }
356
- /** Check if any trace listeners are active (fast path for signal.set) */
357
- function isTracing() {
358
- return _traceListeners !== null;
359
- }
360
- function _peek() {
361
- return this._v;
362
- }
363
- function _set(newValue) {
364
- if (Object.is(this._v, newValue)) return;
365
- const prev = this._v;
366
- this._v = newValue;
367
- if (isTracing()) _notifyTraceListeners(this, prev, newValue);
368
- if (this._d) notifyDirect(this._d);
369
- if (this._s) notifySubscribers(this._s);
370
- }
371
- function _update(fn) {
372
- _set.call(this, fn(this._v));
373
- }
374
- function _subscribe(listener) {
375
- if (!this._s) this._s = /* @__PURE__ */ new Set();
376
- this._s.add(listener);
377
- return () => this._s?.delete(listener);
278
+ //#region src/PyreonUI.tsx
279
+ const _isBrowser = typeof window !== "undefined" && typeof matchMedia === "function";
280
+ /** Reactive signal tracking the OS dark mode preference. Lazy-initialized on first use. */
281
+ let _systemMode;
282
+ function getSystemMode() {
283
+ if (_systemMode) return _systemMode;
284
+ _systemMode = signal(_isBrowser && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
285
+ if (_isBrowser) matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
286
+ _systemMode.set(e.matches ? "dark" : "light");
287
+ });
288
+ return _systemMode;
378
289
  }
290
+ const ModeContext = createContext("light");
291
+ const INVERSED = {
292
+ light: "dark",
293
+ dark: "light"
294
+ };
379
295
  /**
380
- * Register a direct updater lighter than subscribe().
381
- * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).
382
- * Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.
296
+ * Read the resolved color mode ("light" | "dark") from the nearest PyreonUI.
297
+ * Reactive updates when the mode prop changes or when OS preference changes
298
+ * (if mode="system").
299
+ *
300
+ * @example
301
+ * const mode = useMode() // "light" | "dark"
383
302
  */
384
- function _directFn(updater) {
385
- if (!this._d) this._d = [];
386
- const arr = this._d;
387
- const idx = arr.length;
388
- arr.push(updater);
389
- return () => {
390
- arr[idx] = null;
391
- };
303
+ function useMode() {
304
+ return useContext(ModeContext);
392
305
  }
306
+ let _autoInitDone = false;
393
307
  /**
394
- * Notify direct updaters flat array iteration, batch-aware.
395
- * Null slots (from disposed updaters) are skipped.
308
+ * Ensure the CSS engine is initialized. If init() was called manually,
309
+ * this is a no-op. Otherwise, imports @pyreon/styler defaults.
310
+ * Called once on first PyreonUI mount.
396
311
  */
397
- function notifyDirect(updaters) {
398
- if (isBatching()) for (let i = 0; i < updaters.length; i++) {
399
- const fn = updaters[i];
400
- if (fn) enqueuePendingNotification(fn);
401
- }
402
- else for (let i = 0; i < updaters.length; i++) updaters[i]?.();
403
- }
404
- function _debug() {
405
- return {
406
- name: this.label,
407
- value: this._v,
408
- subscriberCount: this._s?.size ?? 0
409
- };
312
+ function autoInit() {
313
+ if (_autoInitDone) return;
314
+ _autoInitDone = true;
410
315
  }
411
316
  /**
412
- * Create a reactive signal.
317
+ * Unified provider for the Pyreon UI system.
318
+ *
319
+ * Replaces the need for separate UnistyleProvider, RocketstyleProvider,
320
+ * and ThemeProvider — one component, zero init.
413
321
  *
414
- * Only 1 closure is allocated (the read function). State is stored as
415
- * properties on the function object (_v, _s) and methods (peek, set,
416
- * update, subscribe) are shared across all signals not per-signal closures.
322
+ * @example
323
+ * ```tsx
324
+ * <PyreonUI theme={{ rootSize: 16, breakpoints: { xs: 0, sm: 576, md: 768 } }} mode="system">
325
+ * <App />
326
+ * </PyreonUI>
327
+ * ```
417
328
  */
418
- function signal(initialValue, options) {
419
- const read = (() => {
420
- trackSubscriber(read);
421
- return read._v;
329
+ function PyreonUI({ theme, mode = "light", inversed, children }) {
330
+ autoInit();
331
+ let resolvedMode;
332
+ if (mode === "system") resolvedMode = getSystemMode()();
333
+ else resolvedMode = mode;
334
+ if (inversed) resolvedMode = INVERSED[resolvedMode];
335
+ const enrichedTheme = enrichTheme(theme);
336
+ provide(ThemeContext, enrichedTheme);
337
+ provide(context, {
338
+ theme: enrichedTheme,
339
+ mode: resolvedMode,
340
+ isDark: resolvedMode === "dark",
341
+ isLight: resolvedMode === "light"
422
342
  });
423
- read._v = initialValue;
424
- read._s = null;
425
- read._d = null;
426
- read.peek = _peek;
427
- read.set = _set;
428
- read.update = _update;
429
- read.subscribe = _subscribe;
430
- read.direct = _directFn;
431
- read.debug = _debug;
432
- read.label = options?.name;
433
- return read;
343
+ provide(ModeContext, resolvedMode);
344
+ return children ?? null;
434
345
  }
435
346
 
347
+ //#endregion
348
+ //#region src/render.tsx
349
+ const render = (content, attachProps) => {
350
+ if (!content) return null;
351
+ const t = typeof content;
352
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return content;
353
+ if (Array.isArray(content)) return content;
354
+ if (typeof content === "function") return h(content, attachProps ?? {});
355
+ if (typeof content === "object") return content;
356
+ return content;
357
+ };
358
+
436
359
  //#endregion
437
360
  //#region src/useStableValue.ts
438
361
  /**
@@ -555,5 +478,5 @@ const merge = (target, ...sources) => {
555
478
  };
556
479
 
557
480
  //#endregion
558
- export { HTML_TAGS, HTML_TEXT_TAGS, Provider, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useStableValue };
481
+ export { HTML_TAGS, HTML_TEXT_TAGS, Provider, PyreonUI, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useMode, useStableValue };
559
482
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@pyreon/ui-core",
3
- "version": "0.3.0",
3
+ "version": "0.11.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/pyreon/ui-system",
7
- "directory": "packages/ui-core"
7
+ "directory": "packages/ui-system/ui-core"
8
8
  },
9
9
  "description": "Core utilities, config, and context for Pyreon UI System",
10
10
  "license": "MIT",
11
11
  "type": "module",
12
12
  "sideEffects": false,
13
13
  "exports": {
14
+ "bun": "./src/index.ts",
14
15
  "source": "./src/index.ts",
15
16
  "import": "./lib/index.js",
16
17
  "types": "./lib/index.d.ts"
@@ -25,7 +26,7 @@
25
26
  "LICENSE"
26
27
  ],
27
28
  "engines": {
28
- "node": ">= 18"
29
+ "node": ">= 22"
29
30
  },
30
31
  "publishConfig": {
31
32
  "access": "public"
@@ -37,11 +38,13 @@
37
38
  "typecheck": "tsc --noEmit"
38
39
  },
39
40
  "peerDependencies": {
40
- "@pyreon/core": ">=0.4.0 <1.0.0",
41
- "@pyreon/styler": ">=0.3.0"
41
+ "@pyreon/core": "^0.11.0",
42
+ "@pyreon/styler": "^0.11.0",
43
+ "@pyreon/reactivity": "^0.11.0",
44
+ "@pyreon/unistyle": "^0.11.0"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@vitus-labs/tools-rolldown": "^1.15.3",
45
- "@pyreon/typescript": "^0.7.4"
48
+ "@pyreon/typescript": "^0.11.0"
46
49
  }
47
50
  }