@pyreon/elements 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import { Provider } from "@pyreon/unistyle";
2
- import * as _$_pyreon_core0 from "@pyreon/core";
3
2
  import { ComponentFn, PyreonHTMLAttributes, VNodeChild } from "@pyreon/core";
4
3
  import { BreakpointKeys, HTMLTags, HTMLTextTags, config, render } from "@pyreon/ui-core";
5
- import * as _$_pyreon_reactivity0 from "@pyreon/reactivity";
6
4
 
7
5
  //#region src/types.d.ts
8
6
  type ExtractNullableKeys<T> = { [P in keyof T as T[P] extends null | undefined ? never : P]: T[P] };
@@ -313,7 +311,7 @@ interface OverlayContext {
313
311
  }
314
312
  declare const Component$2: (props: OverlayContext & {
315
313
  children: VNodeChild;
316
- }) => _$_pyreon_core0.VNode;
314
+ }) => import("@pyreon/core").VNode;
317
315
  //#endregion
318
316
  //#region src/Overlay/positioning.d.ts
319
317
  type Align$1 = 'bottom' | 'top' | 'left' | 'right';
@@ -361,10 +359,10 @@ declare const useOverlay: ({
361
359
  }?: Partial<UseOverlayProps>) => {
362
360
  triggerRef: (node: HTMLElement | null) => void;
363
361
  contentRef: (node: HTMLElement | null) => void;
364
- active: _$_pyreon_reactivity0.Signal<boolean>;
362
+ active: import("@pyreon/reactivity").Signal<boolean>;
365
363
  align: Align$1;
366
- alignX: _$_pyreon_reactivity0.Signal<AlignX$2>;
367
- alignY: _$_pyreon_reactivity0.Signal<AlignY$2>;
364
+ alignX: import("@pyreon/reactivity").Signal<AlignX$2>;
365
+ alignY: import("@pyreon/reactivity").Signal<AlignY$2>;
368
366
  showContent: () => void;
369
367
  hideContent: () => void;
370
368
  blocked: () => boolean;
@@ -372,8 +370,8 @@ declare const useOverlay: ({
372
370
  setUnblocked: () => void;
373
371
  setupListeners: () => () => void;
374
372
  Provider: (props: OverlayContext & {
375
- children: _$_pyreon_core0.VNodeChild;
376
- }) => _$_pyreon_core0.VNode;
373
+ children: import("@pyreon/core").VNodeChild;
374
+ }) => import("@pyreon/core").VNode;
377
375
  };
378
376
  //#endregion
379
377
  //#region src/Overlay/component.d.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/elements",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Foundational UI components for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,11 +42,12 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "devDependencies": {
45
- "@pyreon/core": "^0.18.0",
46
- "@pyreon/reactivity": "^0.18.0",
47
- "@pyreon/runtime-dom": "^0.18.0",
48
- "@pyreon/test-utils": "^0.13.5",
49
- "@pyreon/typescript": "^0.18.0",
45
+ "@pyreon/core": "^0.20.0",
46
+ "@pyreon/manifest": "0.13.1",
47
+ "@pyreon/reactivity": "^0.20.0",
48
+ "@pyreon/runtime-dom": "^0.20.0",
49
+ "@pyreon/test-utils": "^0.13.7",
50
+ "@pyreon/typescript": "^0.20.0",
50
51
  "@vitest/browser-playwright": "^4.1.4",
51
52
  "@vitus-labs/tools-rolldown": "^2.3.0"
52
53
  },
@@ -54,9 +55,9 @@
54
55
  "node": ">= 22"
55
56
  },
56
57
  "dependencies": {
57
- "@pyreon/core": "^0.18.0",
58
- "@pyreon/reactivity": "^0.18.0",
59
- "@pyreon/ui-core": "^0.18.0",
60
- "@pyreon/unistyle": "^0.18.0"
58
+ "@pyreon/core": "^0.20.0",
59
+ "@pyreon/reactivity": "^0.20.0",
60
+ "@pyreon/ui-core": "^0.20.0",
61
+ "@pyreon/unistyle": "^0.20.0"
61
62
  }
62
63
  }
@@ -3,7 +3,6 @@ import { h } from '@pyreon/core'
3
3
  import { describe, expect, it } from 'vitest'
4
4
  import { Element } from '../Element'
5
5
  import Content from '../helpers/Content/component'
6
- import Wrapper from '../helpers/Wrapper/component'
7
6
 
8
7
  const asVNode = (v: unknown) => v as VNode
9
8
 
@@ -0,0 +1,190 @@
1
+ import { defineManifest } from '@pyreon/manifest'
2
+
3
+ export default defineManifest({
4
+ name: '@pyreon/elements',
5
+ title: 'Base Primitives',
6
+ tagline:
7
+ 'Five foundational layout primitives — Element, Text, List, Overlay, Portal — plus the useOverlay positioning hook and the Iterator data helper',
8
+ description:
9
+ "The structural layer every styled / rocketstyle component renders through. `Element` is the responsive flexbox block (direction / alignX / alignY / gap / block + before/after content slots); `Text` is inline typography; `List` is a flowing-children container with the four-overload Iterator data API; `Overlay` is a positioned layer with backdrop driven by `useOverlay` (viewport flip, ESC, click-outside, scroll tracking, hover delay — never reimplement this); `Portal` renders children outside the DOM hierarchy into a per-instance wrapper. Element has a 2026-Q2 simple-path fast path that inlines the Wrapper helper for non-compound, non-needsFix tags (31-45% faster mount) — its rendered VNode then exposes the HTML tag as `props.as` and layout under `props.$element.{...}` instead of flat props.",
10
+ category: 'browser',
11
+ features: [
12
+ 'Element — responsive flexbox block: direction / alignX / alignY / gap / block, beforeContent / afterContent slots, equalBeforeAfter (ResizeObserver-tracked)',
13
+ 'Text — inline typography primitive',
14
+ 'List — flowing-children container; data via the four-overload Iterator API',
15
+ 'Overlay + useOverlay — positioned layer (dropdown/modal/tooltip) with viewport flip, ESC, click-outside, scroll tracking, hover delay',
16
+ 'Portal — renders children into a per-instance wrapper inside a DOMLocation (default document.body)',
17
+ 'Iterator — Simple / Object / Children / Loose overloads keep primitive-vs-object iteration modes type-safe',
18
+ 'Util — bare utility primitive; Provider re-exported from @pyreon/unistyle',
19
+ 'Simple-path fast path: non-compound Elements skip a component invocation + splitProps + mountChild',
20
+ ],
21
+ api: [
22
+ {
23
+ name: 'Element',
24
+ kind: 'component',
25
+ signature: 'Element(props: ElementProps): VNodeChild',
26
+ summary:
27
+ "The responsive flexbox block primitive every layout-bearing component renders through. Layout props live here (NOT in a styler `.theme()`): `direction` (`inline` | `rows` | `reverseInline` | `reverseRows` — note `row` is INVALID), `alignX`, `alignY`, `gap`, `block`, plus `beforeContent` / `afterContent` slot wrappers and `equalBeforeAfter` (equalizes the slot widths on mount AND keeps them equal via ResizeObserver). The 2026-Q2 simple-path fast path inlines the Wrapper for non-compound, non-needsFix tags: the rendered VNode then exposes the HTML tag as `props.as` and layout under `props.$element.{direction,alignX,alignY,block,equalCols,extraStyles}` rather than flat props (styled-components consumers see no change since `as` is the canonical tag selector).",
28
+ example: `import { Element } from "@pyreon/elements"
29
+
30
+ <Element tag="section" direction="rows" gap="md" alignX="center">
31
+ <Header />
32
+ <Body />
33
+ </Element>`,
34
+ mistakes: [
35
+ 'Using `direction="row"` — invalid; the values are `inline` / `rows` / `reverseInline` / `reverseRows`',
36
+ 'Putting layout props in a styler `.theme()` callback — `direction` / `alignX` / `alignY` / `gap` / `block` are Element ATTRS, not CSS; theme is for colors / spacing / borders',
37
+ 'Reading flat `props.direction` on a simple-path Element in a test or styled consumer — the fast path moves layout to `props.$element.*` and the tag to `props.as`; read both shapes via a helper',
38
+ 'Passing children to a void `tag` (`hr` / `img` / `br` / `input`) — Element correctly drops them; do not rely on a children slot for void tags',
39
+ 'Relying on `equalBeforeAfter` measuring async slot content where `ResizeObserver` is undefined (older runtimes / SSR) — it falls back to the one-shot mount measurement there',
40
+ ],
41
+ seeAlso: ['Text', 'List', 'Portal'],
42
+ },
43
+ {
44
+ name: 'Text',
45
+ kind: 'component',
46
+ signature: 'Text(props: TextProps): VNodeChild',
47
+ summary:
48
+ 'Inline typography primitive — the text counterpart to `Element`. Carries typography props and renders an inline element; use it for runs of text that need the design-system typography contract rather than a raw `<span>`. Like `Element`, visual styling belongs in the styler/rocketstyle layer; `Text` owns the inline-flow structure.',
49
+ example: `import { Text } from "@pyreon/elements"
50
+
51
+ <Text tag="span">Inline label</Text>`,
52
+ seeAlso: ['Element'],
53
+ },
54
+ {
55
+ name: 'List',
56
+ kind: 'component',
57
+ signature: 'List(props: ListProps): VNodeChild',
58
+ summary:
59
+ 'A flowing-children container (`ul` / `ol` / `dl` / custom) built on the Iterator data API. Render children directly OR drive it with `data` + a `component` renderer. Inherits Iterator’s four typed overloads (Simple / Object / Children / Loose) and additionally blocks Element-only `label` / `content` props at the type level.',
60
+ example: `import { List } from "@pyreon/elements"
61
+
62
+ <List tag="ul" data={items()} component={(item) => <li>{item.name}</li>} />`,
63
+ mistakes: [
64
+ 'Mixing primitive and object entries in `data` (`[1, {id:1}, null]`) — primitive arrays and object arrays are mutually exclusive iteration modes; the typed overloads reject the mix for direct callers',
65
+ 'Passing `valueName` with an object-array `data` — `valueName` is a Simple-mode (primitive) prop only',
66
+ 'Passing `children` AND `data`/`component` — Children mode and Object mode are distinct overloads; pick one',
67
+ ],
68
+ seeAlso: ['Iterator', 'Element'],
69
+ },
70
+ {
71
+ name: 'Overlay',
72
+ kind: 'component',
73
+ signature: 'Overlay(props: OverlayProps): VNodeChild',
74
+ summary:
75
+ 'A positioned layer (dropdown / modal / tooltip / popover) with an optional backdrop, driven internally by `useOverlay`. It handles viewport flipping, ESC-to-close, click-outside, scroll tracking, and hover delay — do NOT reimplement any of that in a primitive; compose `Overlay` (or `useOverlay`) instead. Renders through `Portal` so the layer escapes overflow/stacking contexts.',
76
+ example: `import { Overlay } from "@pyreon/elements"
77
+
78
+ <Overlay isOpen={open()} type="dropdown" align="bottom" onClose={() => open.set(false)}>
79
+ <Menu />
80
+ </Overlay>`,
81
+ mistakes: [
82
+ 'Hand-rolling positioning / flip / click-outside / ESC logic in a tooltip or dropdown primitive — `useOverlay` already owns all of it; reimplementing drifts from the shared behavior',
83
+ 'Reading the rendered overlay as `document.body.firstChild` — it renders through `Portal` into a per-instance wrapper; traverse the wrapper, not body’s direct child',
84
+ ],
85
+ seeAlso: ['useOverlay', 'OverlayProvider', 'Portal'],
86
+ },
87
+ {
88
+ name: 'useOverlay',
89
+ kind: 'hook',
90
+ signature:
91
+ 'useOverlay(props?: Partial<UseOverlayProps>): { isOpen, open, close, toggle, triggerProps, overlayProps, /* … */ }',
92
+ summary:
93
+ 'The positioning + interaction engine `Overlay` is built on, exposed for headless consumers. Options: `openOn` / `closeOn` (`click` | `hover` | …), `type` (`dropdown` | `modal` | …), `position` (`fixed` | …), `align` + `alignX` / `alignY` + `offsetX` / `offsetY`, `closeOnEsc`, `hoverDelay`, `throttleDelay`, `parentContainer`, `disabled`, `onOpen` / `onClose`. SSR-safe: the internal positioning helpers early-return under no-`window` so the contract is documented at the call site rather than crashing on the server.',
94
+ example: `import { useOverlay } from "@pyreon/elements"
95
+
96
+ const o = useOverlay({ openOn: "hover", type: "tooltip", hoverDelay: 150 })
97
+ // spread o.triggerProps on the anchor, o.overlayProps on the floating layer`,
98
+ mistakes: [
99
+ 'Passing `align` as a function accessor — it is a value option, not a signal accessor; let the compiler wrap reactive values',
100
+ 'Expecting positioning to run during SSR — the helpers are guarded and no-op without `window`; positioning happens post-mount on the client',
101
+ 'Reaching for `addEventListener` for outside-click / scroll instead of letting `useOverlay` own the listener lifecycle — it self-cleans on unmount',
102
+ ],
103
+ seeAlso: ['Overlay', 'OverlayProvider'],
104
+ },
105
+ {
106
+ name: 'OverlayProvider',
107
+ kind: 'component',
108
+ signature: 'OverlayProvider(props: { children?: VNodeChild }): VNodeChild',
109
+ summary:
110
+ 'Context provider that lets nested overlays coordinate (shared root, stacking, outside-click scoping). `useOverlay` reads it via `useOverlayContext`. Marked `nativeCompat` so it works correctly inside `@pyreon/{react,preact,vue,solid}-compat` apps (its `provide()` runs in Pyreon’s setup frame, not the compat wrapper accessor).',
111
+ example: `import { OverlayProvider } from "@pyreon/elements"
112
+
113
+ <OverlayProvider>
114
+ <App />
115
+ </OverlayProvider>`,
116
+ seeAlso: ['useOverlay', 'Overlay'],
117
+ },
118
+ {
119
+ name: 'Portal',
120
+ kind: 'component',
121
+ signature: 'Portal(props: PortalProps): VNodeChild',
122
+ summary:
123
+ 'Renders children OUTSIDE the parent DOM hierarchy — into a PER-INSTANCE wrapper element (default `<div>`, configurable via `tag`) created inside a `DOMLocation` (default `document.body`). Multiple Portals sharing a location each get their OWN wrapper so children never intermingle, which gives cleanup isolation when several modals / tooltips share a portal root.',
124
+ example: `import { Portal } from "@pyreon/elements"
125
+
126
+ <Portal>
127
+ <Modal />
128
+ </Portal>`,
129
+ mistakes: [
130
+ 'Asserting `document.body.firstChild === modalRoot` in a test — the Portal nests one level deeper; query the per-instance wrapper (`document.body.querySelector("[data-…]").parentElement`) instead',
131
+ 'Assuming all Portals share one container — each instance gets its own wrapper inside the DOMLocation; they do not merge',
132
+ ],
133
+ seeAlso: ['Overlay', 'Element'],
134
+ },
135
+ {
136
+ name: 'Iterator',
137
+ kind: 'component',
138
+ signature: 'Iterator<T>(props: IteratorProps<T>): VNodeChild',
139
+ summary:
140
+ 'The data-iteration helper backing `List` (default export of `helpers/Iterator`). FOUR typed overloads keep iteration modes honest: `SimpleProps<T>` (primitive arrays — `valueName` allowed), `ObjectProps<T>` (object arrays — `valueName` and `children` FORBIDDEN), `ChildrenProps` (no data/component, only children), and a `LooseProps` fallback that exists so rocketstyle/attrs forwarding patterns (`<Iterator {...wrapperProps} />`) bind without a per-call-site overload error. The discriminator picks the overload via `unknown extends T ? Loose : T extends SimpleValue ? Simple : T extends ObjectValue ? Object : Children`.',
141
+ example: `import Iterator from "@pyreon/elements/helpers/Iterator"
142
+
143
+ <Iterator data={users()} component={(u) => <Row user={u} />} />`,
144
+ mistakes: [
145
+ 'Mixed-shape `data` (`[1, {id:1}, null]`) — primitive and object iteration are mutually exclusive; the narrow overloads reject it (the Loose fallback only catches forwarding-pattern shapes)',
146
+ '`valueName` with object-array `data` — Simple-mode only; ObjectProps forbids it',
147
+ '`children` together with `data`/`component` — Children and Object are distinct overloads; the runtime picks the mode by which props are populated, but the types steer you to one',
148
+ ],
149
+ seeAlso: ['List'],
150
+ },
151
+ {
152
+ name: 'Util',
153
+ kind: 'component',
154
+ signature: 'Util(props: UtilProps): VNodeChild',
155
+ summary:
156
+ 'A bare utility primitive — the minimal structural wrapper when you need an Element-family node without layout semantics (no flex direction / align). Use it for thin passthrough containers where `Element` would impose unwanted flex defaults.',
157
+ example: `import { Util } from "@pyreon/elements"
158
+
159
+ <Util>{children}</Util>`,
160
+ seeAlso: ['Element'],
161
+ },
162
+ {
163
+ name: 'Provider',
164
+ kind: 'component',
165
+ signature: 'Provider(props: { children?: VNodeChild }): VNodeChild',
166
+ summary:
167
+ 'Re-exported from `@pyreon/unistyle` for convenience (responsive/breakpoint context). Most apps mount the unified `<PyreonUI>` from `@pyreon/ui-core` instead, which wires this internally — reach for the bare `Provider` only outside the `ui-core` provider tree.',
168
+ example: `import { Provider } from "@pyreon/elements"`,
169
+ seeAlso: ['Element'],
170
+ },
171
+ ],
172
+ gotchas: [
173
+ {
174
+ label: 'Layout in attrs, not theme',
175
+ note: 'Element/Text/List layout props (`direction`, `alignX`, `alignY`, `gap`, `block`, `tag`) are primitive ATTRS — they target the inner flex wrapper. Colors / spacing / borders / shadows belong in the styler or rocketstyle `.theme()` layer. `direction` accepts `inline` / `rows` / `reverseInline` / `reverseRows` — `row` is invalid.',
176
+ },
177
+ {
178
+ label: 'Simple-path fast path changed the VNode shape',
179
+ note: 'A non-compound, non-needsFix Element renders a VNode exposing the HTML tag as `props.as` and layout under `props.$element.{direction,alignX,alignY,block,equalCols,extraStyles}` — NOT flat `props.{tag,direction,…}`. Production styled-components consumers are unaffected (`as` is canonical); test/introspection code must read both shapes.',
180
+ },
181
+ {
182
+ label: 'Overlay positioning is centralised',
183
+ note: 'Viewport flip, ESC, click-outside, scroll tracking, hover delay all live in `useOverlay`. Never reimplement them in a tooltip / dropdown / popover primitive — compose `Overlay` or `useOverlay`. The positioning helpers are SSR-guarded (no-op without `window`).',
184
+ },
185
+ {
186
+ label: 'Portal nests a per-instance wrapper',
187
+ note: '`Portal` renders into its OWN wrapper element inside the DOMLocation (default `document.body`), not directly as a body child. DOM assertions must traverse one extra level; multiple Portals do not share a container.',
188
+ },
189
+ ],
190
+ })
@@ -0,0 +1,45 @@
1
+ import {
2
+ renderApiReferenceEntries,
3
+ renderLlmsFullSection,
4
+ renderLlmsTxtLine,
5
+ } from '@pyreon/manifest'
6
+ import manifest from '../manifest'
7
+
8
+ describe('gen-docs — elements snapshot', () => {
9
+ it('renders a llms.txt bullet starting with the package prefix', () => {
10
+ const line = renderLlmsTxtLine(manifest)
11
+ expect(line.startsWith('- @pyreon/elements —')).toBe(true)
12
+ })
13
+
14
+ it('renders a llms-full.txt section with the right header', () => {
15
+ const section = renderLlmsFullSection(manifest)
16
+ expect(section.startsWith('## @pyreon/elements —')).toBe(true)
17
+ expect(section).toContain('```typescript')
18
+ })
19
+
20
+ it('renders MCP api-reference entries for every api[] item', () => {
21
+ const record = renderApiReferenceEntries(manifest)
22
+ expect(Object.keys(record).sort()).toEqual([
23
+ 'elements/Element',
24
+ 'elements/Iterator',
25
+ 'elements/List',
26
+ 'elements/Overlay',
27
+ 'elements/OverlayProvider',
28
+ 'elements/Portal',
29
+ 'elements/Provider',
30
+ 'elements/Text',
31
+ 'elements/Util',
32
+ 'elements/useOverlay',
33
+ ])
34
+ })
35
+
36
+ it('carries the layout-in-attrs foot-gun on the Element entry', () => {
37
+ const record = renderApiReferenceEntries(manifest)
38
+ expect(record['elements/Element']?.mistakes).toContain('Using `direction="row"` — invalid')
39
+ })
40
+
41
+ it('flags the Portal per-instance-wrapper assertion trap', () => {
42
+ const record = renderApiReferenceEntries(manifest)
43
+ expect(record['elements/Portal']?.mistakes).toContain('document.body.firstChild')
44
+ })
45
+ })