@pyreon/ui-core 0.11.4 → 0.11.6
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 +15 -13
- package/lib/index.d.ts +19 -12
- package/lib/index.js +29 -19
- package/package.json +24 -24
- package/src/PyreonUI.tsx +28 -34
- package/src/__tests__/PyreonUI.test.tsx +40 -37
- package/src/__tests__/compose.test.ts +8 -8
- package/src/__tests__/config.test.ts +37 -37
- package/src/__tests__/context.test.tsx +28 -27
- package/src/__tests__/hoistNonReactStatics.test.tsx +44 -44
- package/src/__tests__/isEmpty.test.ts +15 -15
- package/src/__tests__/isEqual.test.ts +28 -28
- package/src/__tests__/render.test.tsx +28 -28
- package/src/__tests__/useStableValue.test.ts +23 -23
- package/src/__tests__/utils.test.ts +149 -149
- package/src/config.ts +7 -7
- package/src/context.tsx +43 -8
- package/src/hoistNonReactStatics.ts +1 -1
- package/src/html/htmlTags.ts +142 -142
- package/src/html/index.ts +3 -3
- package/src/index.ts +19 -17
- package/src/isEmpty.ts +1 -1
- package/src/isEqual.ts +1 -1
- package/src/render.tsx +6 -6
- package/src/useStableValue.ts +2 -2
- package/src/utils.ts +3 -3
package/README.md
CHANGED
|
@@ -25,7 +25,9 @@ import { Provider } from '@pyreon/ui-core'
|
|
|
25
25
|
|
|
26
26
|
Provider({
|
|
27
27
|
theme: { rootSize: 16, breakpoints: { xs: 0, md: 768 } },
|
|
28
|
-
children: [
|
|
28
|
+
children: [
|
|
29
|
+
/* your app */
|
|
30
|
+
],
|
|
29
31
|
})
|
|
30
32
|
```
|
|
31
33
|
|
|
@@ -58,9 +60,9 @@ Flexible element renderer. Handles components, elements, primitives, and arrays.
|
|
|
58
60
|
```ts
|
|
59
61
|
import { render } from '@pyreon/ui-core'
|
|
60
62
|
|
|
61
|
-
render('hello')
|
|
62
|
-
render(MyComponent)
|
|
63
|
-
render(null)
|
|
63
|
+
render('hello') // => 'hello'
|
|
64
|
+
render(MyComponent) // => MyComponent({})
|
|
65
|
+
render(null) // => null
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
#### isEmpty
|
|
@@ -70,9 +72,9 @@ Type-safe emptiness check. Returns `true` for `null`, `undefined`, `{}`, `[]`, a
|
|
|
70
72
|
```ts
|
|
71
73
|
import { isEmpty } from '@pyreon/ui-core'
|
|
72
74
|
|
|
73
|
-
isEmpty({})
|
|
74
|
-
isEmpty([])
|
|
75
|
-
isEmpty(null)
|
|
75
|
+
isEmpty({}) // => true
|
|
76
|
+
isEmpty([]) // => true
|
|
77
|
+
isEmpty(null) // => true
|
|
76
78
|
isEmpty({ a: 1 }) // => false
|
|
77
79
|
```
|
|
78
80
|
|
|
@@ -83,7 +85,7 @@ Create objects without or with only specified keys. Accept nullable inputs.
|
|
|
83
85
|
```ts
|
|
84
86
|
import { omit, pick } from '@pyreon/ui-core'
|
|
85
87
|
|
|
86
|
-
omit({ a: 1, b: 2, c: 3 }, ['b'])
|
|
88
|
+
omit({ a: 1, b: 2, c: 3 }, ['b']) // => { a: 1, c: 3 }
|
|
87
89
|
pick({ a: 1, b: 2, c: 3 }, ['a', 'b']) // => { a: 1, b: 2 }
|
|
88
90
|
```
|
|
89
91
|
|
|
@@ -95,8 +97,8 @@ Nested property access and mutation by dot/bracket path. `set` has built-in prot
|
|
|
95
97
|
import { set, get } from '@pyreon/ui-core'
|
|
96
98
|
|
|
97
99
|
const obj = {}
|
|
98
|
-
set(obj, 'a.b.c', 42)
|
|
99
|
-
get(obj, 'a.b.c')
|
|
100
|
+
set(obj, 'a.b.c', 42) // => { a: { b: { c: 42 } } }
|
|
101
|
+
get(obj, 'a.b.c') // => 42
|
|
100
102
|
get(obj, 'a.x', 'default') // => 'default'
|
|
101
103
|
```
|
|
102
104
|
|
|
@@ -135,9 +137,9 @@ Both have corresponding TypeScript union types: `HTMLTags` and `HTMLTextTags`.
|
|
|
135
137
|
|
|
136
138
|
## Peer Dependencies
|
|
137
139
|
|
|
138
|
-
| Package
|
|
139
|
-
|
|
|
140
|
-
| @pyreon/core
|
|
140
|
+
| Package | Version |
|
|
141
|
+
| -------------- | -------- |
|
|
142
|
+
| @pyreon/core | >= 0.0.1 |
|
|
141
143
|
| @pyreon/styler | >= 0.0.1 |
|
|
142
144
|
|
|
143
145
|
## License
|
package/lib/index.d.ts
CHANGED
|
@@ -144,7 +144,7 @@ interface PlatformConfig {
|
|
|
144
144
|
createMediaQueries?: (props: {
|
|
145
145
|
breakpoints: Record<string, number>;
|
|
146
146
|
rootSize: number;
|
|
147
|
-
css: CSSEngineConnector[
|
|
147
|
+
css: CSSEngineConnector['css'];
|
|
148
148
|
}) => Record<string, (...args: any[]) => any>;
|
|
149
149
|
}
|
|
150
150
|
type InitConfig = Partial<CSSEngineConnector & PlatformConfig>;
|
|
@@ -164,7 +164,7 @@ declare class Configuration {
|
|
|
164
164
|
};
|
|
165
165
|
component: string | HTMLTags;
|
|
166
166
|
textComponent: string | HTMLTags;
|
|
167
|
-
createMediaQueries: PlatformConfig[
|
|
167
|
+
createMediaQueries: PlatformConfig['createMediaQueries'];
|
|
168
168
|
init: (props: InitConfig) => void;
|
|
169
169
|
}
|
|
170
170
|
declare const config: Configuration;
|
|
@@ -178,10 +178,21 @@ type BreakpointKeys = keyof Breakpoints;
|
|
|
178
178
|
//#endregion
|
|
179
179
|
//#region src/context.d.ts
|
|
180
180
|
/**
|
|
181
|
-
*
|
|
182
|
-
* Carries the theme object plus any extra provider props.
|
|
181
|
+
* Core context value shared across all @pyreon UI packages.
|
|
183
182
|
*/
|
|
184
|
-
|
|
183
|
+
interface CoreContextValue {
|
|
184
|
+
theme: Record<string, unknown>;
|
|
185
|
+
mode: 'light' | 'dark';
|
|
186
|
+
isDark: boolean;
|
|
187
|
+
isLight: boolean;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Internal reactive context shared across all @pyreon packages.
|
|
191
|
+
* Carries the theme object, mode, and derived dark/light flags.
|
|
192
|
+
*
|
|
193
|
+
* ReactiveContext means useContext() returns `() => CoreContextValue`.
|
|
194
|
+
*/
|
|
195
|
+
declare const context: _pyreon_core0.ReactiveContext<CoreContextValue>;
|
|
185
196
|
type Theme = Partial<{
|
|
186
197
|
rootSize: number;
|
|
187
198
|
breakpoints: Breakpoints;
|
|
@@ -190,10 +201,6 @@ type ProviderType = Partial<{
|
|
|
190
201
|
theme: Theme;
|
|
191
202
|
children: VNodeChild;
|
|
192
203
|
} & Record<string, any>>;
|
|
193
|
-
/**
|
|
194
|
-
* Provider that feeds the internal Pyreon context with the theme.
|
|
195
|
-
* When no theme is supplied, renders children directly.
|
|
196
|
-
*/
|
|
197
204
|
declare function Provider({
|
|
198
205
|
theme,
|
|
199
206
|
children,
|
|
@@ -218,8 +225,8 @@ declare const isEmpty: IsEmpty;
|
|
|
218
225
|
declare const isEqual: (a: unknown, b: unknown) => boolean;
|
|
219
226
|
//#endregion
|
|
220
227
|
//#region src/PyreonUI.d.ts
|
|
221
|
-
type ThemeMode =
|
|
222
|
-
type ThemeModeInput = ThemeMode |
|
|
228
|
+
type ThemeMode = 'light' | 'dark';
|
|
229
|
+
type ThemeModeInput = ThemeMode | 'system';
|
|
223
230
|
interface PyreonUIProps {
|
|
224
231
|
/** Theme object with breakpoints, rootSize, and custom keys. */
|
|
225
232
|
theme: PyreonTheme;
|
|
@@ -305,5 +312,5 @@ declare const throttle: <T extends (...args: any[]) => any>(fn: T, wait?: number
|
|
|
305
312
|
};
|
|
306
313
|
declare const merge: <T extends Record<string, any>>(target: T, ...sources: Record<string, any>[]) => T;
|
|
307
314
|
//#endregion
|
|
308
|
-
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 };
|
|
315
|
+
export { type BreakpointKeys, type Breakpoints, type CSSEngineConnector, type CoreContextValue, 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 };
|
|
309
316
|
//# sourceMappingURL=index2.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ThemeContext, css, keyframes, styled } from "@pyreon/styler";
|
|
2
|
-
import {
|
|
2
|
+
import { createReactiveContext, h, provide, useContext } from "@pyreon/core";
|
|
3
3
|
import { computed, signal } from "@pyreon/reactivity";
|
|
4
4
|
import { enrichTheme } from "@pyreon/unistyle";
|
|
5
5
|
|
|
@@ -46,20 +46,36 @@ const isEmpty = ((param) => {
|
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/context.tsx
|
|
48
48
|
/**
|
|
49
|
-
* Internal context shared across all @pyreon packages.
|
|
50
|
-
* Carries the theme object
|
|
49
|
+
* Internal reactive context shared across all @pyreon packages.
|
|
50
|
+
* Carries the theme object, mode, and derived dark/light flags.
|
|
51
|
+
*
|
|
52
|
+
* ReactiveContext means useContext() returns `() => CoreContextValue`.
|
|
51
53
|
*/
|
|
52
|
-
const context =
|
|
54
|
+
const context = createReactiveContext({
|
|
55
|
+
theme: {},
|
|
56
|
+
mode: "light",
|
|
57
|
+
isDark: false,
|
|
58
|
+
isLight: true
|
|
59
|
+
});
|
|
53
60
|
/**
|
|
61
|
+
* @internal Low-level provider — use `PyreonUI` from `@pyreon/ui-core` instead.
|
|
62
|
+
*
|
|
54
63
|
* Provider that feeds the internal Pyreon context with the theme.
|
|
55
64
|
* When no theme is supplied, renders children directly.
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Prefer `<PyreonUI theme={theme}>` which handles all context layers.
|
|
56
67
|
*/
|
|
68
|
+
const __DEV__ = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
|
|
57
69
|
function Provider({ theme, children, ...props }) {
|
|
70
|
+
if (__DEV__) console.warn("[Pyreon] CoreProvider is internal. Use <PyreonUI theme={theme}> instead — it handles all context layers (styler, core, mode) in one component.");
|
|
58
71
|
if (isEmpty(theme) || !theme) return children ?? null;
|
|
59
|
-
provide(context, {
|
|
72
|
+
provide(context, () => ({
|
|
60
73
|
theme,
|
|
74
|
+
mode: props.mode ?? "light",
|
|
75
|
+
isDark: props.isDark ?? false,
|
|
76
|
+
isLight: props.isLight ?? true,
|
|
61
77
|
...props
|
|
62
|
-
});
|
|
78
|
+
}));
|
|
63
79
|
return children ?? null;
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -287,8 +303,8 @@ function getSystemMode() {
|
|
|
287
303
|
});
|
|
288
304
|
return _systemMode;
|
|
289
305
|
}
|
|
290
|
-
/**
|
|
291
|
-
const ModeContext =
|
|
306
|
+
/** Reactive context — useContext(ModeContext) returns () => ThemeMode. */
|
|
307
|
+
const ModeContext = createReactiveContext("light");
|
|
292
308
|
const INVERSED = {
|
|
293
309
|
light: "dark",
|
|
294
310
|
dark: "light"
|
|
@@ -338,18 +354,12 @@ function PyreonUI({ theme, mode = "light", inversed, children }) {
|
|
|
338
354
|
const modeComputed = computed(resolveMode);
|
|
339
355
|
const enrichedTheme = enrichTheme(theme);
|
|
340
356
|
provide(ThemeContext, enrichedTheme);
|
|
341
|
-
provide(context, {
|
|
357
|
+
provide(context, () => ({
|
|
342
358
|
theme: enrichedTheme,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return modeComputed() === "dark";
|
|
348
|
-
},
|
|
349
|
-
get isLight() {
|
|
350
|
-
return modeComputed() === "light";
|
|
351
|
-
}
|
|
352
|
-
});
|
|
359
|
+
mode: modeComputed(),
|
|
360
|
+
isDark: modeComputed() === "dark",
|
|
361
|
+
isLight: modeComputed() === "light"
|
|
362
|
+
}));
|
|
353
363
|
provide(ModeContext, () => modeComputed());
|
|
354
364
|
return children ?? null;
|
|
355
365
|
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/ui-core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
|
+
"description": "Core utilities, config, and context for Pyreon UI System",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/pyreon/pyreon",
|
|
7
9
|
"directory": "packages/ui-system/ui-core"
|
|
8
10
|
},
|
|
9
|
-
"description": "Core utilities, config, and context for Pyreon UI System",
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"type": "module",
|
|
12
|
-
"sideEffects": false,
|
|
13
|
-
"exports": {
|
|
14
|
-
".": {
|
|
15
|
-
"bun": "./src/index.ts",
|
|
16
|
-
"import": "./lib/index.js",
|
|
17
|
-
"types": "./lib/index.d.ts"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"types": "./lib/index.d.ts",
|
|
21
|
-
"main": "./lib/index.js",
|
|
22
11
|
"files": [
|
|
23
12
|
"lib",
|
|
24
13
|
"!lib/**/*.map",
|
|
@@ -27,26 +16,37 @@
|
|
|
27
16
|
"LICENSE",
|
|
28
17
|
"src"
|
|
29
18
|
],
|
|
30
|
-
"
|
|
31
|
-
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"main": "./lib/index.js",
|
|
22
|
+
"types": "./lib/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"bun": "./src/index.ts",
|
|
26
|
+
"import": "./lib/index.js",
|
|
27
|
+
"types": "./lib/index.d.ts"
|
|
28
|
+
}
|
|
32
29
|
},
|
|
33
30
|
"publishConfig": {
|
|
34
31
|
"access": "public"
|
|
35
32
|
},
|
|
36
33
|
"scripts": {
|
|
37
34
|
"build": "bun run vl_rolldown_build",
|
|
38
|
-
"lint": "
|
|
35
|
+
"lint": "oxlint .",
|
|
39
36
|
"test": "vitest run",
|
|
40
37
|
"typecheck": "tsc --noEmit"
|
|
41
38
|
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@pyreon/typescript": "^0.11.6",
|
|
41
|
+
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
42
|
+
},
|
|
42
43
|
"peerDependencies": {
|
|
43
|
-
"@pyreon/core": "^0.11.
|
|
44
|
-
"@pyreon/
|
|
45
|
-
"@pyreon/
|
|
46
|
-
"@pyreon/unistyle": "^0.11.
|
|
44
|
+
"@pyreon/core": "^0.11.6",
|
|
45
|
+
"@pyreon/reactivity": "^0.11.6",
|
|
46
|
+
"@pyreon/styler": "^0.11.6",
|
|
47
|
+
"@pyreon/unistyle": "^0.11.6"
|
|
47
48
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"@pyreon/typescript": "^0.11.4"
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">= 22"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/PyreonUI.tsx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import {
|
|
3
|
-
import { computed, signal } from
|
|
4
|
-
import { ThemeContext } from
|
|
5
|
-
import type { PyreonTheme } from
|
|
6
|
-
import { enrichTheme } from
|
|
7
|
-
import { context as coreContext } from
|
|
1
|
+
import type { VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { createReactiveContext, provide, useContext } from '@pyreon/core'
|
|
3
|
+
import { computed, signal } from '@pyreon/reactivity'
|
|
4
|
+
import { ThemeContext } from '@pyreon/styler'
|
|
5
|
+
import type { PyreonTheme } from '@pyreon/unistyle'
|
|
6
|
+
import { enrichTheme } from '@pyreon/unistyle'
|
|
7
|
+
import { context as coreContext } from './context'
|
|
8
8
|
|
|
9
9
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
10
10
|
|
|
11
|
-
export type ThemeMode =
|
|
12
|
-
export type ThemeModeInput = ThemeMode |
|
|
11
|
+
export type ThemeMode = 'light' | 'dark'
|
|
12
|
+
export type ThemeModeInput = ThemeMode | 'system'
|
|
13
13
|
|
|
14
14
|
export interface PyreonUIProps {
|
|
15
15
|
/** Theme object with breakpoints, rootSize, and custom keys. */
|
|
@@ -27,7 +27,7 @@ export interface PyreonUIProps {
|
|
|
27
27
|
|
|
28
28
|
// ─── System mode detection ──────────────────────────────────────────────────
|
|
29
29
|
|
|
30
|
-
const _isBrowser = typeof window !==
|
|
30
|
+
const _isBrowser = typeof window !== 'undefined' && typeof matchMedia === 'function'
|
|
31
31
|
|
|
32
32
|
/** Reactive signal tracking the OS dark mode preference. Lazy-initialized on first use. */
|
|
33
33
|
let _systemMode: ReturnType<typeof signal<ThemeMode>> | undefined
|
|
@@ -35,12 +35,12 @@ let _systemMode: ReturnType<typeof signal<ThemeMode>> | undefined
|
|
|
35
35
|
function getSystemMode(): ReturnType<typeof signal<ThemeMode>> {
|
|
36
36
|
if (_systemMode) return _systemMode
|
|
37
37
|
|
|
38
|
-
const prefersDark = _isBrowser && matchMedia(
|
|
39
|
-
_systemMode = signal<ThemeMode>(prefersDark ?
|
|
38
|
+
const prefersDark = _isBrowser && matchMedia('(prefers-color-scheme: dark)').matches
|
|
39
|
+
_systemMode = signal<ThemeMode>(prefersDark ? 'dark' : 'light')
|
|
40
40
|
|
|
41
41
|
if (_isBrowser) {
|
|
42
|
-
matchMedia(
|
|
43
|
-
_systemMode?.set(e.matches ?
|
|
42
|
+
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
43
|
+
_systemMode?.set(e.matches ? 'dark' : 'light')
|
|
44
44
|
})
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -49,10 +49,10 @@ function getSystemMode(): ReturnType<typeof signal<ThemeMode>> {
|
|
|
49
49
|
|
|
50
50
|
// ─── Mode context ───────────────────────────────────────────────────────────
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
const ModeContext =
|
|
52
|
+
/** Reactive context — useContext(ModeContext) returns () => ThemeMode. */
|
|
53
|
+
const ModeContext = createReactiveContext<ThemeMode>('light')
|
|
54
54
|
|
|
55
|
-
const INVERSED: Record<ThemeMode, ThemeMode> = { light:
|
|
55
|
+
const INVERSED: Record<ThemeMode, ThemeMode> = { light: 'dark', dark: 'light' }
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Read the resolved color mode ("light" | "dark") from the nearest PyreonUI.
|
|
@@ -96,15 +96,15 @@ function autoInit(): void {
|
|
|
96
96
|
* <PyreonUI theme={theme} mode="system">
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
|
-
export function PyreonUI({ theme, mode =
|
|
99
|
+
export function PyreonUI({ theme, mode = 'light', inversed, children }: PyreonUIProps): VNodeChild {
|
|
100
100
|
autoInit()
|
|
101
101
|
|
|
102
102
|
// Create a reactive mode getter that resolves "system" and applies inversion.
|
|
103
103
|
// This getter is provided via context — consumers read it lazily in their
|
|
104
104
|
// own reactive scopes, so mode changes propagate automatically.
|
|
105
105
|
const resolveMode = (): ThemeMode => {
|
|
106
|
-
const raw = typeof mode ===
|
|
107
|
-
const resolved = raw ===
|
|
106
|
+
const raw = typeof mode === 'function' ? mode() : mode
|
|
107
|
+
const resolved = raw === 'system' ? getSystemMode()() : raw
|
|
108
108
|
return inversed ? INVERSED[resolved] : resolved
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -119,21 +119,15 @@ export function PyreonUI({ theme, mode = "light", inversed, children }: PyreonUI
|
|
|
119
119
|
// 1. Styler ThemeContext — for styled() components and useTheme()
|
|
120
120
|
provide(ThemeContext, enrichedTheme)
|
|
121
121
|
|
|
122
|
-
// 2. Core context — provide a reactive
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
provide(coreContext, {
|
|
122
|
+
// 2. Core context — provide a reactive getter function.
|
|
123
|
+
// coreContext is a ReactiveContext, so provide(() => value).
|
|
124
|
+
// Rocketstyle reads mode/isDark/isLight by calling the getter.
|
|
125
|
+
provide(coreContext, () => ({
|
|
126
126
|
theme: enrichedTheme,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return modeComputed() === "dark"
|
|
132
|
-
},
|
|
133
|
-
get isLight() {
|
|
134
|
-
return modeComputed() === "light"
|
|
135
|
-
},
|
|
136
|
-
})
|
|
127
|
+
mode: modeComputed(),
|
|
128
|
+
isDark: modeComputed() === 'dark',
|
|
129
|
+
isLight: modeComputed() === 'light',
|
|
130
|
+
}))
|
|
137
131
|
|
|
138
132
|
// 3. Mode context — getter function for useMode()
|
|
139
133
|
provide(ModeContext, () => modeComputed())
|
|
@@ -1,97 +1,100 @@
|
|
|
1
|
-
import { h } from
|
|
2
|
-
import { describe, expect, it, vi } from
|
|
3
|
-
import { PyreonUI } from
|
|
1
|
+
import { h } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { PyreonUI } from '../PyreonUI'
|
|
4
4
|
|
|
5
5
|
// Spy on provide to verify context provision
|
|
6
|
-
const provideSpy = vi.spyOn(await import(
|
|
6
|
+
const provideSpy = vi.spyOn(await import('@pyreon/core'), 'provide')
|
|
7
7
|
|
|
8
8
|
/** Get the value argument (2nd arg) from a provide() call by index. */
|
|
9
9
|
const getProvideValue = (callIndex: number): any => provideSpy.mock.calls[callIndex]![1]
|
|
10
10
|
|
|
11
|
-
describe(
|
|
11
|
+
describe('PyreonUI', () => {
|
|
12
12
|
const theme = {
|
|
13
13
|
rootSize: 16,
|
|
14
14
|
breakpoints: { xs: 0, sm: 576, md: 768 },
|
|
15
|
-
colors: { primary:
|
|
15
|
+
colors: { primary: '#228be6' },
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
beforeEach(() => {
|
|
19
19
|
provideSpy.mockClear()
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it(
|
|
23
|
-
const child = h(
|
|
22
|
+
it('renders children', () => {
|
|
23
|
+
const child = h('div', null, 'hello')
|
|
24
24
|
const result = PyreonUI({ theme, children: child })
|
|
25
25
|
expect(result).toBe(child)
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
it(
|
|
28
|
+
it('returns null when no children', () => {
|
|
29
29
|
const result = PyreonUI({ theme })
|
|
30
30
|
expect(result).toBeNull()
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
it(
|
|
33
|
+
it('calls provide three times (ThemeContext, core context, mode context)', () => {
|
|
34
34
|
PyreonUI({ theme, children: null })
|
|
35
35
|
expect(provideSpy).toHaveBeenCalledTimes(3)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
it(
|
|
38
|
+
it('defaults mode to light', () => {
|
|
39
39
|
PyreonUI({ theme, children: null })
|
|
40
40
|
|
|
41
|
-
// Core context (2nd call) —
|
|
42
|
-
const
|
|
43
|
-
expect(
|
|
41
|
+
// Core context (2nd call) — ReactiveContext getter function
|
|
42
|
+
const coreCtxGetter = getProvideValue(1)
|
|
43
|
+
expect(typeof coreCtxGetter).toBe('function')
|
|
44
|
+
const coreCtx = coreCtxGetter()
|
|
45
|
+
expect(coreCtx.mode).toBe('light')
|
|
44
46
|
expect(coreCtx.isLight).toBe(true)
|
|
45
47
|
expect(coreCtx.isDark).toBe(false)
|
|
46
48
|
|
|
47
49
|
// Mode context (3rd call) — getter function
|
|
48
50
|
const modeGetter = getProvideValue(2)
|
|
49
|
-
expect(typeof modeGetter).toBe(
|
|
50
|
-
expect(modeGetter()).toBe(
|
|
51
|
+
expect(typeof modeGetter).toBe('function')
|
|
52
|
+
expect(modeGetter()).toBe('light')
|
|
51
53
|
})
|
|
52
54
|
|
|
53
|
-
it(
|
|
54
|
-
PyreonUI({ theme, mode:
|
|
55
|
+
it('provides dark mode', () => {
|
|
56
|
+
PyreonUI({ theme, mode: 'dark', children: null })
|
|
55
57
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
+
const coreCtxGetter = getProvideValue(1)
|
|
59
|
+
const coreCtx = coreCtxGetter()
|
|
60
|
+
expect(coreCtx.mode).toBe('dark')
|
|
58
61
|
expect(coreCtx.isDark).toBe(true)
|
|
59
62
|
expect(coreCtx.isLight).toBe(false)
|
|
60
63
|
|
|
61
64
|
const modeGetter = getProvideValue(2)
|
|
62
|
-
expect(modeGetter()).toBe(
|
|
65
|
+
expect(modeGetter()).toBe('dark')
|
|
63
66
|
})
|
|
64
67
|
|
|
65
|
-
it(
|
|
66
|
-
PyreonUI({ theme, mode:
|
|
67
|
-
expect(getProvideValue(2)()).toBe(
|
|
68
|
+
it('inverts mode when inversed=true', () => {
|
|
69
|
+
PyreonUI({ theme, mode: 'light', inversed: true, children: null })
|
|
70
|
+
expect(getProvideValue(2)()).toBe('dark')
|
|
68
71
|
})
|
|
69
72
|
|
|
70
|
-
it(
|
|
71
|
-
PyreonUI({ theme, mode:
|
|
72
|
-
expect(getProvideValue(2)()).toBe(
|
|
73
|
+
it('inverts dark to light', () => {
|
|
74
|
+
PyreonUI({ theme, mode: 'dark', inversed: true, children: null })
|
|
75
|
+
expect(getProvideValue(2)()).toBe('light')
|
|
73
76
|
})
|
|
74
77
|
|
|
75
|
-
it(
|
|
78
|
+
it('enriches theme with __PYREON__ before providing', () => {
|
|
76
79
|
PyreonUI({ theme, children: null })
|
|
77
80
|
|
|
78
81
|
const providedTheme = getProvideValue(0)
|
|
79
82
|
expect(providedTheme.__PYREON__).toBeDefined()
|
|
80
|
-
expect(providedTheme.__PYREON__.sortedBreakpoints).toEqual([
|
|
81
|
-
expect(providedTheme.colors).toEqual({ primary:
|
|
83
|
+
expect(providedTheme.__PYREON__.sortedBreakpoints).toEqual(['xs', 'sm', 'md'])
|
|
84
|
+
expect(providedTheme.colors).toEqual({ primary: '#228be6' })
|
|
82
85
|
})
|
|
83
86
|
|
|
84
|
-
it(
|
|
85
|
-
PyreonUI({ theme, mode:
|
|
86
|
-
expect(getProvideValue(2)()).toBe(
|
|
87
|
+
it('works with system mode (resolves to light in happy-dom)', () => {
|
|
88
|
+
PyreonUI({ theme, mode: 'system', children: null })
|
|
89
|
+
expect(getProvideValue(2)()).toBe('light')
|
|
87
90
|
})
|
|
88
91
|
|
|
89
|
-
it(
|
|
90
|
-
PyreonUI({ theme, mode:
|
|
92
|
+
it('mode context is a getter function (reactive-ready)', () => {
|
|
93
|
+
PyreonUI({ theme, mode: 'dark', children: null })
|
|
91
94
|
const modeGetter = getProvideValue(2)
|
|
92
95
|
// Mode context is a function, not a static value — consumers call it
|
|
93
96
|
// inside their own reactive scopes for reactive mode switching.
|
|
94
|
-
expect(typeof modeGetter).toBe(
|
|
95
|
-
expect(modeGetter()).toBe(
|
|
97
|
+
expect(typeof modeGetter).toBe('function')
|
|
98
|
+
expect(modeGetter()).toBe('dark')
|
|
96
99
|
})
|
|
97
100
|
})
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import compose from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import compose from '../compose'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
4
|
+
describe('compose', () => {
|
|
5
|
+
it('should compose two functions right-to-left', () => {
|
|
6
6
|
const double = (x: number) => x * 2
|
|
7
7
|
const addOne = (x: number) => x + 1
|
|
8
8
|
const composed = compose(addOne, double)
|
|
@@ -10,7 +10,7 @@ describe("compose", () => {
|
|
|
10
10
|
expect(composed(3)).toBe(7)
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
it(
|
|
13
|
+
it('should compose three functions right-to-left', () => {
|
|
14
14
|
const add = (x: number) => x + 1
|
|
15
15
|
const mul = (x: number) => x * 3
|
|
16
16
|
const sub = (x: number) => x - 2
|
|
@@ -18,15 +18,15 @@ describe("compose", () => {
|
|
|
18
18
|
expect(compose(add, mul, sub)(5)).toBe(10)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
it(
|
|
21
|
+
it('should work with a single function', () => {
|
|
22
22
|
const identity = (x: number) => x
|
|
23
23
|
expect(compose(identity)(42)).toBe(42)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
it(
|
|
26
|
+
it('should pass value through string transforms', () => {
|
|
27
27
|
const upper = (s: string) => s.toUpperCase()
|
|
28
28
|
const exclaim = (s: string) => `${s}!`
|
|
29
29
|
// exclaim('hello') = 'hello!', upper('hello!') = 'HELLO!'
|
|
30
|
-
expect(compose(upper, exclaim)(
|
|
30
|
+
expect(compose(upper, exclaim)('hello')).toBe('HELLO!')
|
|
31
31
|
})
|
|
32
32
|
})
|