@pyreon/elements 0.13.1 → 0.15.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,8 @@
1
1
  import { Provider } from "@pyreon/unistyle";
2
- import * as _pyreon_core0 from "@pyreon/core";
2
+ import * as _$_pyreon_core0 from "@pyreon/core";
3
3
  import { ComponentFn, PyreonHTMLAttributes, VNodeChild } from "@pyreon/core";
4
4
  import { BreakpointKeys, HTMLTags, HTMLTextTags, config, render } from "@pyreon/ui-core";
5
- import * as _pyreon_reactivity0 from "@pyreon/reactivity";
5
+ import * as _$_pyreon_reactivity0 from "@pyreon/reactivity";
6
6
 
7
7
  //#region src/types.d.ts
8
8
  type ExtractNullableKeys<T> = { [P in keyof T as T[P] extends null | undefined ? never : P]: T[P] };
@@ -256,7 +256,7 @@ interface OverlayContext {
256
256
  }
257
257
  declare const Component$3: (props: OverlayContext & {
258
258
  children: VNodeChild;
259
- }) => _pyreon_core0.VNode;
259
+ }) => _$_pyreon_core0.VNode;
260
260
  //#endregion
261
261
  //#region src/Overlay/positioning.d.ts
262
262
  type Align$1 = 'bottom' | 'top' | 'left' | 'right';
@@ -304,10 +304,10 @@ declare const useOverlay: ({
304
304
  }?: Partial<UseOverlayProps>) => {
305
305
  triggerRef: (node: HTMLElement | null) => void;
306
306
  contentRef: (node: HTMLElement | null) => void;
307
- active: _pyreon_reactivity0.Signal<boolean>;
307
+ active: _$_pyreon_reactivity0.Signal<boolean>;
308
308
  align: Align$1;
309
- alignX: _pyreon_reactivity0.Signal<AlignX$2>;
310
- alignY: _pyreon_reactivity0.Signal<AlignY$2>;
309
+ alignX: _$_pyreon_reactivity0.Signal<AlignX$2>;
310
+ alignY: _$_pyreon_reactivity0.Signal<AlignY$2>;
311
311
  showContent: () => void;
312
312
  hideContent: () => void;
313
313
  blocked: () => boolean;
@@ -315,8 +315,8 @@ declare const useOverlay: ({
315
315
  setUnblocked: () => void;
316
316
  setupListeners: () => () => void;
317
317
  Provider: (props: OverlayContext & {
318
- children: _pyreon_core0.VNodeChild;
319
- }) => _pyreon_core0.VNode;
318
+ children: _$_pyreon_core0.VNodeChild;
319
+ }) => _$_pyreon_core0.VNode;
320
320
  };
321
321
  //#endregion
322
322
  //#region src/Overlay/component.d.ts
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Provider, alignContent, extendCss, makeItResponsive, value } from "@pyreon/unistyle";
2
- import { Fragment, Portal, createContext, onMount, provide, splitProps, useContext } from "@pyreon/core";
2
+ import { Fragment, Portal, createContext, nativeCompat, onMount, provide, splitProps, useContext } from "@pyreon/core";
3
3
  import { config, isEmpty, omit, pick, render, throttle } from "@pyreon/ui-core";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "@pyreon/core/jsx-runtime";
4
5
  import { signal } from "@pyreon/reactivity";
5
6
 
6
7
  //#region src/constants.ts
@@ -8,7 +9,7 @@ const PKG_NAME = "@pyreon/elements";
8
9
 
9
10
  //#endregion
10
11
  //#region src/utils.ts
11
- const IS_DEVELOPMENT = import.meta.env?.DEV === true;
12
+ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
12
13
 
13
14
  //#endregion
14
15
  //#region src/helpers/Content/styled.ts
@@ -82,58 +83,6 @@ const StyledComponent = styled$2(component$1, { layer: "elements" })`
82
83
  })};
83
84
  `;
84
85
 
85
- //#endregion
86
- //#region ../../core/core/lib/jsx-runtime.js
87
- /** Marker for fragment nodes — renders children without a wrapper element */
88
- const Fragment$1 = Symbol("Pyreon.Fragment");
89
- /**
90
- * Hyperscript function — the compiled output of JSX.
91
- * `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
92
- *
93
- * Generic on P so TypeScript validates props match the component's signature
94
- * at the call site, then stores the result in the loosely-typed VNode.
95
- */
96
- /** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
97
- const EMPTY_PROPS = {};
98
- function h(type, props, ...children) {
99
- return {
100
- type,
101
- props: props ?? EMPTY_PROPS,
102
- children: normalizeChildren(children),
103
- key: props?.key ?? null
104
- };
105
- }
106
- function normalizeChildren(children) {
107
- for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
108
- return children;
109
- }
110
- function flattenChildren(children) {
111
- const result = [];
112
- for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
113
- else result.push(child);
114
- return result;
115
- }
116
- /**
117
- * JSX automatic runtime.
118
- *
119
- * When tsconfig has `"jsxImportSource": "@pyreon/core"`, the TS/bundler compiler
120
- * rewrites JSX to imports from this file automatically:
121
- * <div class="x" /> → jsx("div", { class: "x" })
122
- */
123
- function jsx(type, props, key) {
124
- const { children, ...rest } = props;
125
- const propsWithKey = key != null ? {
126
- ...rest,
127
- key
128
- } : rest;
129
- if (typeof type === "function") return h(type, children !== void 0 ? {
130
- ...propsWithKey,
131
- children
132
- } : propsWithKey);
133
- return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
134
- }
135
- const jsxs = jsx;
136
-
137
86
  //#endregion
138
87
  //#region src/helpers/Content/component.tsx
139
88
  /**
@@ -182,6 +131,120 @@ const Component$9 = (props) => {
182
131
  //#region src/helpers/Content/index.ts
183
132
  var Content_default = Component$9;
184
133
 
134
+ //#endregion
135
+ //#region src/Element/constants.ts
136
+ /**
137
+ * HTML tags that are inline-level by default. When Element renders one of
138
+ * these tags, child Content wrappers use `span` instead of `div` to
139
+ * preserve valid HTML nesting.
140
+ */
141
+ const INLINE_ELEMENTS = {
142
+ span: true,
143
+ a: true,
144
+ button: true,
145
+ input: true,
146
+ label: true,
147
+ select: true,
148
+ textarea: true,
149
+ br: true,
150
+ img: true,
151
+ strong: true,
152
+ small: true,
153
+ code: true,
154
+ b: true,
155
+ big: true,
156
+ i: true,
157
+ tt: true,
158
+ abbr: true,
159
+ acronym: true,
160
+ cite: true,
161
+ dfn: true,
162
+ em: true,
163
+ kbd: true,
164
+ samp: true,
165
+ var: true,
166
+ bdo: true,
167
+ map: true,
168
+ object: true,
169
+ q: true,
170
+ script: true,
171
+ sub: true,
172
+ sup: true
173
+ };
174
+ /**
175
+ * HTML void/self-closing elements that cannot have children. When Element
176
+ * detects one of these tags, it skips rendering beforeContent/content/afterContent
177
+ * and returns the Wrapper alone.
178
+ */
179
+ const EMPTY_ELEMENTS = {
180
+ area: true,
181
+ base: true,
182
+ br: true,
183
+ col: true,
184
+ embed: true,
185
+ hr: true,
186
+ img: true,
187
+ input: true,
188
+ keygen: true,
189
+ link: true,
190
+ textarea: true,
191
+ source: true,
192
+ track: true,
193
+ wbr: true
194
+ };
195
+
196
+ //#endregion
197
+ //#region src/Element/utils.ts
198
+ /** Checks whether the given HTML tag is an inline-level element, used to determine sub-tag nesting. */
199
+ const isInlineElement = (tag) => {
200
+ if (tag && tag in INLINE_ELEMENTS) return true;
201
+ return false;
202
+ };
203
+ /** Checks whether the given HTML tag is a void element that cannot have children. */
204
+ const getShouldBeEmpty = (tag) => {
205
+ if (tag && tag in EMPTY_ELEMENTS) return true;
206
+ return false;
207
+ };
208
+
209
+ //#endregion
210
+ //#region src/helpers/internElementBundle.ts
211
+ /**
212
+ * Module-scope intern cache for `$element` bundles passed to Wrapper's styled
213
+ * component. Same primitive prop tuple → same object identity, so the styler's
214
+ * `elClassCache` (added 2026-Q2 alongside this) hits and skips the resolve
215
+ * pipeline. Analogous to `@pyreon/rocketstyle`'s dimension-prop memo (PR #344)
216
+ * but at the layer below — covers non-rocketstyle Element / Wrapper / Text usage
217
+ * AND the residual styled wrappers under any rocketstyle component.
218
+ *
219
+ * Cache key is a JSON-stringified shallow snapshot of the bundle. LRU-bound at
220
+ * 256 entries; oldest-first eviction. Bail (return the input as-is, no cache)
221
+ * when any value is a function (signal accessor) or a non-string object (CSS
222
+ * callback / CSSResult / nested object) — those cannot be safely round-tripped
223
+ * through JSON without losing identity guarantees.
224
+ */
225
+ const _bundleCache = /* @__PURE__ */ new Map();
226
+ const BUNDLE_CAP = 256;
227
+ const internElementBundle = (bundle) => {
228
+ for (const k in bundle) {
229
+ const v = bundle[k];
230
+ if (typeof v === "function") return bundle;
231
+ if (v != null && typeof v === "object") return bundle;
232
+ }
233
+ const key = JSON.stringify(bundle);
234
+ const existing = _bundleCache.get(key);
235
+ if (existing) {
236
+ _bundleCache.delete(key);
237
+ _bundleCache.set(key, existing);
238
+ return existing;
239
+ }
240
+ if (_bundleCache.size >= BUNDLE_CAP) {
241
+ const oldest = _bundleCache.keys().next().value;
242
+ if (oldest !== void 0) _bundleCache.delete(oldest);
243
+ }
244
+ _bundleCache.set(key, bundle);
245
+ return bundle;
246
+ };
247
+
185
248
  //#endregion
186
249
  //#region src/helpers/Wrapper/styled.ts
187
250
  /**
@@ -292,36 +355,47 @@ const Component$8 = (props) => {
292
355
  ref: own.ref,
293
356
  as: own.tag
294
357
  };
295
- if (!(!own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag))) return /* @__PURE__ */ jsx(styled_default$1, {
296
- ...commonProps,
297
- $element: {
358
+ const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag);
359
+ const isVoidTag = !own.dangerouslySetInnerHTML && getShouldBeEmpty(own.tag);
360
+ if (!needsFix) {
361
+ const bundle = internElementBundle({
298
362
  block: own.block,
299
363
  direction: own.direction,
300
364
  alignX: own.alignX,
301
365
  alignY: own.alignY,
302
366
  equalCols: own.equalCols,
303
367
  extraStyles: own.extendCss
304
- },
305
- children: own.children
306
- });
368
+ });
369
+ if (isVoidTag) return /* @__PURE__ */ jsx(styled_default$1, {
370
+ ...commonProps,
371
+ $element: bundle
372
+ });
373
+ return /* @__PURE__ */ jsx(styled_default$1, {
374
+ ...commonProps,
375
+ $element: bundle,
376
+ children: own.children
377
+ });
378
+ }
307
379
  const asTag = own.isInline ? "span" : "div";
380
+ const parentBundle = internElementBundle({
381
+ parentFix: true,
382
+ block: own.block,
383
+ extraStyles: own.extendCss
384
+ });
385
+ const childBundle = internElementBundle({
386
+ childFix: true,
387
+ direction: own.direction,
388
+ alignX: own.alignX,
389
+ alignY: own.alignY,
390
+ equalCols: own.equalCols
391
+ });
308
392
  return /* @__PURE__ */ jsx(styled_default$1, {
309
393
  ...commonProps,
310
- $element: {
311
- parentFix: true,
312
- block: own.block,
313
- extraStyles: own.extendCss
314
- },
394
+ $element: parentBundle,
315
395
  children: /* @__PURE__ */ jsx(styled_default$1, {
316
396
  as: asTag,
317
397
  $childFix: true,
318
- $element: {
319
- childFix: true,
320
- direction: own.direction,
321
- alignX: own.alignX,
322
- alignY: own.alignY,
323
- equalCols: own.equalCols
324
- },
398
+ $element: childBundle,
325
399
  children: own.children
326
400
  })
327
401
  });
@@ -331,81 +405,6 @@ const Component$8 = (props) => {
331
405
  //#region src/helpers/Wrapper/index.ts
332
406
  var Wrapper_default = Component$8;
333
407
 
334
- //#endregion
335
- //#region src/Element/constants.ts
336
- /**
337
- * HTML tags that are inline-level by default. When Element renders one of
338
- * these tags, child Content wrappers use `span` instead of `div` to
339
- * preserve valid HTML nesting.
340
- */
341
- const INLINE_ELEMENTS = {
342
- span: true,
343
- a: true,
344
- button: true,
345
- input: true,
346
- label: true,
347
- select: true,
348
- textarea: true,
349
- br: true,
350
- img: true,
351
- strong: true,
352
- small: true,
353
- code: true,
354
- b: true,
355
- big: true,
356
- i: true,
357
- tt: true,
358
- abbr: true,
359
- acronym: true,
360
- cite: true,
361
- dfn: true,
362
- em: true,
363
- kbd: true,
364
- samp: true,
365
- var: true,
366
- bdo: true,
367
- map: true,
368
- object: true,
369
- q: true,
370
- script: true,
371
- sub: true,
372
- sup: true
373
- };
374
- /**
375
- * HTML void/self-closing elements that cannot have children. When Element
376
- * detects one of these tags, it skips rendering beforeContent/content/afterContent
377
- * and returns the Wrapper alone.
378
- */
379
- const EMPTY_ELEMENTS = {
380
- area: true,
381
- base: true,
382
- br: true,
383
- col: true,
384
- embed: true,
385
- hr: true,
386
- img: true,
387
- input: true,
388
- keygen: true,
389
- link: true,
390
- textarea: true,
391
- source: true,
392
- track: true,
393
- wbr: true
394
- };
395
-
396
- //#endregion
397
- //#region src/Element/utils.ts
398
- /** Checks whether the given HTML tag is an inline-level element, used to determine sub-tag nesting. */
399
- const isInlineElement = (tag) => {
400
- if (tag && tag in INLINE_ELEMENTS) return true;
401
- return false;
402
- };
403
- /** Checks whether the given HTML tag is a void element that cannot have children. */
404
- const getShouldBeEmpty = (tag) => {
405
- if (tag && tag in EMPTY_ELEMENTS) return true;
406
- return false;
407
- };
408
-
409
408
  //#endregion
410
409
  //#region src/Element/component.tsx
411
410
  /**
@@ -416,6 +415,7 @@ const getShouldBeEmpty = (tag) => {
416
415
  * like void elements (input, img) and inline elements (span, a) by
417
416
  * skipping children or switching sub-tags accordingly.
418
417
  */
418
+ const WRAPPER_DEV_PROPS = IS_DEVELOPMENT ? { "data-pyr-element": "Element" } : {};
419
419
  const equalize = (el, direction) => {
420
420
  const beforeEl = el.firstElementChild;
421
421
  const afterEl = el.lastElementChild;
@@ -515,6 +515,28 @@ const Component = (props) => {
515
515
  ...rest,
516
516
  ...WRAPPER_PROPS
517
517
  });
518
+ const needsFix = !rest.dangerouslySetInnerHTML && isWebFixNeeded(own.tag);
519
+ if (isSimpleElement && !needsFix) return /* @__PURE__ */ jsx(styled_default$1, {
520
+ ...rest,
521
+ ...WRAPPER_DEV_PROPS,
522
+ ref: mergedRef,
523
+ as: own.tag,
524
+ $element: internElementBundle({
525
+ block: own.block,
526
+ direction: wrapperDirection,
527
+ alignX: wrapperAlignX,
528
+ alignY: wrapperAlignY,
529
+ equalCols: own.equalCols,
530
+ extraStyles: own.css
531
+ }),
532
+ children: render(getChildren())
533
+ });
534
+ if (isSimpleElement) return /* @__PURE__ */ jsx(Wrapper_default, {
535
+ ...rest,
536
+ ...WRAPPER_PROPS,
537
+ isInline,
538
+ children: render(getChildren())
539
+ });
518
540
  return /* @__PURE__ */ jsxs(Wrapper_default, {
519
541
  ...rest,
520
542
  ...WRAPPER_PROPS,
@@ -532,7 +554,7 @@ const Component = (props) => {
532
554
  gap: own.gap,
533
555
  children: own.beforeContent
534
556
  }),
535
- isSimpleElement ? render(getChildren()) : /* @__PURE__ */ jsx(Content_default, {
557
+ /* @__PURE__ */ jsx(Content_default, {
536
558
  tag: SUB_TAG,
537
559
  contentType: "content",
538
560
  parentDirection: wrapperDirection,
@@ -753,6 +775,7 @@ const Component$3 = (props) => {
753
775
  });
754
776
  return /* @__PURE__ */ jsx(Fragment$1, { children: props.children });
755
777
  };
778
+ nativeCompat(Component$3);
756
779
 
757
780
  //#endregion
758
781
  //#region src/Overlay/positioning.ts
@@ -1209,6 +1232,7 @@ const name$3 = `${PKG_NAME}/Overlay`;
1209
1232
  Component$2.displayName = name$3;
1210
1233
  Component$2.pkgName = PKG_NAME;
1211
1234
  Component$2.PYREON__COMPONENT = name$3;
1235
+ nativeCompat(Component$2);
1212
1236
 
1213
1237
  //#endregion
1214
1238
  //#region src/Portal/component.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/elements",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "Foundational UI components for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "lib",
13
+ "!lib/**/*.map",
13
14
  "!lib/analysis",
14
15
  "README.md",
15
16
  "LICENSE",
@@ -41,19 +42,19 @@
41
42
  "typecheck": "tsc --noEmit"
42
43
  },
43
44
  "devDependencies": {
44
- "@pyreon/core": "^0.13.1",
45
- "@pyreon/reactivity": "^0.13.1",
46
- "@pyreon/runtime-dom": "^0.13.1",
47
- "@pyreon/test-utils": "^0.13.1",
48
- "@pyreon/typescript": "^0.13.1",
45
+ "@pyreon/core": "^0.15.0",
46
+ "@pyreon/reactivity": "^0.15.0",
47
+ "@pyreon/runtime-dom": "^0.15.0",
48
+ "@pyreon/test-utils": "^0.13.2",
49
+ "@pyreon/typescript": "^0.15.0",
49
50
  "@vitest/browser-playwright": "^4.1.4",
50
- "@vitus-labs/tools-rolldown": "^1.15.3"
51
+ "@vitus-labs/tools-rolldown": "^2.3.0"
51
52
  },
52
53
  "peerDependencies": {
53
- "@pyreon/core": "^0.13.1",
54
- "@pyreon/reactivity": "^0.13.1",
55
- "@pyreon/ui-core": "^0.13.1",
56
- "@pyreon/unistyle": "^0.13.1"
54
+ "@pyreon/core": "^0.15.0",
55
+ "@pyreon/reactivity": "^0.15.0",
56
+ "@pyreon/ui-core": "^0.15.0",
57
+ "@pyreon/unistyle": "^0.15.0"
57
58
  },
58
59
  "engines": {
59
60
  "node": ">= 22"
@@ -11,9 +11,17 @@ import { onMount, splitProps } from '@pyreon/core'
11
11
  import { render } from '@pyreon/ui-core'
12
12
  import { PKG_NAME } from '../constants'
13
13
  import { Content, Wrapper } from '../helpers'
14
+ import { internElementBundle } from '../helpers/internElementBundle'
15
+ import WrapperStyled from '../helpers/Wrapper/styled'
16
+ import { isWebFixNeeded } from '../helpers/Wrapper/utils'
17
+ import { IS_DEVELOPMENT } from '../utils'
14
18
  import type { PyreonElement } from './types'
15
19
  import { getShouldBeEmpty, isInlineElement } from './utils'
16
20
 
21
+ const WRAPPER_DEV_PROPS: Record<string, string> = IS_DEVELOPMENT
22
+ ? { 'data-pyr-element': 'Element' }
23
+ : {}
24
+
17
25
  const equalize = (el: HTMLElement, direction: unknown) => {
18
26
  const beforeEl = el.firstElementChild as HTMLElement | null
19
27
  const afterEl = el.lastElementChild as HTMLElement | null
@@ -156,6 +164,45 @@ const Component: PyreonElement = (props) => {
156
164
  return <Wrapper {...rest} {...WRAPPER_PROPS} />
157
165
  }
158
166
 
167
+ // Simple-Element fast path: no beforeContent / afterContent slots, and no
168
+ // button/fieldset/legend two-layer flex fix needed. Inline the Wrapper
169
+ // helper directly into a single Styled invocation — saves one component
170
+ // hop, one splitProps call, and one mountChild per Element. The Wrapper
171
+ // helper still exists for the rare needsFix case below; tests that asserted
172
+ // Wrapper appears in the VNode tree are updated to the new shape.
173
+ const dangerouslySetInnerHTML = (rest as { dangerouslySetInnerHTML?: unknown })
174
+ .dangerouslySetInnerHTML
175
+ const needsFix = !dangerouslySetInnerHTML && isWebFixNeeded(own.tag)
176
+
177
+ if (isSimpleElement && !needsFix) {
178
+ return (
179
+ <WrapperStyled
180
+ {...rest}
181
+ {...WRAPPER_DEV_PROPS}
182
+ ref={mergedRef}
183
+ as={own.tag}
184
+ $element={internElementBundle({
185
+ block: own.block,
186
+ direction: wrapperDirection,
187
+ alignX: wrapperAlignX,
188
+ alignY: wrapperAlignY,
189
+ equalCols: own.equalCols,
190
+ extraStyles: own.css,
191
+ })}
192
+ >
193
+ {render(getChildren())}
194
+ </WrapperStyled>
195
+ )
196
+ }
197
+
198
+ if (isSimpleElement) {
199
+ return (
200
+ <Wrapper {...rest} {...WRAPPER_PROPS} isInline={isInline}>
201
+ {render(getChildren())}
202
+ </Wrapper>
203
+ )
204
+ }
205
+
159
206
  return (
160
207
  <Wrapper {...rest} {...WRAPPER_PROPS} isInline={isInline}>
161
208
  {own.beforeContent && (
@@ -174,22 +221,18 @@ const Component: PyreonElement = (props) => {
174
221
  </Content>
175
222
  )}
176
223
 
177
- {isSimpleElement ? (
178
- render(getChildren())
179
- ) : (
180
- <Content
181
- tag={SUB_TAG}
182
- contentType="content"
183
- parentDirection={wrapperDirection}
184
- extendCss={own.contentCss}
185
- direction={contentDirection}
186
- alignX={contentAlignX}
187
- alignY={contentAlignY}
188
- equalCols={own.equalCols}
189
- >
190
- {getChildren()}
191
- </Content>
192
- )}
224
+ <Content
225
+ tag={SUB_TAG}
226
+ contentType="content"
227
+ parentDirection={wrapperDirection}
228
+ extendCss={own.contentCss}
229
+ direction={contentDirection}
230
+ alignX={contentAlignX}
231
+ alignY={contentAlignY}
232
+ equalCols={own.equalCols}
233
+ >
234
+ {getChildren()}
235
+ </Content>
193
236
 
194
237
  {own.afterContent && (
195
238
  <Content
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { VNodeChild } from '@pyreon/core'
10
- import { onMount, Portal, splitProps } from '@pyreon/core'
10
+ import { nativeCompat, onMount, Portal, splitProps } from '@pyreon/core'
11
11
  import { render } from '@pyreon/ui-core'
12
12
  import { PKG_NAME } from '../constants'
13
13
  import type { Content, PyreonComponent } from '../types'
@@ -132,4 +132,9 @@ Component.displayName = name
132
132
  Component.pkgName = PKG_NAME
133
133
  Component.PYREON__COMPONENT = name
134
134
 
135
+ // Mark as native — compat-mode jsx() runtimes skip wrapCompatComponent so
136
+ // Overlay's onMount + Portal + useOverlay hook setup run inside Pyreon's
137
+ // setup frame.
138
+ nativeCompat(Component)
139
+
135
140
  export default Component
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { VNodeChild } from '@pyreon/core'
8
- import { createContext, provide, useContext } from '@pyreon/core'
8
+ import { createContext, nativeCompat, provide, useContext } from '@pyreon/core'
9
9
 
10
10
  export interface OverlayContext {
11
11
  blocked: boolean | (() => boolean)
@@ -29,4 +29,8 @@ const Component = (props: OverlayContext & { children: VNodeChild }) => {
29
29
  return <>{props.children}</>
30
30
  }
31
31
 
32
+ // Mark as native — invoked by Overlay internally; needs Pyreon's setup
33
+ // frame for provide(context, ...) to reach descendant overlays.
34
+ nativeCompat(Component)
35
+
32
36
  export default Component