@pyreon/elements 0.14.0 → 0.16.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.
@@ -0,0 +1,121 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { styles } from '../helpers/Wrapper/styled'
3
+
4
+ /**
5
+ * Regression test for the responsive `block` cascade bug (mirrors vitus-labs's
6
+ * "couple of fixes" PR #121).
7
+ *
8
+ * Scenario: a responsive theme like `block: [true, false, true]` runs the
9
+ * styles callback once per breakpoint with a single-value `t.block`. The bug
10
+ * was that `align-self`, `flex`/`width` etc. were emitted ONLY when
11
+ * `t.block` was truthy — so the breakpoint where `t.block` is false emitted
12
+ * nothing, leaving the previous breakpoint's `align-self: stretch` cascading
13
+ * through the mobile-first @media query.
14
+ *
15
+ * Fix: always emit a value for these properties (truthy → stretch/100%,
16
+ * falsy → auto/auto). The mobile-first @media cascade then resets cleanly
17
+ * when `block` flips false at a later breakpoint.
18
+ *
19
+ * The styles callback is called per-breakpoint with the per-breakpoint theme
20
+ * already resolved to a single value — so testing it directly with two
21
+ * scalar themes is enough to lock in the always-emit contract.
22
+ */
23
+
24
+ const identityCss = (strings: TemplateStringsArray, ...vals: unknown[]) => {
25
+ let r = ''
26
+ for (let i = 0; i < strings.length; i++) {
27
+ r += strings[i]
28
+ if (i < vals.length) {
29
+ const v = vals[i]
30
+ // Mirror styled-components / styler interpolation: drop falsy
31
+ // interpolations, stringify the rest. CSSResult instances (from the
32
+ // alignContent / extendCss helpers) coerce via their toString.
33
+ if (v === false || v == null) continue
34
+ r += String(v)
35
+ }
36
+ }
37
+ return r
38
+ }
39
+
40
+ const renderAt = (theme: Record<string, unknown>): string =>
41
+ String(styles({ theme, css: identityCss }))
42
+
43
+ describe('Wrapper styles — responsive block cascade reset', () => {
44
+ it('emits stretch/100%/flex when block is true', () => {
45
+ const out = renderAt({
46
+ block: true,
47
+ direction: 'inline',
48
+ alignX: 'left',
49
+ alignY: 'center',
50
+ })
51
+ expect(out).toContain('align-self: stretch')
52
+ expect(out).toContain('width: 100%')
53
+ expect(out).toContain('display: flex')
54
+ })
55
+
56
+ it('emits auto/auto/inline-flex reset when block is false (key fix)', () => {
57
+ const out = renderAt({
58
+ block: false,
59
+ direction: 'inline',
60
+ alignX: 'left',
61
+ alignY: 'center',
62
+ })
63
+ // The fix: these properties MUST be emitted with reset values so the
64
+ // mobile-first @media cascade doesn't leak `align-self: stretch` from a
65
+ // smaller breakpoint where block was true.
66
+ expect(out).toContain('align-self: auto')
67
+ expect(out).toContain('width: auto')
68
+ expect(out).toContain('display: inline-flex')
69
+ })
70
+
71
+ it('emits height: 100% when alignY is "block", auto otherwise', () => {
72
+ const blockY = renderAt({
73
+ block: false,
74
+ direction: 'inline',
75
+ alignX: 'left',
76
+ alignY: 'block',
77
+ })
78
+ expect(blockY).toContain('height: 100%')
79
+
80
+ const nonBlockY = renderAt({
81
+ block: false,
82
+ direction: 'inline',
83
+ alignX: 'left',
84
+ alignY: 'center',
85
+ })
86
+ expect(nonBlockY).toContain('height: auto')
87
+ })
88
+
89
+ it('does not emit display when childFix is set (parent split)', () => {
90
+ const out = renderAt({
91
+ block: true,
92
+ childFix: true,
93
+ direction: 'inline',
94
+ alignX: 'left',
95
+ alignY: 'center',
96
+ })
97
+ // childFix branch handles its own display rules outside the responsive
98
+ // styles callback.
99
+ expect(out).not.toContain('display: flex;')
100
+ expect(out).not.toContain('display: inline-flex;')
101
+ })
102
+
103
+ it('emits parentFix flex-direction: column only when parentFix is set', () => {
104
+ const withParentFix = renderAt({
105
+ block: true,
106
+ parentFix: true,
107
+ direction: 'inline',
108
+ alignX: 'left',
109
+ alignY: 'center',
110
+ })
111
+ expect(withParentFix).toContain('flex-direction: column')
112
+
113
+ const withoutParentFix = renderAt({
114
+ block: true,
115
+ direction: 'inline',
116
+ alignX: 'left',
117
+ alignY: 'center',
118
+ })
119
+ expect(withoutParentFix).not.toContain('flex-direction: column')
120
+ })
121
+ })
package/src/env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal process type — just enough for `process.env.NODE_ENV` checks.
3
+ * Avoids requiring @types/node in consumers that import pyreon source
4
+ * via the `"bun"` export condition.
5
+ */
6
+ declare var process: { env: { NODE_ENV?: string } }
@@ -10,7 +10,15 @@
10
10
  import type { VNode, VNodeChild } from '@pyreon/core'
11
11
  import { Fragment } from '@pyreon/core'
12
12
  import { isEmpty, render } from '@pyreon/ui-core'
13
- import type { ExtendedProps, ObjectValue, Props, SimpleValue } from './types'
13
+ import type {
14
+ ChildrenProps,
15
+ ExtendedProps,
16
+ LooseProps,
17
+ ObjectProps,
18
+ ObjectValue,
19
+ SimpleProps,
20
+ SimpleValue,
21
+ } from './types'
14
22
 
15
23
  type ClassifiedData =
16
24
  | { type: 'simple'; data: SimpleValue[] }
@@ -70,7 +78,7 @@ const attachItemProps: AttachItemProps = ({ i, length }: { i: number; length: nu
70
78
  }
71
79
  }
72
80
 
73
- const Component = (props: Props) => {
81
+ const Component = (props: LooseProps) => {
74
82
  const {
75
83
  itemKey,
76
84
  valueName,
@@ -246,7 +254,50 @@ const Component = (props: Props) => {
246
254
  return renderItems()
247
255
  }
248
256
 
249
- export default Object.assign(Component, {
257
+ // ---------------------------------------------------------------------------
258
+ // Public callable type — overloads expose the generic `<T>` API at the JSX
259
+ // boundary while the impl stays loose-typed. TS picks the matching overload
260
+ // based on the props object passed:
261
+ //
262
+ // <Iterator data={['a','b']} valueName="text" component={Item} />
263
+ // ^ T inferred as string → SimpleProps<string> overload selected
264
+ //
265
+ // <Iterator data={users} component={UserCard} />
266
+ // ^ T inferred as User → ObjectProps<User> overload selected
267
+ //
268
+ // <Iterator>{...}</Iterator> → ChildrenProps overload selected
269
+ // ---------------------------------------------------------------------------
270
+ export interface IteratorComponent {
271
+ // T is inferred from the `data` prop at the JSX site — no explicit
272
+ // generic argument needed. Order matters: SimpleProps first (matches
273
+ // `data: SimpleValue[]`), then ObjectProps (object[]), then ChildrenProps,
274
+ // then a LooseProps fallback.
275
+ //
276
+ // The narrow overloads (Simple / Object / Children) drive per-mode T
277
+ // inference and stricter compile-time errors for direct callers (e.g.
278
+ // `valueName` required for primitive arrays, forbidden for object arrays).
279
+ // The LooseProps fallback exists for forwarding patterns where the props
280
+ // type is a wide union that doesn't bind to any single narrow overload —
281
+ // notably `Partial<(typeof Wrapper)['$$types']>` spread back into the JSX
282
+ // site after `@pyreon/rocketstyle`'s 4-overload-aware `ExtractProps`
283
+ // distributes the union across all of Iterator's call signatures. Without
284
+ // the loose binding home, the wide union has nowhere to land and TS
285
+ // reports "no overload matches this call" at every forwarding site.
286
+ //
287
+ // Direct callers still see the strict per-mode errors — the loose fallback
288
+ // only fires when none of the three narrow overloads match.
289
+ <T extends SimpleValue>(props: SimpleProps<T>): VNodeChild
290
+ <T extends ObjectValue>(props: ObjectProps<T>): VNodeChild
291
+ (props: ChildrenProps): VNodeChild
292
+ (props: LooseProps): VNodeChild
293
+ isIterator: true
294
+ RESERVED_PROPS: typeof RESERVED_PROPS
295
+ displayName?: string
296
+ }
297
+
298
+ const Iterator = Object.assign(Component, {
250
299
  isIterator: true as const,
251
300
  RESERVED_PROPS,
252
- })
301
+ }) as unknown as IteratorComponent
302
+
303
+ export default Iterator
@@ -1,14 +1,30 @@
1
1
  import component from './component'
2
2
  import type {
3
+ ChildrenProps,
3
4
  ElementType,
4
5
  ExtendedProps,
6
+ LooseProps,
5
7
  MaybeNull,
8
+ ObjectProps,
6
9
  ObjectValue,
7
10
  Props,
8
11
  PropsCallback,
12
+ SimpleProps,
9
13
  SimpleValue,
10
14
  } from './types'
11
15
 
12
- export type { ElementType, ExtendedProps, MaybeNull, ObjectValue, Props, PropsCallback, SimpleValue }
16
+ export type {
17
+ ChildrenProps,
18
+ ElementType,
19
+ ExtendedProps,
20
+ LooseProps,
21
+ MaybeNull,
22
+ ObjectProps,
23
+ ObjectValue,
24
+ Props,
25
+ PropsCallback,
26
+ SimpleProps,
27
+ SimpleValue,
28
+ }
13
29
 
14
30
  export default component
@@ -23,6 +23,80 @@ export type ExtendedProps = {
23
23
  position: number
24
24
  }
25
25
 
26
+ // ---------------------------------------------------------------------------
27
+ // Per-mode prop shapes — narrowed via the `T` data-element type
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /**
31
+ * Iterator over an array of strings/numbers. Each item is wrapped in
32
+ * `{ [valueName]: item }` and that object is what callbacks see + what's
33
+ * spread onto the rendered component.
34
+ */
35
+ export type SimpleProps<T extends SimpleValue> = {
36
+ data: Array<T | MaybeNull>
37
+ /** A component to be rendered per item. */
38
+ component: ElementType
39
+ /**
40
+ * Key under which each primitive value is exposed to `component` and
41
+ * callbacks. Defaults to `'children'` at runtime — i.e. the value is
42
+ * passed to the component as its children.
43
+ */
44
+ valueName?: string
45
+ /** Optional wrapper around each item. */
46
+ wrapComponent?: ElementType
47
+ /** Stable key per item (defaults to index). */
48
+ itemKey?: (item: T, index: number) => SimpleValue
49
+ /** Extra props merged onto the rendered component, optionally per-item. */
50
+ itemProps?: TObj | ((item: { [k: string]: T }, ext: ExtendedProps) => TObj)
51
+ /** Extra props merged onto the wrapper, optionally per-item. */
52
+ wrapProps?: TObj | ((item: { [k: string]: T }, ext: ExtendedProps) => TObj)
53
+ children?: never
54
+ }
55
+
56
+ /**
57
+ * Iterator over an array of objects. Each item is spread onto the rendered
58
+ * component as props. Per-item `component` overrides also work — when an
59
+ * item carries its own `component` field, the wrapper is bypassed.
60
+ */
61
+ export type ObjectProps<T extends ObjectValue> = {
62
+ data: Array<T | MaybeNull>
63
+ /** Default component to be rendered per item (item-level `component` overrides). */
64
+ component: ElementType
65
+ /** `valueName` is meaningless when iterating objects — TS forbids it. */
66
+ valueName?: never
67
+ /** Optional wrapper around each item. */
68
+ wrapComponent?: ElementType
69
+ /** Stable key per item — pick a key from the item, or compute it. */
70
+ itemKey?: keyof T | ((item: T, index: number) => SimpleValue)
71
+ /** Extra props merged onto the rendered component, optionally per-item. */
72
+ itemProps?: TObj | ((item: T, ext: ExtendedProps) => TObj)
73
+ /** Extra props merged onto the wrapper, optionally per-item. */
74
+ wrapProps?: TObj | ((item: T, ext: ExtendedProps) => TObj)
75
+ children?: never
76
+ }
77
+
78
+ /**
79
+ * Iterator over `children` — no `data`/`component`. Each child gets
80
+ * positional metadata via `itemProps` and an optional `wrapComponent`.
81
+ */
82
+ export type ChildrenProps = {
83
+ children: VNodeChild
84
+ data?: never
85
+ component?: never
86
+ valueName?: never
87
+ itemKey?: never
88
+ wrapComponent?: ElementType
89
+ itemProps?: TObj | ((_: Record<string, never>, ext: ExtendedProps) => TObj)
90
+ wrapProps?: TObj | ((_: Record<string, never>, ext: ExtendedProps) => TObj)
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Loose backward-compatible fallback shape (today's behavior)
95
+ //
96
+ // Used when callers don't (or can't) parameterize `Props<T>` — keeps the
97
+ // existing call surface intact so this refactor lands non-breaking.
98
+ // ---------------------------------------------------------------------------
99
+
26
100
  export type PropsCallback =
27
101
  | TObj
28
102
  | ((
@@ -30,50 +104,35 @@ export type PropsCallback =
30
104
  extendedProps: ExtendedProps,
31
105
  ) => TObj)
32
106
 
33
- export type Props = Partial<{
34
- /**
35
- * Valid children
36
- */
107
+ export type LooseProps = Partial<{
37
108
  children: VNodeChild
38
-
39
- /**
40
- * Array of data passed to `component` prop
41
- */
42
109
  data: Array<SimpleValue | ObjectValue | MaybeNull>
43
-
44
- /**
45
- * A component to be rendered within list
46
- */
47
110
  component: ElementType
48
-
49
- /**
50
- * Defines name of the prop to be passed to the iteration component
51
- * when **data** prop is type of `string[]`, `number[]` or combination
52
- * of both. Otherwise ignored.
53
- */
54
111
  valueName: string
55
-
56
- /**
57
- * A component to be rendered within list. `wrapComponent`
58
- * wraps `component`. Therefore it can be used to enhance the behavior
59
- * of the list component
60
- */
61
112
  wrapComponent: ElementType
62
-
63
- /**
64
- * Extension of **item** `component` props to be passed
65
- */
66
113
  itemProps: PropsCallback
67
-
68
- /**
69
- * Extension of **item** `wrapComponent` props to be passed
70
- */
71
- wrapProps?: PropsCallback
72
-
73
- /**
74
- * Extension of **item** `wrapComponent` props to be passed
75
- */
76
- itemKey?:
114
+ wrapProps: PropsCallback
115
+ itemKey:
77
116
  | keyof ObjectValue
78
117
  | ((item: SimpleValue | Omit<ObjectValue, 'component'>, index: number) => SimpleValue)
79
118
  }>
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Public, generic-aware Props<T>
122
+ //
123
+ // Props<string> → SimpleProps<string> (valueName REQUIRED)
124
+ // Props<{ id; name }> → ObjectProps<{...}> (valueName FORBIDDEN)
125
+ // Props<unknown> / Props → LooseProps (today's behavior)
126
+ //
127
+ // `unknown extends T` is the canonical "did the caller actually narrow T?"
128
+ // check — true only when T is left as `unknown`, false for any concrete
129
+ // narrowing. Fallback to LooseProps preserves existing call sites.
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export type Props<T = unknown> = unknown extends T
133
+ ? LooseProps
134
+ : T extends SimpleValue
135
+ ? SimpleProps<T>
136
+ : T extends ObjectValue
137
+ ? ObjectProps<T>
138
+ : ChildrenProps
@@ -5,7 +5,9 @@
5
5
  * support `display: flex` consistently across browsers.
6
6
  */
7
7
  import { splitProps } from '@pyreon/core'
8
+ import { getShouldBeEmpty } from '../../Element/utils'
8
9
  import { IS_DEVELOPMENT } from '../../utils'
10
+ import { internElementBundle } from '../internElementBundle'
9
11
  import Styled from './styled'
10
12
  import type { Props } from './types'
11
13
  import { isWebFixNeeded } from './utils'
@@ -41,46 +43,81 @@ const Component = (props: Partial<Props> & { ref?: unknown }) => {
41
43
 
42
44
  const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag)
43
45
 
46
+ // Void HTML elements (hr, input, img, br, …) cannot have children. Even
47
+ // a falsy `{own.children}` slot becomes `[undefined]` in the vnode and
48
+ // trips runtime-dom's void-element warning. Element already skips passing
49
+ // children to Wrapper for void tags; this guard makes sure the empty
50
+ // slot is dropped here too instead of leaking into the JSX.
51
+ const isVoidTag = !own.dangerouslySetInnerHTML && getShouldBeEmpty(own.tag)
52
+
53
+ // dangerouslySetInnerHTML and children are mutually exclusive — both
54
+ // become inner content (per `runtime-server/src/index.ts:228` and
55
+ // `runtime-dom/src/props.ts:289`). Pre-fix the prop was in OWN_KEYS,
56
+ // moved into `own` by splitProps, and never re-attached to the rendered
57
+ // vnode — so `<Logo dangerouslySetInnerHTML={...} />` rendered an empty
58
+ // <div></div>. Forward the prop to the styled vnode and drop the
59
+ // children slot when innerHTML is set.
60
+ const innerHTML = own.dangerouslySetInnerHTML
61
+
44
62
  if (!needsFix) {
63
+ const bundle = internElementBundle({
64
+ block: own.block,
65
+ direction: own.direction,
66
+ alignX: own.alignX,
67
+ alignY: own.alignY,
68
+ equalCols: own.equalCols,
69
+ extraStyles: own.extendCss,
70
+ })
71
+ if (isVoidTag) {
72
+ return <Styled {...commonProps} $element={bundle} />
73
+ }
74
+ if (innerHTML) {
75
+ return <Styled {...commonProps} $element={bundle} dangerouslySetInnerHTML={innerHTML} />
76
+ }
45
77
  return (
46
- <Styled
47
- {...commonProps}
48
- $element={{
49
- block: own.block,
50
- direction: own.direction,
51
- alignX: own.alignX,
52
- alignY: own.alignY,
53
- equalCols: own.equalCols,
54
- extraStyles: own.extendCss,
55
- }}
56
- >
78
+ <Styled {...commonProps} $element={bundle}>
57
79
  {own.children}
58
80
  </Styled>
59
81
  )
60
82
  }
61
83
 
62
84
  const asTag = own.isInline ? 'span' : 'div'
85
+ const parentBundle = internElementBundle({
86
+ parentFix: true as const,
87
+ block: own.block,
88
+ extraStyles: own.extendCss,
89
+ })
90
+ const childBundle = internElementBundle({
91
+ childFix: true as const,
92
+ direction: own.direction,
93
+ alignX: own.alignX,
94
+ alignY: own.alignY,
95
+ equalCols: own.equalCols,
96
+ })
97
+
98
+ // needsFix path: innerHTML belongs on the INNER styled node (where the
99
+ // actual content lives), NOT on the outer flex-fix wrapper. The
100
+ // `needsFix` computation already excludes the innerHTML case
101
+ // (`!own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag)`), so this
102
+ // branch normally won't execute when innerHTML is set — but we keep
103
+ // the defensive forwarding so the contract is robust against future
104
+ // refactors of the needsFix gate.
105
+ if (innerHTML) {
106
+ return (
107
+ <Styled {...commonProps} $element={parentBundle}>
108
+ <Styled
109
+ as={asTag}
110
+ $childFix
111
+ $element={childBundle}
112
+ dangerouslySetInnerHTML={innerHTML}
113
+ />
114
+ </Styled>
115
+ )
116
+ }
63
117
 
64
118
  return (
65
- <Styled
66
- {...commonProps}
67
- $element={{
68
- parentFix: true as const,
69
- block: own.block,
70
- extraStyles: own.extendCss,
71
- }}
72
- >
73
- <Styled
74
- as={asTag}
75
- $childFix
76
- $element={{
77
- childFix: true as const,
78
- direction: own.direction,
79
- alignX: own.alignX,
80
- alignY: own.alignY,
81
- equalCols: own.equalCols,
82
- }}
83
- >
119
+ <Styled {...commonProps} $element={parentBundle}>
120
+ <Styled as={asTag} $childFix $element={childBundle}>
84
121
  {own.children}
85
122
  </Styled>
86
123
  </Styled>
@@ -23,31 +23,25 @@ const parentFixCSS = `
23
23
  flex-direction: column;
24
24
  `
25
25
 
26
- const fullHeightCSS = `
27
- height: 100%;
28
- `
29
-
30
- const blockCSS = `
31
- align-self: stretch;
32
- flex: 1;
33
- min-width: 0;
34
- `
35
-
36
- const childFixPosition = (isBlock?: boolean) => `display: ${isBlock ? 'flex' : 'inline-flex'};`
37
-
38
- const styles: ResponsiveStylesCallback = ({ theme: t, css: cssFn }) => cssFn`
39
- ${t.alignY === 'block' && fullHeightCSS};
40
-
26
+ export const styles: ResponsiveStylesCallback = ({ theme: t, css: cssFn }) => cssFn`
41
27
  ${alignContent({
42
28
  direction: t.direction,
43
29
  alignX: t.alignX,
44
30
  alignY: t.alignY,
45
31
  })};
46
32
 
47
- ${t.block && blockCSS};
48
- ${t.alignY === 'block' && t.block && fullHeightCSS};
33
+ /*
34
+ * Always emit a value for the block-related properties so a responsive
35
+ * theme that flips from \`block: true\` at one breakpoint to \`block: false\`
36
+ * at another resets cleanly. Previously \`align-self\` / \`width\` / \`height\`
37
+ * were only set when the truthy branch matched, which left the prior
38
+ * breakpoint's values cascading through.
39
+ */
40
+ ${`align-self: ${t.block ? 'stretch' : 'auto'};
41
+ width: ${t.block ? '100%' : 'auto'};
42
+ height: ${t.alignY === 'block' ? '100%' : 'auto'};`};
49
43
 
50
- ${!t.childFix && childFixPosition(t.block)};
44
+ ${!t.childFix && `display: ${t.block ? 'flex' : 'inline-flex'};`};
51
45
  ${t.parentFix && parentFixCSS};
52
46
 
53
47
  ${t.extraStyles && extendCss(t.extraStyles as Parameters<typeof extendCss>[0])};
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Module-scope intern cache for `$element` bundles passed to Wrapper's styled
3
+ * component. Same primitive prop tuple → same object identity, so the styler's
4
+ * `elClassCache` (added 2026-Q2 alongside this) hits and skips the resolve
5
+ * pipeline. Analogous to `@pyreon/rocketstyle`'s dimension-prop memo (PR #344)
6
+ * but at the layer below — covers non-rocketstyle Element / Wrapper / Text usage
7
+ * AND the residual styled wrappers under any rocketstyle component.
8
+ *
9
+ * Cache key is a JSON-stringified shallow snapshot of the bundle. LRU-bound at
10
+ * 256 entries; oldest-first eviction. Bail (return the input as-is, no cache)
11
+ * when any value is a function (signal accessor) or a non-string object (CSS
12
+ * callback / CSSResult / nested object) — those cannot be safely round-tripped
13
+ * through JSON without losing identity guarantees.
14
+ */
15
+ const _bundleCache = new Map<string, Record<string, unknown>>()
16
+ const BUNDLE_CAP = 256
17
+
18
+ export const internElementBundle = <T extends Record<string, unknown>>(bundle: T): T => {
19
+ for (const k in bundle) {
20
+ const v = bundle[k]
21
+ if (typeof v === 'function') return bundle
22
+ if (v != null && typeof v === 'object') return bundle
23
+ }
24
+ const key = JSON.stringify(bundle)
25
+ const existing = _bundleCache.get(key)
26
+ if (existing) {
27
+ _bundleCache.delete(key)
28
+ _bundleCache.set(key, existing)
29
+ return existing as T
30
+ }
31
+ if (_bundleCache.size >= BUNDLE_CAP) {
32
+ const oldest = _bundleCache.keys().next().value
33
+ if (oldest !== undefined) _bundleCache.delete(oldest)
34
+ }
35
+ _bundleCache.set(key, bundle)
36
+ return bundle
37
+ }
package/src/index.ts CHANGED
@@ -3,12 +3,16 @@ import { Provider } from '@pyreon/unistyle'
3
3
  export type { ElementProps, PyreonElement } from './Element'
4
4
  export { Element } from './Element'
5
5
  export type {
6
+ ChildrenProps as IteratorChildrenProps,
6
7
  ElementType,
7
8
  ExtendedProps,
9
+ LooseProps as IteratorLooseProps,
8
10
  MaybeNull,
11
+ ObjectProps as IteratorObjectProps,
9
12
  ObjectValue,
10
13
  Props as IteratorProps,
11
14
  PropsCallback,
15
+ SimpleProps as IteratorSimpleProps,
12
16
  SimpleValue,
13
17
  } from './helpers/Iterator'
14
18
  export { default as Iterator } from './helpers/Iterator'
package/src/types.ts CHANGED
@@ -69,8 +69,39 @@ export type Responsive =
69
69
 
70
70
  export type ExtendCss = Css | Css[] | Partial<Record<BreakpointKeys, Css>>
71
71
 
72
- export type ExtractProps<TComponentOrTProps> =
73
- TComponentOrTProps extends ComponentFn<infer TProps> ? TProps : TComponentOrTProps
72
+ /**
73
+ * Extracts the props type from a Pyreon component function — multi-overload
74
+ * aware. Matches up to 4 call signatures and produces the UNION of their
75
+ * first-argument types. Iterator / List ship 3-overload primitives whose
76
+ * LAST overload is `ChildrenProps` (the loosest); without overload-aware
77
+ * extraction, `ExtractProps<Iterator>` returned just `ChildrenProps` and
78
+ * lost `SimpleProps<T>` / `ObjectProps<T>` shapes. Mirrors vitus-labs
79
+ * PR #222.
80
+ *
81
+ * Kept in sync with the copies in `@pyreon/core` / `@pyreon/attrs` /
82
+ * `@pyreon/rocketstyle`.
83
+ */
84
+ export type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends {
85
+ (props: infer P1, ...args: any): any
86
+ (props: infer P2, ...args: any): any
87
+ (props: infer P3, ...args: any): any
88
+ (props: infer P4, ...args: any): any
89
+ }
90
+ ? P1 | P2 | P3 | P4
91
+ : TComponentOrTProps extends {
92
+ (props: infer P1, ...args: any): any
93
+ (props: infer P2, ...args: any): any
94
+ (props: infer P3, ...args: any): any
95
+ }
96
+ ? P1 | P2 | P3
97
+ : TComponentOrTProps extends {
98
+ (props: infer P1, ...args: any): any
99
+ (props: infer P2, ...args: any): any
100
+ }
101
+ ? P1 | P2
102
+ : TComponentOrTProps extends ComponentFn<infer TProps>
103
+ ? TProps
104
+ : TComponentOrTProps
74
105
 
75
106
  export type PyreonComponent<P extends Record<string, any> = {}> = ComponentFn<P> & PyreonStatic
76
107
 
package/src/utils.ts CHANGED
@@ -2,7 +2,4 @@
2
2
  // literal-replaced so prod bundles tree-shake the dev branch to zero bytes.
3
3
  // Typed through a narrowing interface so downstream packages don't need
4
4
  // `vite/client` in their tsconfigs to type-check this file transitively.
5
- interface ViteMeta {
6
- readonly env?: { readonly DEV?: boolean }
7
- }
8
- export const IS_DEVELOPMENT: boolean = (import.meta as ViteMeta).env?.DEV === true
5
+ export const IS_DEVELOPMENT: boolean = process.env.NODE_ENV !== 'production'
@@ -1 +0,0 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/Element/types.ts","../../src/Element/component.tsx","../../src/helpers/Iterator/types.ts","../../src/helpers/Iterator/component.tsx","../../src/List/component.tsx","../../src/Overlay/context.tsx","../../src/Overlay/positioning.ts","../../src/Overlay/useOverlay.tsx","../../src/Overlay/component.tsx","../../src/Portal/component.tsx","../../src/Text/component.tsx","../../src/Util/component.tsx"],"mappings":"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"}