@pyreon/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.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/lib/analysis/index.js.html +1 -1
  3. package/lib/index.js +33 -5
  4. package/lib/index.js.map +1 -1
  5. package/lib/jsx-dev-runtime.js.map +1 -1
  6. package/lib/jsx-runtime.js.map +1 -1
  7. package/lib/types/index.d.ts +145 -98
  8. package/lib/types/index.d.ts.map +1 -1
  9. package/lib/types/jsx-dev-runtime.d.ts +94 -94
  10. package/lib/types/jsx-runtime.d.ts +94 -94
  11. package/package.json +11 -11
  12. package/src/component.ts +2 -2
  13. package/src/context.ts +75 -4
  14. package/src/dynamic.ts +4 -4
  15. package/src/error-boundary.ts +10 -10
  16. package/src/for.ts +8 -2
  17. package/src/h.ts +4 -4
  18. package/src/index.ts +30 -27
  19. package/src/jsx-dev-runtime.ts +1 -1
  20. package/src/jsx-runtime.ts +108 -108
  21. package/src/lazy.ts +4 -4
  22. package/src/lifecycle.ts +6 -6
  23. package/src/portal.ts +2 -2
  24. package/src/show.ts +4 -4
  25. package/src/style.ts +51 -51
  26. package/src/suspense.ts +8 -8
  27. package/src/telemetry.ts +1 -1
  28. package/src/tests/component.test.ts +60 -60
  29. package/src/tests/context.test.ts +102 -102
  30. package/src/tests/core.test.ts +376 -376
  31. package/src/tests/cx.test.ts +34 -34
  32. package/src/tests/dynamic.test.ts +28 -28
  33. package/src/tests/error-boundary.test.ts +51 -51
  34. package/src/tests/for.test.ts +26 -26
  35. package/src/tests/h.test.ts +100 -100
  36. package/src/tests/jsx-compat.test.tsx +41 -41
  37. package/src/tests/lazy.test.ts +28 -28
  38. package/src/tests/lifecycle.test.ts +35 -35
  39. package/src/tests/map-array.test.ts +36 -36
  40. package/src/tests/portal.test.ts +21 -21
  41. package/src/tests/props-extended.test.ts +51 -51
  42. package/src/tests/props.test.ts +62 -62
  43. package/src/tests/ref.test.ts +20 -20
  44. package/src/tests/show.test.ts +94 -94
  45. package/src/tests/style.test.ts +101 -101
  46. package/src/tests/suspense.test.ts +44 -44
  47. package/src/tests/telemetry.test.ts +35 -35
package/src/style.ts CHANGED
@@ -3,50 +3,50 @@
3
3
  // CSS properties where numeric values are unitless (e.g. `opacity: 0.5`, `zIndex: 10`).
4
4
  // All other numeric values get "px" appended automatically (e.g. `height: 100` → `"100px"`).
5
5
  export const CSS_UNITLESS = new Set([
6
- "animationIterationCount",
7
- "aspectRatio",
8
- "borderImageOutset",
9
- "borderImageSlice",
10
- "borderImageWidth",
11
- "boxFlex",
12
- "boxFlexGroup",
13
- "boxOrdinalGroup",
14
- "columnCount",
15
- "columns",
16
- "flex",
17
- "flexGrow",
18
- "flexPositive",
19
- "flexShrink",
20
- "flexNegative",
21
- "flexOrder",
22
- "gridArea",
23
- "gridRow",
24
- "gridRowEnd",
25
- "gridRowSpan",
26
- "gridRowStart",
27
- "gridColumn",
28
- "gridColumnEnd",
29
- "gridColumnSpan",
30
- "gridColumnStart",
31
- "fontWeight",
32
- "lineClamp",
33
- "lineHeight",
34
- "opacity",
35
- "order",
36
- "orphans",
37
- "scale",
38
- "tabSize",
39
- "widows",
40
- "zIndex",
41
- "zoom",
42
- "fillOpacity",
43
- "floodOpacity",
44
- "stopOpacity",
45
- "strokeDasharray",
46
- "strokeDashoffset",
47
- "strokeMiterlimit",
48
- "strokeOpacity",
49
- "strokeWidth",
6
+ 'animationIterationCount',
7
+ 'aspectRatio',
8
+ 'borderImageOutset',
9
+ 'borderImageSlice',
10
+ 'borderImageWidth',
11
+ 'boxFlex',
12
+ 'boxFlexGroup',
13
+ 'boxOrdinalGroup',
14
+ 'columnCount',
15
+ 'columns',
16
+ 'flex',
17
+ 'flexGrow',
18
+ 'flexPositive',
19
+ 'flexShrink',
20
+ 'flexNegative',
21
+ 'flexOrder',
22
+ 'gridArea',
23
+ 'gridRow',
24
+ 'gridRowEnd',
25
+ 'gridRowSpan',
26
+ 'gridRowStart',
27
+ 'gridColumn',
28
+ 'gridColumnEnd',
29
+ 'gridColumnSpan',
30
+ 'gridColumnStart',
31
+ 'fontWeight',
32
+ 'lineClamp',
33
+ 'lineHeight',
34
+ 'opacity',
35
+ 'order',
36
+ 'orphans',
37
+ 'scale',
38
+ 'tabSize',
39
+ 'widows',
40
+ 'zIndex',
41
+ 'zoom',
42
+ 'fillOpacity',
43
+ 'floodOpacity',
44
+ 'stopOpacity',
45
+ 'strokeDasharray',
46
+ 'strokeDashoffset',
47
+ 'strokeMiterlimit',
48
+ 'strokeOpacity',
49
+ 'strokeWidth',
50
50
  ])
51
51
 
52
52
  // ─── Class utilities ─────────────────────────────────────────────────────────
@@ -62,17 +62,17 @@ export type ClassValue =
62
62
  | Record<string, boolean | null | undefined | (() => boolean)>
63
63
 
64
64
  function cxObject(obj: Record<string, boolean | null | undefined | (() => boolean)>): string {
65
- let result = ""
65
+ let result = ''
66
66
  for (const key in obj) {
67
67
  const v = obj[key]
68
- const truthy = typeof v === "function" ? v() : v
68
+ const truthy = typeof v === 'function' ? v() : v
69
69
  if (truthy) result = result ? `${result} ${key}` : key
70
70
  }
71
71
  return result
72
72
  }
73
73
 
74
74
  function cxArray(arr: ClassValue[]): string {
75
- let result = ""
75
+ let result = ''
76
76
  for (const item of arr) {
77
77
  const resolved = cx(item)
78
78
  if (resolved) result = result ? `${result} ${resolved}` : resolved
@@ -82,9 +82,9 @@ function cxArray(arr: ClassValue[]): string {
82
82
 
83
83
  /** Resolve a ClassValue into a flat class string (like clsx/cx). */
84
84
  export function cx(value: ClassValue): string {
85
- if (value == null || value === false || value === true) return ""
86
- if (typeof value === "string") return value
87
- if (typeof value === "number") return String(value)
85
+ if (value == null || value === false || value === true) return ''
86
+ if (typeof value === 'string') return value
87
+ if (typeof value === 'number') return String(value)
88
88
  if (Array.isArray(value)) return cxArray(value)
89
89
  return cxObject(value)
90
90
  }
@@ -98,5 +98,5 @@ export function toKebabCase(str: string): string {
98
98
 
99
99
  /** Normalize a style value — appends "px" to numbers for non-unitless properties. */
100
100
  export function normalizeStyleValue(key: string, value: unknown): string {
101
- return typeof value === "number" && !CSS_UNITLESS.has(key) ? `${value}px` : String(value)
101
+ return typeof value === 'number' && !CSS_UNITLESS.has(key) ? `${value}px` : String(value)
102
102
  }
package/src/suspense.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Fragment, h } from "./h"
2
- import type { Props, VNode, VNodeChild } from "./types"
1
+ import { Fragment, h } from './h'
2
+ import type { Props, VNode, VNodeChild } from './types'
3
3
 
4
- const __DEV__ = typeof process !== "undefined" && process.env.NODE_ENV !== "production"
4
+ const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
5
5
 
6
6
  /** Internal marker attached to lazy()-wrapped components */
7
7
  export type LazyComponent<P extends Props = Props> = ((props: P) => VNodeChild) & {
@@ -25,25 +25,25 @@ export function Suspense(props: { fallback: VNodeChild; children?: VNodeChild })
25
25
  if (__DEV__ && props.fallback === undefined) {
26
26
  // biome-ignore lint/suspicious/noConsole: dev-only warning
27
27
  console.warn(
28
- "[Pyreon] <Suspense> is missing a `fallback` prop. Provide fallback UI to show while loading.",
28
+ '[Pyreon] <Suspense> is missing a `fallback` prop. Provide fallback UI to show while loading.',
29
29
  )
30
30
  }
31
31
 
32
32
  return h(Fragment, null, () => {
33
33
  const ch = props.children
34
- const childNode = typeof ch === "function" ? ch() : ch
34
+ const childNode = typeof ch === 'function' ? ch() : ch
35
35
 
36
36
  // Check if the child is a VNode whose type is a lazy component still loading
37
37
  const isLoading =
38
38
  childNode != null &&
39
- typeof childNode === "object" &&
39
+ typeof childNode === 'object' &&
40
40
  !Array.isArray(childNode) &&
41
- typeof (childNode as VNode).type === "function" &&
41
+ typeof (childNode as VNode).type === 'function' &&
42
42
  ((childNode as VNode).type as unknown as LazyComponent).__loading?.()
43
43
 
44
44
  if (isLoading) {
45
45
  const fb = props.fallback
46
- return typeof fb === "function" ? fb() : fb
46
+ return typeof fb === 'function' ? fb() : fb
47
47
  }
48
48
  return childNode
49
49
  })
package/src/telemetry.ts CHANGED
@@ -16,7 +16,7 @@ export interface ErrorContext {
16
16
  /** Component function name, or "Anonymous" */
17
17
  component: string
18
18
  /** Lifecycle phase where the error occurred */
19
- phase: "setup" | "render" | "mount" | "unmount" | "effect"
19
+ phase: 'setup' | 'render' | 'mount' | 'unmount' | 'effect'
20
20
  /** The thrown value */
21
21
  error: unknown
22
22
  /** Unix timestamp (ms) */
@@ -5,28 +5,28 @@ import {
5
5
  propagateError,
6
6
  pushErrorBoundary,
7
7
  runWithHooks,
8
- } from "../component"
9
- import { h } from "../h"
10
- import { onErrorCaptured, onMount, onUnmount, onUpdate } from "../lifecycle"
11
- import type { ComponentFn, LifecycleHooks, VNode } from "../types"
8
+ } from '../component'
9
+ import { h } from '../h'
10
+ import { onErrorCaptured, onMount, onUnmount, onUpdate } from '../lifecycle'
11
+ import type { ComponentFn, LifecycleHooks, VNode } from '../types'
12
12
 
13
- describe("defineComponent", () => {
14
- test("returns the exact same function (identity)", () => {
15
- const fn: ComponentFn = () => h("div", null)
13
+ describe('defineComponent', () => {
14
+ test('returns the exact same function (identity)', () => {
15
+ const fn: ComponentFn = () => h('div', null)
16
16
  expect(defineComponent(fn)).toBe(fn)
17
17
  })
18
18
 
19
- test("preserves typed props", () => {
19
+ test('preserves typed props', () => {
20
20
  const Comp = defineComponent<{ count: number }>((props) => {
21
- return h("span", null, String(props.count))
21
+ return h('span', null, String(props.count))
22
22
  })
23
23
  const node = Comp({ count: 10 })
24
- expect((node as VNode).type).toBe("span")
24
+ expect((node as VNode).type).toBe('span')
25
25
  })
26
26
  })
27
27
 
28
- describe("runWithHooks", () => {
29
- test("captures all lifecycle hook types", () => {
28
+ describe('runWithHooks', () => {
29
+ test('captures all lifecycle hook types', () => {
30
30
  const mountFn = () => undefined
31
31
  const unmountFn = () => {}
32
32
  const updateFn = () => {}
@@ -37,7 +37,7 @@ describe("runWithHooks", () => {
37
37
  onUnmount(unmountFn)
38
38
  onUpdate(updateFn)
39
39
  onErrorCaptured(errorFn)
40
- return h("div", null)
40
+ return h('div', null)
41
41
  }
42
42
 
43
43
  const { vnode, hooks } = runWithHooks(Comp, {})
@@ -48,51 +48,51 @@ describe("runWithHooks", () => {
48
48
  expect(hooks.error).toContain(errorFn)
49
49
  })
50
50
 
51
- test("returns null vnode for component returning null", () => {
51
+ test('returns null vnode for component returning null', () => {
52
52
  const { vnode } = runWithHooks(() => null, {})
53
53
  expect(vnode).toBeNull()
54
54
  })
55
55
 
56
- test("returns string vnode for component returning string", () => {
57
- const { vnode } = runWithHooks(() => "hello", {})
58
- expect(vnode).toBe("hello")
56
+ test('returns string vnode for component returning string', () => {
57
+ const { vnode } = runWithHooks(() => 'hello', {})
58
+ expect(vnode).toBe('hello')
59
59
  })
60
60
 
61
- test("clears hooks context after execution", () => {
62
- const Comp: ComponentFn = () => h("div", null)
61
+ test('clears hooks context after execution', () => {
62
+ const Comp: ComponentFn = () => h('div', null)
63
63
  runWithHooks(Comp, {})
64
64
  // After runWithHooks, lifecycle hooks should be no-ops
65
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
65
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
66
66
  onMount(() => {})
67
67
  expect(warnSpy).toHaveBeenCalled()
68
68
  warnSpy.mockRestore()
69
69
  })
70
70
 
71
- test("clears hooks context even when component throws", () => {
71
+ test('clears hooks context even when component throws', () => {
72
72
  const Comp: ComponentFn = () => {
73
- throw new Error("boom")
73
+ throw new Error('boom')
74
74
  }
75
- expect(() => runWithHooks(Comp, {})).toThrow("boom")
75
+ expect(() => runWithHooks(Comp, {})).toThrow('boom')
76
76
  // Should still be cleared
77
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
77
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
78
78
  onMount(() => {})
79
79
  expect(warnSpy).toHaveBeenCalled()
80
80
  warnSpy.mockRestore()
81
81
  })
82
82
 
83
- test("passes props to component function", () => {
83
+ test('passes props to component function', () => {
84
84
  let received: unknown = null
85
85
  runWithHooks(
86
86
  ((props: { msg: string }) => {
87
87
  received = props
88
88
  return null
89
89
  }) as ComponentFn,
90
- { msg: "hello" },
90
+ { msg: 'hello' },
91
91
  )
92
- expect(received).toEqual({ msg: "hello" })
92
+ expect(received).toEqual({ msg: 'hello' })
93
93
  })
94
94
 
95
- test("captures multiple hooks of same type", () => {
95
+ test('captures multiple hooks of same type', () => {
96
96
  const Comp: ComponentFn = () => {
97
97
  onMount(() => undefined)
98
98
  onMount(() => undefined)
@@ -105,8 +105,8 @@ describe("runWithHooks", () => {
105
105
  expect(hooks.unmount).toHaveLength(2)
106
106
  })
107
107
 
108
- test("empty hooks when component registers none", () => {
109
- const { hooks } = runWithHooks(() => h("div", null), {})
108
+ test('empty hooks when component registers none', () => {
109
+ const { hooks } = runWithHooks(() => h('div', null), {})
110
110
  expect(hooks.mount).toHaveLength(0)
111
111
  expect(hooks.unmount).toHaveLength(0)
112
112
  expect(hooks.update).toHaveLength(0)
@@ -114,38 +114,38 @@ describe("runWithHooks", () => {
114
114
  })
115
115
  })
116
116
 
117
- describe("propagateError", () => {
118
- test("returns true when handler returns true", () => {
117
+ describe('propagateError', () => {
118
+ test('returns true when handler returns true', () => {
119
119
  const hooks: LifecycleHooks = {
120
120
  mount: [],
121
121
  unmount: [],
122
122
  update: [],
123
123
  error: [() => true],
124
124
  }
125
- expect(propagateError(new Error("test"), hooks)).toBe(true)
125
+ expect(propagateError(new Error('test'), hooks)).toBe(true)
126
126
  })
127
127
 
128
- test("returns false when no handlers", () => {
128
+ test('returns false when no handlers', () => {
129
129
  const hooks: LifecycleHooks = {
130
130
  mount: [],
131
131
  unmount: [],
132
132
  update: [],
133
133
  error: [],
134
134
  }
135
- expect(propagateError(new Error("test"), hooks)).toBe(false)
135
+ expect(propagateError(new Error('test'), hooks)).toBe(false)
136
136
  })
137
137
 
138
- test("returns false when handler returns undefined", () => {
138
+ test('returns false when handler returns undefined', () => {
139
139
  const hooks: LifecycleHooks = {
140
140
  mount: [],
141
141
  unmount: [],
142
142
  update: [],
143
143
  error: [() => undefined],
144
144
  }
145
- expect(propagateError(new Error("test"), hooks)).toBe(false)
145
+ expect(propagateError(new Error('test'), hooks)).toBe(false)
146
146
  })
147
147
 
148
- test("stops at first handler returning true", () => {
148
+ test('stops at first handler returning true', () => {
149
149
  let secondCalled = false
150
150
  const hooks: LifecycleHooks = {
151
151
  mount: [],
@@ -159,11 +159,11 @@ describe("propagateError", () => {
159
159
  },
160
160
  ],
161
161
  }
162
- expect(propagateError("err", hooks)).toBe(true)
162
+ expect(propagateError('err', hooks)).toBe(true)
163
163
  expect(secondCalled).toBe(false)
164
164
  })
165
165
 
166
- test("continues to next handler when first returns undefined", () => {
166
+ test('continues to next handler when first returns undefined', () => {
167
167
  const calls: number[] = []
168
168
  const hooks: LifecycleHooks = {
169
169
  mount: [],
@@ -180,11 +180,11 @@ describe("propagateError", () => {
180
180
  },
181
181
  ],
182
182
  }
183
- expect(propagateError("err", hooks)).toBe(true)
183
+ expect(propagateError('err', hooks)).toBe(true)
184
184
  expect(calls).toEqual([1, 2])
185
185
  })
186
186
 
187
- test("passes the error to each handler", () => {
187
+ test('passes the error to each handler', () => {
188
188
  const errors: unknown[] = []
189
189
  const hooks: LifecycleHooks = {
190
190
  mount: [],
@@ -201,37 +201,37 @@ describe("propagateError", () => {
201
201
  },
202
202
  ],
203
203
  }
204
- const testErr = new Error("propagated")
204
+ const testErr = new Error('propagated')
205
205
  propagateError(testErr, hooks)
206
206
  expect(errors).toEqual([testErr, testErr])
207
207
  })
208
208
  })
209
209
 
210
- describe("pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary", () => {
210
+ describe('pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary', () => {
211
211
  afterEach(() => {
212
212
  // Clean up any leftover boundaries — pop until empty
213
213
  // dispatchToErrorBoundary returns false when stack is empty
214
- while (dispatchToErrorBoundary("cleanup-probe")) {
214
+ while (dispatchToErrorBoundary('cleanup-probe')) {
215
215
  popErrorBoundary()
216
216
  }
217
217
  })
218
218
 
219
- test("dispatches to the most recently pushed boundary", () => {
219
+ test('dispatches to the most recently pushed boundary', () => {
220
220
  let caught: unknown = null
221
221
  pushErrorBoundary((err) => {
222
222
  caught = err
223
223
  return true
224
224
  })
225
- expect(dispatchToErrorBoundary("test-error")).toBe(true)
226
- expect(caught).toBe("test-error")
225
+ expect(dispatchToErrorBoundary('test-error')).toBe(true)
226
+ expect(caught).toBe('test-error')
227
227
  popErrorBoundary()
228
228
  })
229
229
 
230
- test("returns false when no boundary is registered", () => {
231
- expect(dispatchToErrorBoundary("no-boundary")).toBe(false)
230
+ test('returns false when no boundary is registered', () => {
231
+ expect(dispatchToErrorBoundary('no-boundary')).toBe(false)
232
232
  })
233
233
 
234
- test("nested boundaries — innermost catches first", () => {
234
+ test('nested boundaries — innermost catches first', () => {
235
235
  const caught: string[] = []
236
236
  pushErrorBoundary((err) => {
237
237
  caught.push(`outer: ${err}`)
@@ -241,17 +241,17 @@ describe("pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary", () =>
241
241
  caught.push(`inner: ${err}`)
242
242
  return true
243
243
  })
244
- dispatchToErrorBoundary("test")
245
- expect(caught).toEqual(["inner: test"])
244
+ dispatchToErrorBoundary('test')
245
+ expect(caught).toEqual(['inner: test'])
246
246
  popErrorBoundary()
247
247
 
248
248
  // After popping inner, outer should catch
249
- dispatchToErrorBoundary("test2")
250
- expect(caught).toEqual(["inner: test", "outer: test2"])
249
+ dispatchToErrorBoundary('test2')
250
+ expect(caught).toEqual(['inner: test', 'outer: test2'])
251
251
  popErrorBoundary()
252
252
  })
253
253
 
254
- test("boundary handler returning false does not propagate to outer", () => {
254
+ test('boundary handler returning false does not propagate to outer', () => {
255
255
  // dispatchToErrorBoundary only calls the innermost handler
256
256
  let outerCalled = false
257
257
  pushErrorBoundary(() => {
@@ -259,23 +259,23 @@ describe("pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary", () =>
259
259
  return true
260
260
  })
261
261
  pushErrorBoundary(() => false)
262
- const result = dispatchToErrorBoundary("test")
262
+ const result = dispatchToErrorBoundary('test')
263
263
  expect(result).toBe(false)
264
264
  expect(outerCalled).toBe(false) // outer not called — only innermost is checked
265
265
  popErrorBoundary()
266
266
  popErrorBoundary()
267
267
  })
268
268
 
269
- test("push and pop maintain stack correctly", () => {
269
+ test('push and pop maintain stack correctly', () => {
270
270
  const results: boolean[] = []
271
271
  pushErrorBoundary(() => true)
272
272
  pushErrorBoundary(() => true)
273
273
  pushErrorBoundary(() => true)
274
274
  popErrorBoundary()
275
275
  popErrorBoundary()
276
- results.push(dispatchToErrorBoundary("x"))
276
+ results.push(dispatchToErrorBoundary('x'))
277
277
  popErrorBoundary()
278
- results.push(dispatchToErrorBoundary("y"))
278
+ results.push(dispatchToErrorBoundary('y'))
279
279
  expect(results).toEqual([true, false])
280
280
  })
281
281
  })