@pyreon/ui-core 0.12.15 → 0.13.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.js +11 -6
- package/lib/index.js.map +1 -1
- package/package.json +6 -6
- package/src/PyreonUI.tsx +26 -12
- package/src/__tests__/PyreonUI.test.tsx +3 -1
package/lib/index.js
CHANGED
|
@@ -346,17 +346,22 @@ function autoInit() {
|
|
|
346
346
|
*/
|
|
347
347
|
function PyreonUI(props) {
|
|
348
348
|
autoInit();
|
|
349
|
+
const parentModeAccessor = useContext(ModeContext);
|
|
349
350
|
const resolveMode = () => {
|
|
350
|
-
const mode = props.mode
|
|
351
|
-
|
|
352
|
-
|
|
351
|
+
const mode = props.mode;
|
|
352
|
+
let resolved;
|
|
353
|
+
if (mode === void 0 || mode === null) resolved = parentModeAccessor();
|
|
354
|
+
else {
|
|
355
|
+
const raw = typeof mode === "function" ? mode() : mode;
|
|
356
|
+
resolved = raw === "system" ? getSystemMode()() : raw;
|
|
357
|
+
}
|
|
353
358
|
return props.inversed ? INVERSED[resolved] : resolved;
|
|
354
359
|
};
|
|
355
360
|
const modeComputed = computed(resolveMode);
|
|
356
|
-
const enrichedTheme = enrichTheme(props.theme);
|
|
357
|
-
provide(ThemeContext, enrichedTheme);
|
|
361
|
+
const enrichedTheme = computed(() => enrichTheme(props.theme));
|
|
362
|
+
provide(ThemeContext, () => enrichedTheme());
|
|
358
363
|
provide(context, () => ({
|
|
359
|
-
theme: enrichedTheme,
|
|
364
|
+
theme: enrichedTheme(),
|
|
360
365
|
mode: modeComputed(),
|
|
361
366
|
isDark: modeComputed() === "dark",
|
|
362
367
|
isLight: modeComputed() === "light"
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["coreContext"],"sources":["../src/compose.ts","../src/config.ts","../src/isEmpty.ts","../src/context.tsx","../src/hoistNonReactStatics.ts","../src/html/htmlTags.ts","../src/isEqual.ts","../src/PyreonUI.tsx","../src/render.tsx","../src/useStableValue.ts","../src/utils.ts"],"sourcesContent":["type ArityOneFn = (arg: any) => any\ntype PickLastInTuple<T extends any[]> = T extends [...rest: infer _U, argn: infer L] ? L : any\ntype FirstFnParameterType<T extends any[]> = Parameters<PickLastInTuple<T>>[any]\ntype LastFnReturnType<T extends any[]> = ReturnType<T[0]>\n\nconst compose =\n <T extends ArityOneFn[]>(...fns: T) =>\n (p: FirstFnParameterType<T>): LastFnReturnType<T> =>\n fns.reduceRight((acc: any, cur: any) => cur(acc), p)\n\nexport default compose\n","import type { StyledFunction } from '@pyreon/styler'\nimport { css, keyframes, styled } from '@pyreon/styler'\nimport type { HTMLTags } from './html'\n\n/**\n * Describes the shape of the CSS-in-JS engine.\n * Pyreon uses @pyreon/styler directly — no connector abstraction needed.\n * This type is kept for API compatibility with downstream packages.\n */\nexport interface CSSEngineConnector {\n css: typeof css\n styled: typeof styled\n keyframes: typeof keyframes\n}\n\ninterface PlatformConfig {\n component: string | HTMLTags\n textComponent: string | HTMLTags\n createMediaQueries?: (props: {\n breakpoints: Record<string, number>\n rootSize: number\n css: CSSEngineConnector['css']\n }) => Record<string, (...args: any[]) => any>\n}\n\ntype InitConfig = Partial<CSSEngineConnector & PlatformConfig>\n\n/**\n * Configuration singleton that bridges the UI system with the CSS engine.\n * All packages reference config.css, config.styled, etc.\n *\n * In Pyreon, the engine is @pyreon/styler and is available immediately —\n * no lazy initialization or connector pattern needed.\n */\nclass Configuration {\n css = css\n styled: StyledFunction = styled\n keyframes = keyframes\n component: string | HTMLTags = 'div'\n textComponent: string | HTMLTags = 'span'\n createMediaQueries: PlatformConfig['createMediaQueries'] = undefined\n\n init = (props: InitConfig) => {\n if (props.css) this.css = props.css\n if (props.styled) this.styled = props.styled\n if (props.keyframes) this.keyframes = props.keyframes\n if (props.component) this.component = props.component\n if (props.textComponent) this.textComponent = props.textComponent\n if (props.createMediaQueries) this.createMediaQueries = props.createMediaQueries\n }\n}\n\nconst config = new Configuration()\nconst { init } = config\n\nexport default config\nexport { init }\n","export type IsEmpty = <T extends Record<number | string, any> | any[] | null | undefined>(\n param: T,\n) => T extends null | undefined\n ? true\n : keyof T extends never\n ? true\n : T extends T[]\n ? T[number] extends never\n ? true\n : false\n : false\n\nconst isEmpty = (<T extends Record<number | string, any> | any[] | null | undefined>(param: T) => {\n if (!param) return true\n if (typeof param !== 'object') return true\n if (Array.isArray(param)) return param.length === 0\n return Object.keys(param).length === 0\n}) as IsEmpty\n\nexport default isEmpty\n","import type { VNodeChild } from '@pyreon/core'\nimport { createReactiveContext, provide } from '@pyreon/core'\nimport isEmpty from './isEmpty'\nimport type { Breakpoints } from './types'\n\n/**\n * Core context value shared across all @pyreon UI packages.\n */\nexport interface CoreContextValue {\n theme: Record<string, unknown>\n mode: 'light' | 'dark'\n isDark: boolean\n isLight: boolean\n}\n\n/**\n * Internal reactive context shared across all @pyreon packages.\n * Carries the theme object, mode, and derived dark/light flags.\n *\n * ReactiveContext means useContext() returns `() => CoreContextValue`.\n */\nconst context = createReactiveContext<CoreContextValue>({\n theme: {},\n mode: 'light',\n isDark: false,\n isLight: true,\n})\n\ntype Theme = Partial<\n {\n rootSize: number\n breakpoints: Breakpoints\n } & Record<string, any>\n>\n\ntype ProviderType = Partial<\n {\n theme: Theme\n children: VNodeChild\n } & Record<string, any>\n>\n\n/**\n * @internal Low-level provider — use `PyreonUI` from `@pyreon/ui-core` instead.\n *\n * Provider that feeds the internal Pyreon context with the theme.\n * When no theme is supplied, renders children directly.\n *\n * @deprecated Prefer `<PyreonUI theme={theme}>` which handles all context layers.\n */\nconst __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'\n\nfunction Provider({ theme, children, ...props }: ProviderType): VNodeChild {\n if (__DEV__) {\n // oxlint-disable-next-line no-console\n console.warn(\n '[Pyreon] CoreProvider is internal. Use <PyreonUI theme={theme}> instead — it handles all context layers (styler, core, mode) in one component.',\n )\n }\n if (isEmpty(theme) || !theme) return children ?? null\n\n provide(context, () => ({\n theme: theme as Record<string, unknown>,\n mode: (props.mode as 'light' | 'dark') ?? 'light',\n isDark: props.isDark as boolean ?? false,\n isLight: props.isLight as boolean ?? true,\n ...props,\n }))\n\n return children ?? null\n}\n\nexport { context }\n\nexport default Provider\n","const KNOWN_STATICS: Record<string, true> = {\n name: true,\n length: true,\n prototype: true,\n caller: true,\n callee: true,\n arguments: true,\n arity: true,\n}\n\nconst COMPONENT_STATICS: Record<string, true> = {\n displayName: true,\n defaultProps: true,\n}\n\n/**\n * Copies non-framework static properties from a source component to a target.\n *\n * Pyreon equivalent of hoistNonReactStatics — simplified since Pyreon\n * components are plain functions without React-specific statics like\n * contextType, propTypes, getDerivedStateFromProps, etc.\n */\nconst hoistNonReactStatics = <T, S>(\n target: T,\n source: S,\n excludeList?: Record<string, true>,\n): T => {\n if (typeof source === 'string') return target\n\n const proto = Object.getPrototypeOf(source)\n if (proto && proto !== Object.prototype) {\n hoistNonReactStatics(target, proto, excludeList)\n }\n\n const keys: (string | symbol)[] = [\n ...Object.getOwnPropertyNames(source),\n ...Object.getOwnPropertySymbols(source),\n ]\n\n for (const key of keys) {\n const k = key as string\n if (KNOWN_STATICS[k] || excludeList?.[k] || COMPONENT_STATICS[k]) {\n continue\n }\n\n const descriptor = Object.getOwnPropertyDescriptor(source, key)\n if (descriptor) {\n try {\n Object.defineProperty(target, key, descriptor)\n } catch {\n // Silently skip non-configurable properties\n }\n }\n }\n\n return target\n}\n\nexport default hoistNonReactStatics\n","const HTML_TAGS = [\n 'a',\n 'abbr',\n 'address',\n 'area',\n 'article',\n 'aside',\n 'audio',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blockquote',\n 'body',\n 'br',\n 'button',\n 'canvas',\n 'caption',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'data',\n 'datalist',\n 'dd',\n 'del',\n 'details',\n 'dfn',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'embed',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'html',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'map',\n 'mark',\n 'meter',\n 'nav',\n 'object',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'section',\n 'select',\n 'small',\n 'source',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'svg',\n 'table',\n 'tbody',\n 'td',\n 'template',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'track',\n 'u',\n 'ul',\n 'var',\n 'video',\n 'wbr',\n] as const\n\nconst HTML_TEXT_TAGS = [\n 'abbr',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blockquote',\n 'cite',\n 'code',\n 'del',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'figcaption',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'i',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'p',\n 'pre',\n 'q',\n 'rp',\n 'rt',\n 's',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'time',\n 'u',\n] as const\n\nexport type HTMLTags = (typeof HTML_TAGS)[number]\nexport type HTMLTextTags = (typeof HTML_TEXT_TAGS)[number]\nexport { HTML_TAGS, HTML_TEXT_TAGS }\n","const isArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!isEqual(a[i], b[i])) return false\n }\n return true\n}\n\nconst isObjectEqual = (a: Record<string, unknown>, b: Record<string, unknown>): boolean => {\n const aKeys = Object.keys(a)\n if (aKeys.length !== Object.keys(b).length) return false\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false\n if (!isEqual(a[key], b[key])) return false\n }\n return true\n}\n\nconst isEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true\n if (typeof a !== typeof b || a == null || b == null || typeof a !== 'object') return false\n if (Array.isArray(a)) return Array.isArray(b) && isArrayEqual(a, b)\n if (Array.isArray(b)) return false\n return isObjectEqual(a as Record<string, unknown>, b as Record<string, unknown>)\n}\n\nexport default isEqual\n","import type { VNodeChild } from '@pyreon/core'\nimport { createReactiveContext, provide, useContext } from '@pyreon/core'\nimport { computed, signal } from '@pyreon/reactivity'\nimport { ThemeContext } from '@pyreon/styler'\nimport type { PyreonTheme } from '@pyreon/unistyle'\nimport { enrichTheme } from '@pyreon/unistyle'\nimport { context as coreContext } from './context'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type ThemeMode = 'light' | 'dark'\nexport type ThemeModeInput = ThemeMode | 'system'\n\nexport interface PyreonUIProps {\n /** Theme object with breakpoints, rootSize, and custom keys. */\n theme: PyreonTheme\n /**\n * Color mode: \"light\", \"dark\", or \"system\" (follows OS preference).\n * Can be a signal or getter for reactive mode switching.\n * @default \"light\"\n */\n mode?: ThemeModeInput | (() => ThemeModeInput) | undefined\n /** Flip mode for a nested section (e.g. dark sidebar in light app). */\n inversed?: boolean | undefined\n children?: VNodeChild\n}\n\n// ─── System mode detection ──────────────────────────────────────────────────\n\nconst _isBrowser = typeof window !== 'undefined' && typeof matchMedia === 'function'\n\n/** Reactive signal tracking the OS dark mode preference. Lazy-initialized on first use. */\nlet _systemMode: ReturnType<typeof signal<ThemeMode>> | undefined\n\nfunction getSystemMode(): ReturnType<typeof signal<ThemeMode>> {\n if (_systemMode) return _systemMode\n\n const prefersDark = _isBrowser && matchMedia('(prefers-color-scheme: dark)').matches\n _systemMode = signal<ThemeMode>(prefersDark ? 'dark' : 'light')\n\n if (_isBrowser) {\n matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {\n _systemMode?.set(e.matches ? 'dark' : 'light')\n })\n }\n\n return _systemMode\n}\n\n// ─── Mode context ───────────────────────────────────────────────────────────\n\n/** Reactive context — useContext(ModeContext) returns () => ThemeMode. */\nconst ModeContext = createReactiveContext<ThemeMode>('light')\n\nconst INVERSED: Record<ThemeMode, ThemeMode> = { light: 'dark', dark: 'light' }\n\n/**\n * Read the resolved color mode (\"light\" | \"dark\") from the nearest PyreonUI.\n * Reactive — updates when the mode prop changes or when OS preference changes\n * (if mode=\"system\").\n *\n * @example\n * const mode = useMode() // \"light\" | \"dark\"\n */\nexport function useMode(): ThemeMode {\n return useContext(ModeContext)()\n}\n\n// ─── Auto-init ──────────────────────────────────────────────────────────────\n\nlet _autoInitDone = false\n\nfunction autoInit(): void {\n if (_autoInitDone) return\n _autoInitDone = true\n}\n\n// ─── PyreonUI ───────────────────────────────────────────────────────────────\n\n/**\n * Unified provider for the Pyreon UI system.\n *\n * Replaces the need for separate UnistyleProvider, RocketstyleProvider,\n * and ThemeProvider — one component, zero init.\n *\n * Mode can be a static string OR a signal/getter for reactive switching:\n * ```tsx\n * // Static\n * <PyreonUI theme={theme} mode=\"dark\">\n *\n * // Reactive signal\n * const mode = signal<ThemeModeInput>(\"light\")\n * <PyreonUI theme={theme} mode={mode}>\n *\n * // System (follows OS preference)\n * <PyreonUI theme={theme} mode=\"system\">\n * ```\n */\nexport function PyreonUI(props: PyreonUIProps): VNodeChild {\n autoInit()\n\n // IMPORTANT: do NOT destructure props. Components run once in Pyreon, and\n // destructuring captures values at setup time — losing reactivity. Reading\n // `props.mode` / `props.inversed` lazily inside `resolveMode()` lets the\n // computed re-evaluate when the underlying reactive props change (parent\n // re-renders with a different value, or signal-driven mode toggling).\n //\n // Previously this destructured `{ theme, mode = 'light', inversed, children }`\n // which made `inversed` permanently static — toggling inversed in a parent\n // had no effect because the local boolean was captured once. See\n // `.claude/rules/anti-patterns.md` \"Destructuring props\" entry.\n\n // Create a reactive mode getter that resolves \"system\" and applies inversion.\n // This getter is provided via context — consumers read it lazily in their\n // own reactive scopes, so mode changes propagate automatically.\n const resolveMode = (): ThemeMode => {\n const mode = props.mode ?? 'light'\n const raw = typeof mode === 'function' ? mode() : mode\n const resolved = raw === 'system' ? getSystemMode()() : raw\n return props.inversed ? INVERSED[resolved] : resolved\n }\n\n // Wrap in computed for memoization\n const modeComputed = computed(resolveMode)\n\n // Enrich theme with responsive utilities (__PYREON__).\n // Theme is still captured once at setup — themes are typically stable\n // objects defined at module load. If the user wants to swap the theme\n // object dynamically, that's a future enhancement (would need a\n // computed wrapper here too).\n const enrichedTheme = enrichTheme(props.theme)\n\n // Provide to all three context layers:\n\n // 1. Styler ThemeContext — for styled() components and useTheme()\n provide(ThemeContext, enrichedTheme)\n\n // 2. Core context — provide a reactive getter function.\n // coreContext is a ReactiveContext, so provide(() => value).\n // Rocketstyle reads mode/isDark/isLight by calling the getter.\n provide(coreContext, () => ({\n theme: enrichedTheme,\n mode: modeComputed(),\n isDark: modeComputed() === 'dark',\n isLight: modeComputed() === 'light',\n }))\n\n // 3. Mode context — getter function for useMode()\n provide(ModeContext, () => modeComputed())\n\n return props.children ?? null\n}\n","import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'\nimport { h } from '@pyreon/core'\n\ntype RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild\n\n/**\n * Flexible element renderer that handles multiple content types:\n * - Primitives (string, number) — returned as-is\n * - Arrays — returned as-is\n * - Component functions — created via h()\n * - VNode objects — returned as-is (with props merged if provided)\n * - Render props (functions) — called with attachProps\n * - Falsy values — return null\n */\nexport type Render = <T extends Record<string, any> | undefined>(\n content?: ComponentFn | string | VNodeChild | VNodeChild[] | RenderProps<T>,\n attachProps?: T,\n) => VNodeChild\n\nconst render: Render = (content, attachProps) => {\n if (!content) return null\n\n const t = typeof content\n if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') {\n return content as VNodeChild\n }\n\n if (Array.isArray(content)) {\n return content as VNodeChild\n }\n\n if (typeof content === 'function') {\n // Extract key from props — it's a VNode concept, not a component prop.\n // Passing key inside props causes JSX runtime warnings.\n if (attachProps && 'key' in attachProps) {\n const { key, ...rest } = attachProps\n return h(content as string | ComponentFn, rest as Props)\n }\n return h(content as string | ComponentFn, (attachProps ?? {}) as Props)\n }\n\n // VNode object — return directly\n if (typeof content === 'object') {\n return content as VNodeChild\n }\n\n return content as VNodeChild\n}\n\nexport default render\n","import { signal } from '@pyreon/reactivity'\nimport isEqual from './isEqual'\n\n/**\n * Returns a referentially stable version of `value`. The returned reference\n * only changes when the value is no longer deeply equal to the previous one.\n *\n * Pyreon equivalent of the React useStableValue hook — uses a signal\n * internally to hold the stable reference.\n */\nconst useStableValue = <T>(value: T): T => {\n const ref = signal(value)\n\n if (!isEqual(ref.peek(), value)) {\n ref.set(value)\n }\n\n return ref.peek()\n}\n\nexport default useStableValue\n","export const omit = <T extends Record<string, any>>(\n obj: T | null | undefined,\n keys?: readonly (string | keyof T)[],\n): Partial<T> => {\n if (obj == null) return {} as Partial<T>\n if (!keys || keys.length === 0) return { ...obj }\n const result: Record<string, any> = {}\n const keysSet = new Set(keys as readonly string[])\n for (const key in obj) {\n if (Object.hasOwn(obj, key) && !keysSet.has(key)) {\n result[key] = obj[key]\n }\n }\n return result as Partial<T>\n}\n\nexport const pick = <T extends Record<string, any>>(\n obj: T | null | undefined,\n keys?: readonly (string | keyof T)[],\n): Partial<T> => {\n if (obj == null) return {} as Partial<T>\n if (!keys || keys.length === 0) return { ...obj }\n const result: Record<string, any> = {}\n for (const key of keys) {\n const k = key as string\n if (Object.hasOwn(obj, k)) {\n result[k] = obj[k]\n }\n }\n return result as Partial<T>\n}\n\nconst PATH_RE = /[^.[\\]]+/g\n\nconst parsePath = (path: string | string[]): string[] => {\n if (Array.isArray(path)) return path\n const parts = path.match(PATH_RE)\n return parts ?? []\n}\n\nconst isUnsafeKey = (key: string): boolean =>\n key === '__proto__' || key === 'prototype' || key === 'constructor'\n\nexport const get = (obj: any, path: string | string[], defaultValue?: any): any => {\n const keys = parsePath(path)\n let result = obj\n for (const key of keys) {\n if (result == null || isUnsafeKey(key)) return defaultValue\n result = result[key]\n }\n return result === undefined ? defaultValue : result\n}\n\nexport const set = (\n obj: Record<string, any>,\n path: string | string[],\n value: any,\n): Record<string, any> => {\n const keys = parsePath(path)\n let current = obj\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i] as string\n if (isUnsafeKey(key)) return obj\n const nextKey = keys[i + 1] as string\n if (isUnsafeKey(nextKey)) return obj\n if (current[key] == null) {\n current[key] = /^\\d+$/.test(nextKey) ? [] : {}\n }\n current = current[key]\n }\n const lastKey = keys[keys.length - 1]\n if (lastKey != null && !isUnsafeKey(lastKey)) {\n current[lastKey] = value\n }\n return obj\n}\n\nexport const throttle = <T extends (...args: any[]) => any>(\n fn: T,\n wait: number = 0,\n options?: { leading?: boolean; trailing?: boolean },\n): T & { cancel: () => void } => {\n const leading = options?.leading !== false\n const trailing = options?.trailing !== false\n let lastCallTime: number | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n let lastArgs: any[] | undefined\n\n const invoke = (args: any[]) => {\n lastCallTime = Date.now()\n fn(...args)\n }\n\n const startTrailingTimer = (args: any[], delay: number) => {\n lastArgs = args\n if (timeoutId !== undefined) return\n timeoutId = setTimeout(() => {\n timeoutId = undefined\n if (lastArgs) {\n invoke(lastArgs)\n lastArgs = undefined\n }\n }, delay)\n }\n\n const throttled = (...args: any[]) => {\n const now = Date.now()\n const elapsed = lastCallTime === undefined ? wait : now - lastCallTime\n if (elapsed >= wait) {\n if (leading) {\n invoke(args)\n } else {\n lastCallTime = now\n if (trailing) startTrailingTimer(args, wait)\n }\n } else if (trailing) {\n startTrailingTimer(args, wait - elapsed)\n }\n }\n\n throttled.cancel = () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n lastArgs = undefined\n lastCallTime = undefined\n }\n\n return throttled as T & { cancel: () => void }\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, any> =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n\nexport const merge = <T extends Record<string, any>>(\n target: T,\n ...sources: Record<string, any>[]\n): T => {\n for (const source of sources) {\n if (source == null) continue\n for (const key of Object.keys(source)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue\n const targetVal = (target as Record<string, unknown>)[key]\n const sourceVal = source[key]\n if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {\n ;(target as Record<string, unknown>)[key] = merge({ ...targetVal }, sourceVal)\n } else {\n ;(target as Record<string, unknown>)[key] = sourceVal\n }\n }\n }\n return target\n}\n"],"mappings":";;;;;;AAKA,MAAM,WACqB,GAAG,SAC3B,MACC,IAAI,aAAa,KAAU,QAAa,IAAI,IAAI,EAAE,EAAE;;;;;;;;;;;AC0BxD,IAAM,gBAAN,MAAoB;CAClB,MAAM;CACN,SAAyB;CACzB,YAAY;CACZ,YAA+B;CAC/B,gBAAmC;CACnC,qBAA2D;CAE3D,QAAQ,UAAsB;AAC5B,MAAI,MAAM,IAAK,MAAK,MAAM,MAAM;AAChC,MAAI,MAAM,OAAQ,MAAK,SAAS,MAAM;AACtC,MAAI,MAAM,UAAW,MAAK,YAAY,MAAM;AAC5C,MAAI,MAAM,UAAW,MAAK,YAAY,MAAM;AAC5C,MAAI,MAAM,cAAe,MAAK,gBAAgB,MAAM;AACpD,MAAI,MAAM,mBAAoB,MAAK,qBAAqB,MAAM;;;AAIlE,MAAM,SAAS,IAAI,eAAe;AAClC,MAAM,EAAE,SAAS;;;;ACzCjB,MAAM,YAA+E,UAAa;AAChG,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,WAAW;AAClD,QAAO,OAAO,KAAK,MAAM,CAAC,WAAW;;;;;;;;;;;ACKvC,MAAM,UAAU,sBAAwC;CACtD,OAAO,EAAE;CACT,MAAM;CACN,QAAQ;CACR,SAAS;CACV,CAAC;;;;;;;;;AAwBF,MAAM,UAAU,OAAO,YAAY,eAAe,SAAS,KAAK,aAAa;AAE7E,SAAS,SAAS,EAAE,OAAO,UAAU,GAAG,SAAmC;AACzE,KAAI,QAEF,SAAQ,KACN,iJACD;AAEH,KAAI,QAAQ,MAAM,IAAI,CAAC,MAAO,QAAO,YAAY;AAEjD,SAAQ,gBAAgB;EACf;EACP,MAAO,MAAM,QAA6B;EAC1C,QAAQ,MAAM,UAAqB;EACnC,SAAS,MAAM,WAAsB;EACrC,GAAG;EACJ,EAAE;AAEH,QAAO,YAAY;;;;;ACrErB,MAAM,gBAAsC;CAC1C,MAAM;CACN,QAAQ;CACR,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,OAAO;CACR;AAED,MAAM,oBAA0C;CAC9C,aAAa;CACb,cAAc;CACf;;;;;;;;AASD,MAAM,wBACJ,QACA,QACA,gBACM;AACN,KAAI,OAAO,WAAW,SAAU,QAAO;CAEvC,MAAM,QAAQ,OAAO,eAAe,OAAO;AAC3C,KAAI,SAAS,UAAU,OAAO,UAC5B,sBAAqB,QAAQ,OAAO,YAAY;CAGlD,MAAM,OAA4B,CAChC,GAAG,OAAO,oBAAoB,OAAO,EACrC,GAAG,OAAO,sBAAsB,OAAO,CACxC;AAED,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,IAAI;AACV,MAAI,cAAc,MAAM,cAAc,MAAM,kBAAkB,GAC5D;EAGF,MAAM,aAAa,OAAO,yBAAyB,QAAQ,IAAI;AAC/D,MAAI,WACF,KAAI;AACF,UAAO,eAAe,QAAQ,KAAK,WAAW;UACxC;;AAMZ,QAAO;;;;;ACvDT,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AClJD,MAAM,gBAAgB,GAAc,MAA0B;AAC5D,KAAI,EAAE,WAAW,EAA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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["coreContext"],"sources":["../src/compose.ts","../src/config.ts","../src/isEmpty.ts","../src/context.tsx","../src/hoistNonReactStatics.ts","../src/html/htmlTags.ts","../src/isEqual.ts","../src/PyreonUI.tsx","../src/render.tsx","../src/useStableValue.ts","../src/utils.ts"],"sourcesContent":["type ArityOneFn = (arg: any) => any\ntype PickLastInTuple<T extends any[]> = T extends [...rest: infer _U, argn: infer L] ? L : any\ntype FirstFnParameterType<T extends any[]> = Parameters<PickLastInTuple<T>>[any]\ntype LastFnReturnType<T extends any[]> = ReturnType<T[0]>\n\nconst compose =\n <T extends ArityOneFn[]>(...fns: T) =>\n (p: FirstFnParameterType<T>): LastFnReturnType<T> =>\n fns.reduceRight((acc: any, cur: any) => cur(acc), p)\n\nexport default compose\n","import type { StyledFunction } from '@pyreon/styler'\nimport { css, keyframes, styled } from '@pyreon/styler'\nimport type { HTMLTags } from './html'\n\n/**\n * Describes the shape of the CSS-in-JS engine.\n * Pyreon uses @pyreon/styler directly — no connector abstraction needed.\n * This type is kept for API compatibility with downstream packages.\n */\nexport interface CSSEngineConnector {\n css: typeof css\n styled: typeof styled\n keyframes: typeof keyframes\n}\n\ninterface PlatformConfig {\n component: string | HTMLTags\n textComponent: string | HTMLTags\n createMediaQueries?: (props: {\n breakpoints: Record<string, number>\n rootSize: number\n css: CSSEngineConnector['css']\n }) => Record<string, (...args: any[]) => any>\n}\n\ntype InitConfig = Partial<CSSEngineConnector & PlatformConfig>\n\n/**\n * Configuration singleton that bridges the UI system with the CSS engine.\n * All packages reference config.css, config.styled, etc.\n *\n * In Pyreon, the engine is @pyreon/styler and is available immediately —\n * no lazy initialization or connector pattern needed.\n */\nclass Configuration {\n css = css\n styled: StyledFunction = styled\n keyframes = keyframes\n component: string | HTMLTags = 'div'\n textComponent: string | HTMLTags = 'span'\n createMediaQueries: PlatformConfig['createMediaQueries'] = undefined\n\n init = (props: InitConfig) => {\n if (props.css) this.css = props.css\n if (props.styled) this.styled = props.styled\n if (props.keyframes) this.keyframes = props.keyframes\n if (props.component) this.component = props.component\n if (props.textComponent) this.textComponent = props.textComponent\n if (props.createMediaQueries) this.createMediaQueries = props.createMediaQueries\n }\n}\n\nconst config = new Configuration()\nconst { init } = config\n\nexport default config\nexport { init }\n","export type IsEmpty = <T extends Record<number | string, any> | any[] | null | undefined>(\n param: T,\n) => T extends null | undefined\n ? true\n : keyof T extends never\n ? true\n : T extends T[]\n ? T[number] extends never\n ? true\n : false\n : false\n\nconst isEmpty = (<T extends Record<number | string, any> | any[] | null | undefined>(param: T) => {\n if (!param) return true\n if (typeof param !== 'object') return true\n if (Array.isArray(param)) return param.length === 0\n return Object.keys(param).length === 0\n}) as IsEmpty\n\nexport default isEmpty\n","import type { VNodeChild } from '@pyreon/core'\nimport { createReactiveContext, provide } from '@pyreon/core'\nimport isEmpty from './isEmpty'\nimport type { Breakpoints } from './types'\n\n/**\n * Core context value shared across all @pyreon UI packages.\n */\nexport interface CoreContextValue {\n theme: Record<string, unknown>\n mode: 'light' | 'dark'\n isDark: boolean\n isLight: boolean\n}\n\n/**\n * Internal reactive context shared across all @pyreon packages.\n * Carries the theme object, mode, and derived dark/light flags.\n *\n * ReactiveContext means useContext() returns `() => CoreContextValue`.\n */\nconst context = createReactiveContext<CoreContextValue>({\n theme: {},\n mode: 'light',\n isDark: false,\n isLight: true,\n})\n\ntype Theme = Partial<\n {\n rootSize: number\n breakpoints: Breakpoints\n } & Record<string, any>\n>\n\ntype ProviderType = Partial<\n {\n theme: Theme\n children: VNodeChild\n } & Record<string, any>\n>\n\n/**\n * @internal Low-level provider — use `PyreonUI` from `@pyreon/ui-core` instead.\n *\n * Provider that feeds the internal Pyreon context with the theme.\n * When no theme is supplied, renders children directly.\n *\n * @deprecated Prefer `<PyreonUI theme={theme}>` which handles all context layers.\n */\nconst __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'\n\nfunction Provider({ theme, children, ...props }: ProviderType): VNodeChild {\n if (__DEV__) {\n // oxlint-disable-next-line no-console\n console.warn(\n '[Pyreon] CoreProvider is internal. Use <PyreonUI theme={theme}> instead — it handles all context layers (styler, core, mode) in one component.',\n )\n }\n if (isEmpty(theme) || !theme) return children ?? null\n\n provide(context, () => ({\n theme: theme as Record<string, unknown>,\n mode: (props.mode as 'light' | 'dark') ?? 'light',\n isDark: props.isDark as boolean ?? false,\n isLight: props.isLight as boolean ?? true,\n ...props,\n }))\n\n return children ?? null\n}\n\nexport { context }\n\nexport default Provider\n","const KNOWN_STATICS: Record<string, true> = {\n name: true,\n length: true,\n prototype: true,\n caller: true,\n callee: true,\n arguments: true,\n arity: true,\n}\n\nconst COMPONENT_STATICS: Record<string, true> = {\n displayName: true,\n defaultProps: true,\n}\n\n/**\n * Copies non-framework static properties from a source component to a target.\n *\n * Pyreon equivalent of hoistNonReactStatics — simplified since Pyreon\n * components are plain functions without React-specific statics like\n * contextType, propTypes, getDerivedStateFromProps, etc.\n */\nconst hoistNonReactStatics = <T, S>(\n target: T,\n source: S,\n excludeList?: Record<string, true>,\n): T => {\n if (typeof source === 'string') return target\n\n const proto = Object.getPrototypeOf(source)\n if (proto && proto !== Object.prototype) {\n hoistNonReactStatics(target, proto, excludeList)\n }\n\n const keys: (string | symbol)[] = [\n ...Object.getOwnPropertyNames(source),\n ...Object.getOwnPropertySymbols(source),\n ]\n\n for (const key of keys) {\n const k = key as string\n if (KNOWN_STATICS[k] || excludeList?.[k] || COMPONENT_STATICS[k]) {\n continue\n }\n\n const descriptor = Object.getOwnPropertyDescriptor(source, key)\n if (descriptor) {\n try {\n Object.defineProperty(target, key, descriptor)\n } catch {\n // Silently skip non-configurable properties\n }\n }\n }\n\n return target\n}\n\nexport default hoistNonReactStatics\n","const HTML_TAGS = [\n 'a',\n 'abbr',\n 'address',\n 'area',\n 'article',\n 'aside',\n 'audio',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blockquote',\n 'body',\n 'br',\n 'button',\n 'canvas',\n 'caption',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'data',\n 'datalist',\n 'dd',\n 'del',\n 'details',\n 'dfn',\n 'dialog',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'embed',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hr',\n 'html',\n 'i',\n 'iframe',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'map',\n 'mark',\n 'meter',\n 'nav',\n 'object',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'section',\n 'select',\n 'small',\n 'source',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'svg',\n 'table',\n 'tbody',\n 'td',\n 'template',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'track',\n 'u',\n 'ul',\n 'var',\n 'video',\n 'wbr',\n] as const\n\nconst HTML_TEXT_TAGS = [\n 'abbr',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blockquote',\n 'cite',\n 'code',\n 'del',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'figcaption',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'i',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'p',\n 'pre',\n 'q',\n 'rp',\n 'rt',\n 's',\n 'small',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'time',\n 'u',\n] as const\n\nexport type HTMLTags = (typeof HTML_TAGS)[number]\nexport type HTMLTextTags = (typeof HTML_TEXT_TAGS)[number]\nexport { HTML_TAGS, HTML_TEXT_TAGS }\n","const isArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!isEqual(a[i], b[i])) return false\n }\n return true\n}\n\nconst isObjectEqual = (a: Record<string, unknown>, b: Record<string, unknown>): boolean => {\n const aKeys = Object.keys(a)\n if (aKeys.length !== Object.keys(b).length) return false\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false\n if (!isEqual(a[key], b[key])) return false\n }\n return true\n}\n\nconst isEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true\n if (typeof a !== typeof b || a == null || b == null || typeof a !== 'object') return false\n if (Array.isArray(a)) return Array.isArray(b) && isArrayEqual(a, b)\n if (Array.isArray(b)) return false\n return isObjectEqual(a as Record<string, unknown>, b as Record<string, unknown>)\n}\n\nexport default isEqual\n","import type { VNodeChild } from '@pyreon/core'\nimport { createReactiveContext, provide, useContext } from '@pyreon/core'\nimport { computed, signal } from '@pyreon/reactivity'\nimport { ThemeContext } from '@pyreon/styler'\nimport type { PyreonTheme } from '@pyreon/unistyle'\nimport { enrichTheme } from '@pyreon/unistyle'\nimport { context as coreContext } from './context'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type ThemeMode = 'light' | 'dark'\nexport type ThemeModeInput = ThemeMode | 'system'\n\nexport interface PyreonUIProps {\n /** Theme object with breakpoints, rootSize, and custom keys. */\n theme: PyreonTheme\n /**\n * Color mode: \"light\", \"dark\", or \"system\" (follows OS preference).\n * Can be a signal or getter for reactive mode switching.\n * @default \"light\"\n */\n mode?: ThemeModeInput | (() => ThemeModeInput) | undefined\n /** Flip mode for a nested section (e.g. dark sidebar in light app). */\n inversed?: boolean | undefined\n children?: VNodeChild\n}\n\n// ─── System mode detection ──────────────────────────────────────────────────\n\nconst _isBrowser = typeof window !== 'undefined' && typeof matchMedia === 'function'\n\n/** Reactive signal tracking the OS dark mode preference. Lazy-initialized on first use. */\nlet _systemMode: ReturnType<typeof signal<ThemeMode>> | undefined\n\nfunction getSystemMode(): ReturnType<typeof signal<ThemeMode>> {\n if (_systemMode) return _systemMode\n\n const prefersDark = _isBrowser && matchMedia('(prefers-color-scheme: dark)').matches\n _systemMode = signal<ThemeMode>(prefersDark ? 'dark' : 'light')\n\n if (_isBrowser) {\n matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {\n _systemMode?.set(e.matches ? 'dark' : 'light')\n })\n }\n\n return _systemMode\n}\n\n// ─── Mode context ───────────────────────────────────────────────────────────\n\n/** Reactive context — useContext(ModeContext) returns () => ThemeMode. */\nconst ModeContext = createReactiveContext<ThemeMode>('light')\n\nconst INVERSED: Record<ThemeMode, ThemeMode> = { light: 'dark', dark: 'light' }\n\n/**\n * Read the resolved color mode (\"light\" | \"dark\") from the nearest PyreonUI.\n * Reactive — updates when the mode prop changes or when OS preference changes\n * (if mode=\"system\").\n *\n * @example\n * const mode = useMode() // \"light\" | \"dark\"\n */\nexport function useMode(): ThemeMode {\n return useContext(ModeContext)()\n}\n\n// ─── Auto-init ──────────────────────────────────────────────────────────────\n\nlet _autoInitDone = false\n\nfunction autoInit(): void {\n if (_autoInitDone) return\n _autoInitDone = true\n}\n\n// ─── PyreonUI ───────────────────────────────────────────────────────────────\n\n/**\n * Unified provider for the Pyreon UI system.\n *\n * Replaces the need for separate UnistyleProvider, RocketstyleProvider,\n * and ThemeProvider — one component, zero init.\n *\n * Mode can be a static string OR a signal/getter for reactive switching:\n * ```tsx\n * // Static\n * <PyreonUI theme={theme} mode=\"dark\">\n *\n * // Reactive signal\n * const mode = signal<ThemeModeInput>(\"light\")\n * <PyreonUI theme={theme} mode={mode}>\n *\n * // System (follows OS preference)\n * <PyreonUI theme={theme} mode=\"system\">\n * ```\n */\nexport function PyreonUI(props: PyreonUIProps): VNodeChild {\n autoInit()\n\n // IMPORTANT: do NOT destructure props. Components run once in Pyreon, and\n // destructuring captures values at setup time — losing reactivity. Reading\n // `props.mode` / `props.inversed` lazily inside `resolveMode()` lets the\n // computed re-evaluate when the underlying reactive props change (parent\n // re-renders with a different value, or signal-driven mode toggling).\n //\n // Previously this destructured `{ theme, mode = 'light', inversed, children }`\n // which made `inversed` permanently static — toggling inversed in a parent\n // had no effect because the local boolean was captured once. See\n // `.claude/rules/anti-patterns.md` \"Destructuring props\" entry.\n\n // Create a reactive mode getter that resolves \"system\" and applies inversion.\n // This getter is provided via context — consumers read it lazily in their\n // own reactive scopes, so mode changes propagate automatically.\n //\n // When `inversed` is set without an explicit `mode`, inherit the parent's\n // mode and flip it. This makes nested `<PyreonUI inversed>` work reactively:\n // outer light → inner dark, outer dark → inner light.\n //\n // useContext(ModeContext) returns the reactive accessor from the nearest\n // parent PyreonUI. At root level (no parent), the ReactiveContext default\n // returns 'light'. This read is TRACKED inside the computed below, so when\n // the parent's mode changes, this child's computed re-evaluates.\n const parentModeAccessor = useContext(ModeContext)\n const resolveMode = (): ThemeMode => {\n const mode = props.mode\n let resolved: ThemeMode\n if (mode === undefined || mode === null) {\n // No explicit mode — inherit from parent context\n resolved = parentModeAccessor()\n } else {\n const raw = typeof mode === 'function' ? mode() : mode\n resolved = raw === 'system' ? getSystemMode()() : raw\n }\n return props.inversed ? INVERSED[resolved] : resolved\n }\n\n // Wrap in computed for memoization\n const modeComputed = computed(resolveMode)\n\n // Enrich theme — wrapped in computed so user-preference theme swaps\n // propagate. The enrichment itself is cheap (builds a __PYREON__ block).\n const enrichedTheme = computed(() => enrichTheme(props.theme))\n\n // Provide to all three context layers:\n\n // 1. Styler ThemeContext — reactive accessor. DynamicStyled reads this\n // inside its computed() to re-resolve CSS on theme swap.\n provide(ThemeContext, () => enrichedTheme())\n\n // 2. Core context — provide a reactive getter function.\n // coreContext is a ReactiveContext, so provide(() => value).\n // Rocketstyle reads mode/isDark/isLight by calling the getter.\n provide(coreContext, () => ({\n theme: enrichedTheme(),\n mode: modeComputed(),\n isDark: modeComputed() === 'dark',\n isLight: modeComputed() === 'light',\n }))\n\n // 3. Mode context — getter function for useMode()\n provide(ModeContext, () => modeComputed())\n\n return props.children ?? null\n}\n","import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'\nimport { h } from '@pyreon/core'\n\ntype RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild\n\n/**\n * Flexible element renderer that handles multiple content types:\n * - Primitives (string, number) — returned as-is\n * - Arrays — returned as-is\n * - Component functions — created via h()\n * - VNode objects — returned as-is (with props merged if provided)\n * - Render props (functions) — called with attachProps\n * - Falsy values — return null\n */\nexport type Render = <T extends Record<string, any> | undefined>(\n content?: ComponentFn | string | VNodeChild | VNodeChild[] | RenderProps<T>,\n attachProps?: T,\n) => VNodeChild\n\nconst render: Render = (content, attachProps) => {\n if (!content) return null\n\n const t = typeof content\n if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') {\n return content as VNodeChild\n }\n\n if (Array.isArray(content)) {\n return content as VNodeChild\n }\n\n if (typeof content === 'function') {\n // Extract key from props — it's a VNode concept, not a component prop.\n // Passing key inside props causes JSX runtime warnings.\n if (attachProps && 'key' in attachProps) {\n const { key, ...rest } = attachProps\n return h(content as string | ComponentFn, rest as Props)\n }\n return h(content as string | ComponentFn, (attachProps ?? {}) as Props)\n }\n\n // VNode object — return directly\n if (typeof content === 'object') {\n return content as VNodeChild\n }\n\n return content as VNodeChild\n}\n\nexport default render\n","import { signal } from '@pyreon/reactivity'\nimport isEqual from './isEqual'\n\n/**\n * Returns a referentially stable version of `value`. The returned reference\n * only changes when the value is no longer deeply equal to the previous one.\n *\n * Pyreon equivalent of the React useStableValue hook — uses a signal\n * internally to hold the stable reference.\n */\nconst useStableValue = <T>(value: T): T => {\n const ref = signal(value)\n\n if (!isEqual(ref.peek(), value)) {\n ref.set(value)\n }\n\n return ref.peek()\n}\n\nexport default useStableValue\n","export const omit = <T extends Record<string, any>>(\n obj: T | null | undefined,\n keys?: readonly (string | keyof T)[],\n): Partial<T> => {\n if (obj == null) return {} as Partial<T>\n if (!keys || keys.length === 0) return { ...obj }\n const result: Record<string, any> = {}\n const keysSet = new Set(keys as readonly string[])\n for (const key in obj) {\n if (Object.hasOwn(obj, key) && !keysSet.has(key)) {\n result[key] = obj[key]\n }\n }\n return result as Partial<T>\n}\n\nexport const pick = <T extends Record<string, any>>(\n obj: T | null | undefined,\n keys?: readonly (string | keyof T)[],\n): Partial<T> => {\n if (obj == null) return {} as Partial<T>\n if (!keys || keys.length === 0) return { ...obj }\n const result: Record<string, any> = {}\n for (const key of keys) {\n const k = key as string\n if (Object.hasOwn(obj, k)) {\n result[k] = obj[k]\n }\n }\n return result as Partial<T>\n}\n\nconst PATH_RE = /[^.[\\]]+/g\n\nconst parsePath = (path: string | string[]): string[] => {\n if (Array.isArray(path)) return path\n const parts = path.match(PATH_RE)\n return parts ?? []\n}\n\nconst isUnsafeKey = (key: string): boolean =>\n key === '__proto__' || key === 'prototype' || key === 'constructor'\n\nexport const get = (obj: any, path: string | string[], defaultValue?: any): any => {\n const keys = parsePath(path)\n let result = obj\n for (const key of keys) {\n if (result == null || isUnsafeKey(key)) return defaultValue\n result = result[key]\n }\n return result === undefined ? defaultValue : result\n}\n\nexport const set = (\n obj: Record<string, any>,\n path: string | string[],\n value: any,\n): Record<string, any> => {\n const keys = parsePath(path)\n let current = obj\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i] as string\n if (isUnsafeKey(key)) return obj\n const nextKey = keys[i + 1] as string\n if (isUnsafeKey(nextKey)) return obj\n if (current[key] == null) {\n current[key] = /^\\d+$/.test(nextKey) ? [] : {}\n }\n current = current[key]\n }\n const lastKey = keys[keys.length - 1]\n if (lastKey != null && !isUnsafeKey(lastKey)) {\n current[lastKey] = value\n }\n return obj\n}\n\nexport const throttle = <T extends (...args: any[]) => any>(\n fn: T,\n wait: number = 0,\n options?: { leading?: boolean; trailing?: boolean },\n): T & { cancel: () => void } => {\n const leading = options?.leading !== false\n const trailing = options?.trailing !== false\n let lastCallTime: number | undefined\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n let lastArgs: any[] | undefined\n\n const invoke = (args: any[]) => {\n lastCallTime = Date.now()\n fn(...args)\n }\n\n const startTrailingTimer = (args: any[], delay: number) => {\n lastArgs = args\n if (timeoutId !== undefined) return\n timeoutId = setTimeout(() => {\n timeoutId = undefined\n if (lastArgs) {\n invoke(lastArgs)\n lastArgs = undefined\n }\n }, delay)\n }\n\n const throttled = (...args: any[]) => {\n const now = Date.now()\n const elapsed = lastCallTime === undefined ? wait : now - lastCallTime\n if (elapsed >= wait) {\n if (leading) {\n invoke(args)\n } else {\n lastCallTime = now\n if (trailing) startTrailingTimer(args, wait)\n }\n } else if (trailing) {\n startTrailingTimer(args, wait - elapsed)\n }\n }\n\n throttled.cancel = () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n lastArgs = undefined\n lastCallTime = undefined\n }\n\n return throttled as T & { cancel: () => void }\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, any> =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n\nexport const merge = <T extends Record<string, any>>(\n target: T,\n ...sources: Record<string, any>[]\n): T => {\n for (const source of sources) {\n if (source == null) continue\n for (const key of Object.keys(source)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue\n const targetVal = (target as Record<string, unknown>)[key]\n const sourceVal = source[key]\n if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {\n ;(target as Record<string, unknown>)[key] = merge({ ...targetVal }, sourceVal)\n } else {\n ;(target as Record<string, unknown>)[key] = sourceVal\n }\n }\n }\n return target\n}\n"],"mappings":";;;;;;AAKA,MAAM,WACqB,GAAG,SAC3B,MACC,IAAI,aAAa,KAAU,QAAa,IAAI,IAAI,EAAE,EAAE;;;;;;;;;;;AC0BxD,IAAM,gBAAN,MAAoB;CAClB,MAAM;CACN,SAAyB;CACzB,YAAY;CACZ,YAA+B;CAC/B,gBAAmC;CACnC,qBAA2D;CAE3D,QAAQ,UAAsB;AAC5B,MAAI,MAAM,IAAK,MAAK,MAAM,MAAM;AAChC,MAAI,MAAM,OAAQ,MAAK,SAAS,MAAM;AACtC,MAAI,MAAM,UAAW,MAAK,YAAY,MAAM;AAC5C,MAAI,MAAM,UAAW,MAAK,YAAY,MAAM;AAC5C,MAAI,MAAM,cAAe,MAAK,gBAAgB,MAAM;AACpD,MAAI,MAAM,mBAAoB,MAAK,qBAAqB,MAAM;;;AAIlE,MAAM,SAAS,IAAI,eAAe;AAClC,MAAM,EAAE,SAAS;;;;ACzCjB,MAAM,YAA+E,UAAa;AAChG,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,WAAW;AAClD,QAAO,OAAO,KAAK,MAAM,CAAC,WAAW;;;;;;;;;;;ACKvC,MAAM,UAAU,sBAAwC;CACtD,OAAO,EAAE;CACT,MAAM;CACN,QAAQ;CACR,SAAS;CACV,CAAC;;;;;;;;;AAwBF,MAAM,UAAU,OAAO,YAAY,eAAe,SAAS,KAAK,aAAa;AAE7E,SAAS,SAAS,EAAE,OAAO,UAAU,GAAG,SAAmC;AACzE,KAAI,QAEF,SAAQ,KACN,iJACD;AAEH,KAAI,QAAQ,MAAM,IAAI,CAAC,MAAO,QAAO,YAAY;AAEjD,SAAQ,gBAAgB;EACf;EACP,MAAO,MAAM,QAA6B;EAC1C,QAAQ,MAAM,UAAqB;EACnC,SAAS,MAAM,WAAsB;EACrC,GAAG;EACJ,EAAE;AAEH,QAAO,YAAY;;;;;ACrErB,MAAM,gBAAsC;CAC1C,MAAM;CACN,QAAQ;CACR,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,OAAO;CACR;AAED,MAAM,oBAA0C;CAC9C,aAAa;CACb,cAAc;CACf;;;;;;;;AASD,MAAM,wBACJ,QACA,QACA,gBACM;AACN,KAAI,OAAO,WAAW,SAAU,QAAO;CAEvC,MAAM,QAAQ,OAAO,eAAe,OAAO;AAC3C,KAAI,SAAS,UAAU,OAAO,UAC5B,sBAAqB,QAAQ,OAAO,YAAY;CAGlD,MAAM,OAA4B,CAChC,GAAG,OAAO,oBAAoB,OAAO,EACrC,GAAG,OAAO,sBAAsB,OAAO,CACxC;AAED,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,IAAI;AACV,MAAI,cAAc,MAAM,cAAc,MAAM,kBAAkB,GAC5D;EAGF,MAAM,aAAa,OAAO,yBAAyB,QAAQ,IAAI;AAC/D,MAAI,WACF,KAAI;AACF,UAAO,eAAe,QAAQ,KAAK,WAAW;UACxC;;AAMZ,QAAO;;;;;ACvDT,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AClJD,MAAM,gBAAgB,GAAc,MAA0B;AAC5D,KAAI,EAAE,WAAW,EAA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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/ui-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Core utilities, config, and context for Pyreon UI System",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"typecheck": "tsc --noEmit"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@pyreon/typescript": "^0.
|
|
39
|
+
"@pyreon/typescript": "^0.13.0",
|
|
40
40
|
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@pyreon/core": "^0.
|
|
44
|
-
"@pyreon/reactivity": "^0.
|
|
45
|
-
"@pyreon/styler": "^0.
|
|
46
|
-
"@pyreon/unistyle": "^0.
|
|
43
|
+
"@pyreon/core": "^0.13.0",
|
|
44
|
+
"@pyreon/reactivity": "^0.13.0",
|
|
45
|
+
"@pyreon/styler": "^0.13.0",
|
|
46
|
+
"@pyreon/unistyle": "^0.13.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">= 22"
|
package/src/PyreonUI.tsx
CHANGED
|
@@ -113,33 +113,47 @@ export function PyreonUI(props: PyreonUIProps): VNodeChild {
|
|
|
113
113
|
// Create a reactive mode getter that resolves "system" and applies inversion.
|
|
114
114
|
// This getter is provided via context — consumers read it lazily in their
|
|
115
115
|
// own reactive scopes, so mode changes propagate automatically.
|
|
116
|
+
//
|
|
117
|
+
// When `inversed` is set without an explicit `mode`, inherit the parent's
|
|
118
|
+
// mode and flip it. This makes nested `<PyreonUI inversed>` work reactively:
|
|
119
|
+
// outer light → inner dark, outer dark → inner light.
|
|
120
|
+
//
|
|
121
|
+
// useContext(ModeContext) returns the reactive accessor from the nearest
|
|
122
|
+
// parent PyreonUI. At root level (no parent), the ReactiveContext default
|
|
123
|
+
// returns 'light'. This read is TRACKED inside the computed below, so when
|
|
124
|
+
// the parent's mode changes, this child's computed re-evaluates.
|
|
125
|
+
const parentModeAccessor = useContext(ModeContext)
|
|
116
126
|
const resolveMode = (): ThemeMode => {
|
|
117
|
-
const mode = props.mode
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
const mode = props.mode
|
|
128
|
+
let resolved: ThemeMode
|
|
129
|
+
if (mode === undefined || mode === null) {
|
|
130
|
+
// No explicit mode — inherit from parent context
|
|
131
|
+
resolved = parentModeAccessor()
|
|
132
|
+
} else {
|
|
133
|
+
const raw = typeof mode === 'function' ? mode() : mode
|
|
134
|
+
resolved = raw === 'system' ? getSystemMode()() : raw
|
|
135
|
+
}
|
|
120
136
|
return props.inversed ? INVERSED[resolved] : resolved
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
// Wrap in computed for memoization
|
|
124
140
|
const modeComputed = computed(resolveMode)
|
|
125
141
|
|
|
126
|
-
// Enrich theme
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
// object dynamically, that's a future enhancement (would need a
|
|
130
|
-
// computed wrapper here too).
|
|
131
|
-
const enrichedTheme = enrichTheme(props.theme)
|
|
142
|
+
// Enrich theme — wrapped in computed so user-preference theme swaps
|
|
143
|
+
// propagate. The enrichment itself is cheap (builds a __PYREON__ block).
|
|
144
|
+
const enrichedTheme = computed(() => enrichTheme(props.theme))
|
|
132
145
|
|
|
133
146
|
// Provide to all three context layers:
|
|
134
147
|
|
|
135
|
-
// 1. Styler ThemeContext —
|
|
136
|
-
|
|
148
|
+
// 1. Styler ThemeContext — reactive accessor. DynamicStyled reads this
|
|
149
|
+
// inside its computed() to re-resolve CSS on theme swap.
|
|
150
|
+
provide(ThemeContext, () => enrichedTheme())
|
|
137
151
|
|
|
138
152
|
// 2. Core context — provide a reactive getter function.
|
|
139
153
|
// coreContext is a ReactiveContext, so provide(() => value).
|
|
140
154
|
// Rocketstyle reads mode/isDark/isLight by calling the getter.
|
|
141
155
|
provide(coreContext, () => ({
|
|
142
|
-
theme: enrichedTheme,
|
|
156
|
+
theme: enrichedTheme(),
|
|
143
157
|
mode: modeComputed(),
|
|
144
158
|
isDark: modeComputed() === 'dark',
|
|
145
159
|
isLight: modeComputed() === 'light',
|
|
@@ -79,7 +79,9 @@ describe('PyreonUI', () => {
|
|
|
79
79
|
it('enriches theme with __PYREON__ before providing', () => {
|
|
80
80
|
PyreonUI({ theme, children: null })
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
// ThemeContext is reactive — the provided value is an accessor.
|
|
83
|
+
const providedThemeGetter = getProvideValue(0)
|
|
84
|
+
const providedTheme = providedThemeGetter()
|
|
83
85
|
expect(providedTheme.__PYREON__).toBeDefined()
|
|
84
86
|
expect(providedTheme.__PYREON__.sortedBreakpoints).toEqual(['xs', 'sm', 'md'])
|
|
85
87
|
expect(providedTheme.colors).toEqual({ primary: '#228be6' })
|