@pyreon/elements 0.15.0 → 0.18.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
@@ -182,54 +182,87 @@ type ExtendedProps = {
182
182
  even: boolean;
183
183
  position: number;
184
184
  };
185
- type PropsCallback = TObj | ((itemProps: Record<string, never> | Record<string, SimpleValue> | ObjectValue, extendedProps: ExtendedProps) => TObj);
186
- type Props$1 = Partial<{
185
+ /**
186
+ * Iterator over an array of strings/numbers. Each item is wrapped in
187
+ * `{ [valueName]: item }` and that object is what callbacks see + what's
188
+ * spread onto the rendered component.
189
+ */
190
+ type SimpleProps<T extends SimpleValue> = {
191
+ data: Array<T | MaybeNull>; /** A component to be rendered per item. */
192
+ component: ElementType;
187
193
  /**
188
- * Valid children
189
- */
194
+ * Key under which each primitive value is exposed to `component` and
195
+ * callbacks. Defaults to `'children'` at runtime — i.e. the value is
196
+ * passed to the component as its children.
197
+ */
198
+ valueName?: string; /** Optional wrapper around each item. */
199
+ wrapComponent?: ElementType; /** Stable key per item (defaults to index). */
200
+ itemKey?: (item: T, index: number) => SimpleValue; /** Extra props merged onto the rendered component, optionally per-item. */
201
+ itemProps?: TObj | ((item: {
202
+ [k: string]: T;
203
+ }, ext: ExtendedProps) => TObj); /** Extra props merged onto the wrapper, optionally per-item. */
204
+ wrapProps?: TObj | ((item: {
205
+ [k: string]: T;
206
+ }, ext: ExtendedProps) => TObj);
207
+ children?: never;
208
+ };
209
+ /**
210
+ * Iterator over an array of objects. Each item is spread onto the rendered
211
+ * component as props. Per-item `component` overrides also work — when an
212
+ * item carries its own `component` field, the wrapper is bypassed.
213
+ */
214
+ type ObjectProps<T extends ObjectValue> = {
215
+ data: Array<T | MaybeNull>; /** Default component to be rendered per item (item-level `component` overrides). */
216
+ component: ElementType; /** `valueName` is meaningless when iterating objects — TS forbids it. */
217
+ valueName?: never; /** Optional wrapper around each item. */
218
+ wrapComponent?: ElementType; /** Stable key per item — pick a key from the item, or compute it. */
219
+ itemKey?: keyof T | ((item: T, index: number) => SimpleValue); /** Extra props merged onto the rendered component, optionally per-item. */
220
+ itemProps?: TObj | ((item: T, ext: ExtendedProps) => TObj); /** Extra props merged onto the wrapper, optionally per-item. */
221
+ wrapProps?: TObj | ((item: T, ext: ExtendedProps) => TObj);
222
+ children?: never;
223
+ };
224
+ /**
225
+ * Iterator over `children` — no `data`/`component`. Each child gets
226
+ * positional metadata via `itemProps` and an optional `wrapComponent`.
227
+ */
228
+ type ChildrenProps = {
229
+ children: VNodeChild;
230
+ data?: never;
231
+ component?: never;
232
+ valueName?: never;
233
+ itemKey?: never;
234
+ wrapComponent?: ElementType;
235
+ itemProps?: TObj | ((_: Record<string, never>, ext: ExtendedProps) => TObj);
236
+ wrapProps?: TObj | ((_: Record<string, never>, ext: ExtendedProps) => TObj);
237
+ };
238
+ type PropsCallback = TObj | ((itemProps: Record<string, never> | Record<string, SimpleValue> | ObjectValue, extendedProps: ExtendedProps) => TObj);
239
+ type LooseProps = Partial<{
190
240
  children: VNodeChild;
191
- /**
192
- * Array of data passed to `component` prop
193
- */
194
241
  data: Array<SimpleValue | ObjectValue | MaybeNull>;
195
- /**
196
- * A component to be rendered within list
197
- */
198
242
  component: ElementType;
199
- /**
200
- * Defines name of the prop to be passed to the iteration component
201
- * when **data** prop is type of `string[]`, `number[]` or combination
202
- * of both. Otherwise ignored.
203
- */
204
243
  valueName: string;
205
- /**
206
- * A component to be rendered within list. `wrapComponent`
207
- * wraps `component`. Therefore it can be used to enhance the behavior
208
- * of the list component
209
- */
210
244
  wrapComponent: ElementType;
211
- /**
212
- * Extension of **item** `component` props to be passed
213
- */
214
245
  itemProps: PropsCallback;
215
- /**
216
- * Extension of **item** `wrapComponent` props to be passed
217
- */
218
- wrapProps?: PropsCallback;
219
- /**
220
- * Extension of **item** `wrapComponent` props to be passed
221
- */
222
- itemKey?: keyof ObjectValue | ((item: SimpleValue | Omit<ObjectValue, 'component'>, index: number) => SimpleValue);
246
+ wrapProps: PropsCallback;
247
+ itemKey: keyof ObjectValue | ((item: SimpleValue | Omit<ObjectValue, 'component'>, index: number) => SimpleValue);
223
248
  }>;
249
+ type Props$1<T = unknown> = unknown extends T ? LooseProps : T extends SimpleValue ? SimpleProps<T> : T extends ObjectValue ? ObjectProps<T> : ChildrenProps;
224
250
  //#endregion
225
251
  //#region src/helpers/Iterator/component.d.ts
226
- declare const _default: ((props: Props$1) => VNodeChild) & {
252
+ declare const RESERVED_PROPS: readonly ["children", "component", "wrapComponent", "data", "itemKey", "valueName", "itemProps", "wrapProps"];
253
+ interface IteratorComponent {
254
+ <T extends SimpleValue>(props: SimpleProps<T>): VNodeChild;
255
+ <T extends ObjectValue>(props: ObjectProps<T>): VNodeChild;
256
+ (props: ChildrenProps): VNodeChild;
257
+ (props: LooseProps): VNodeChild;
227
258
  isIterator: true;
228
- RESERVED_PROPS: readonly ["children", "component", "wrapComponent", "data", "itemKey", "valueName", "itemProps", "wrapProps"];
229
- };
259
+ RESERVED_PROPS: typeof RESERVED_PROPS;
260
+ displayName?: string;
261
+ }
262
+ declare const Iterator: IteratorComponent;
230
263
  //#endregion
231
264
  //#region src/List/component.d.ts
232
- type ListProps = {
265
+ type ListOnly = {
233
266
  /**
234
267
  * A boolean value. When set to `false`, component returns fragment.
235
268
  * When set to `true`, component returns as the **root** element `Element`
@@ -239,14 +272,38 @@ type ListProps = {
239
272
  /**
240
273
  * Label prop from `Element` component is being ignored.
241
274
  */
242
- label: never;
275
+ label?: never;
243
276
  /**
244
277
  * Content prop from `Element` component is being ignored.
245
278
  */
246
- content: never;
279
+ content?: never;
247
280
  };
248
- type Props$2 = MergeTypes<[Props$1, ListProps]> & Partial<Omit<Props, 'children' | 'content' | 'label'>>;
249
- declare const Component$1: PyreonElement<Props$2>;
281
+ /**
282
+ * Props that List accepts on top of the Iterator branch — the Element prop
283
+ * surface (so `tag`, `direction`, `alignX`, etc. forward when
284
+ * `rootElement` is true) plus the List-only toggle.
285
+ */
286
+ type ListExtras = Partial<Omit<Props, 'children' | 'content' | 'label'>> & ListOnly;
287
+ /**
288
+ * Public Props — generic over the data element type so callers get the same
289
+ * inference Iterator does, plus the List-specific `rootElement` toggle and
290
+ * Element prop forwarding.
291
+ *
292
+ * Props<string> → SimpleProps & ListExtras (valueName REQUIRED)
293
+ * Props<{ id; name }> → ObjectProps & ListExtras (valueName FORBIDDEN)
294
+ * Props<unknown> / Props → LooseProps & ListExtras (today's behavior)
295
+ */
296
+ type Props$2<T = unknown> = MergeTypes<[Props$1<T>, ListExtras]>;
297
+ interface ListComponent {
298
+ <T extends SimpleValue>(props: SimpleProps<T> & ListExtras): VNodeChild;
299
+ <T extends ObjectValue>(props: ObjectProps<T> & ListExtras): VNodeChild;
300
+ (props: ChildrenProps & ListExtras): VNodeChild;
301
+ (props: LooseProps & ListExtras): VNodeChild;
302
+ displayName?: string;
303
+ pkgName?: string;
304
+ PYREON__COMPONENT?: string;
305
+ }
306
+ declare const _default: ListComponent;
250
307
  //#endregion
251
308
  //#region src/Overlay/context.d.ts
252
309
  interface OverlayContext {
@@ -254,7 +311,7 @@ interface OverlayContext {
254
311
  setBlocked: () => void;
255
312
  setUnblocked: () => void;
256
313
  }
257
- declare const Component$3: (props: OverlayContext & {
314
+ declare const Component$2: (props: OverlayContext & {
258
315
  children: VNodeChild;
259
316
  }) => _$_pyreon_core0.VNode;
260
317
  //#endregion
@@ -343,24 +400,24 @@ type Props$3 = {
343
400
  triggerRefName?: string;
344
401
  contentRefName?: string;
345
402
  } & UseOverlayProps;
346
- declare const Component$2: PyreonComponent<Props$3>;
403
+ declare const Component$1: PyreonComponent<Props$3>;
347
404
  //#endregion
348
405
  //#region src/Portal/component.d.ts
349
406
  interface Props$4 {
350
407
  /**
351
- * Defines a HTML DOM where children to be appended.
408
+ * DOM element to mount the wrapper into. Defaults to `document.body`.
352
409
  */
353
410
  DOMLocation?: HTMLElement;
354
411
  /**
355
- * Children to be rendered within **Portal** component.
412
+ * Children rendered inside the wrapper.
356
413
  */
357
414
  children: VNodeChild;
358
415
  /**
359
- * Valid HTML Tag
416
+ * HTML tag for the per-instance wrapper element. Defaults to `'div'`.
360
417
  */
361
418
  tag?: string;
362
419
  }
363
- declare const Component$4: PyreonComponent<Props$4>;
420
+ declare const Component$3: PyreonComponent<Props$4>;
364
421
  //#endregion
365
422
  //#region src/Text/component.d.ts
366
423
  type Props$5 = Partial<{
@@ -385,7 +442,7 @@ type Props$5 = Partial<{
385
442
  */
386
443
  css: ExtendCss;
387
444
  }> & PyreonHTMLAttributes;
388
- declare const Component$5: PyreonComponent<Props$5> & {
445
+ declare const Component$4: PyreonComponent<Props$5> & {
389
446
  isText?: true;
390
447
  };
391
448
  //#endregion
@@ -404,7 +461,7 @@ interface Props$6 {
404
461
  */
405
462
  style?: Record<string, unknown> | undefined;
406
463
  }
407
- declare const Component$6: PyreonComponent<Props$6>;
464
+ declare const Component$5: PyreonComponent<Props$6>;
408
465
  //#endregion
409
- export { type AlignX, type AlignY, type Content, type ContentBoolean, type Direction, Component as Element, type Props as ElementProps, type ElementType, type ExtendCss, type ExtendedProps, type InnerRef, _default as Iterator, type Props$1 as IteratorProps, Component$1 as List, type Props$2 as ListProps, type MaybeNull, type ObjectValue, Component$2 as Overlay, type Props$3 as OverlayProps, Component$3 as OverlayProvider, Component$4 as Portal, type Props$4 as PortalProps, type PropsCallback, Provider, type PyreonElement, type PyreonStatic, type Responsive, type ResponsiveBoolType, type SimpleValue, Component$5 as Text, type Props$5 as TextProps, type UseOverlayProps, Component$6 as Util, type Props$6 as UtilProps, useOverlay };
466
+ export { type AlignX, type AlignY, type Content, type ContentBoolean, type Direction, Component as Element, type Props as ElementProps, type ElementType, type ExtendCss, type ExtendedProps, type InnerRef, Iterator, type ChildrenProps as IteratorChildrenProps, type LooseProps as IteratorLooseProps, type ObjectProps as IteratorObjectProps, type Props$1 as IteratorProps, type SimpleProps as IteratorSimpleProps, _default as List, type Props$2 as ListProps, type MaybeNull, type ObjectValue, Component$1 as Overlay, type Props$3 as OverlayProps, Component$2 as OverlayProvider, Component$3 as Portal, type Props$4 as PortalProps, type PropsCallback, Provider, type PyreonElement, type PyreonStatic, type Responsive, type ResponsiveBoolType, type SimpleValue, Component$4 as Text, type Props$5 as TextProps, type UseOverlayProps, Component$5 as Util, type Props$6 as UtilProps, useOverlay };
410
467
  //# sourceMappingURL=index2.d.ts.map
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Provider, alignContent, extendCss, makeItResponsive, value } from "@pyreon/unistyle";
2
- import { Fragment, Portal, createContext, nativeCompat, onMount, provide, splitProps, useContext } from "@pyreon/core";
2
+ import { Fragment, Portal, createContext, h, nativeCompat, onMount, onUnmount, provide, splitProps, useContext } from "@pyreon/core";
3
3
  import { config, isEmpty, omit, pick, render, throttle } from "@pyreon/ui-core";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "@pyreon/core/jsx-runtime";
5
5
  import { signal } from "@pyreon/reactivity";
@@ -264,28 +264,25 @@ const childFixCSS = `
264
264
  const parentFixCSS = `
265
265
  flex-direction: column;
266
266
  `;
267
- const fullHeightCSS = `
268
- height: 100%;
269
- `;
270
- const blockCSS = `
271
- align-self: stretch;
272
- flex: 1;
273
- min-width: 0;
274
- `;
275
- const childFixPosition = (isBlock) => `display: ${isBlock ? "flex" : "inline-flex"};`;
276
267
  const styles$1 = ({ theme: t, css: cssFn }) => cssFn`
277
- ${t.alignY === "block" && fullHeightCSS};
278
-
279
268
  ${alignContent({
280
269
  direction: t.direction,
281
270
  alignX: t.alignX,
282
271
  alignY: t.alignY
283
272
  })};
284
273
 
285
- ${t.block && blockCSS};
286
- ${t.alignY === "block" && t.block && fullHeightCSS};
274
+ /*
275
+ * Always emit a value for the block-related properties so a responsive
276
+ * theme that flips from \`block: true\` at one breakpoint to \`block: false\`
277
+ * at another resets cleanly. Previously \`align-self\` / \`width\` / \`height\`
278
+ * were only set when the truthy branch matched, which left the prior
279
+ * breakpoint's values cascading through.
280
+ */
281
+ ${`align-self: ${t.block ? "stretch" : "auto"};
282
+ width: ${t.block ? "100%" : "auto"};
283
+ height: ${t.alignY === "block" ? "100%" : "auto"};`};
287
284
 
288
- ${!t.childFix && childFixPosition(t.block)};
285
+ ${!t.childFix && `display: ${t.block ? "flex" : "inline-flex"};`};
289
286
  ${t.parentFix && parentFixCSS};
290
287
 
291
288
  ${t.extraStyles && extendCss(t.extraStyles)};
@@ -334,6 +331,34 @@ const isWebFixNeeded = (tag) => {
334
331
  * support `display: flex` consistently across browsers.
335
332
  */
336
333
  const DEV_PROPS = IS_DEVELOPMENT ? { "data-pyr-element": "Element" } : {};
334
+ /**
335
+ * Build a props object for `h(Styled, ...)` by copying own property
336
+ * DESCRIPTORS from `rest`, then layering the additional fields. Compiler-
337
+ * emitted reactive props (`_rp(() => signal())` converted to getters by
338
+ * `makeReactiveProps`) survive end-to-end with their getter intact.
339
+ *
340
+ * Why we bypass JSX spread here: the standard JSX automatic-runtime
341
+ * compilation lowers `<Styled {...rest} foo={x}>` to roughly
342
+ * `jsx(Styled, { ...rest, foo: x })`. That `{...rest, foo: x}` object
343
+ * literal is evaluated at JS level — it fires every getter on `rest` and
344
+ * stores the resolved value before `jsx()` ever sees the object. No
345
+ * amount of in-runtime descriptor preservation can recover the getters
346
+ * once they've been collapsed by the surface-level spread. The fix is
347
+ * structural: don't use JSX spread for reactive-prop forwarding. Build
348
+ * the props object with descriptor preservation and pass it to `h()`
349
+ * directly — `h()` stores props as-is on the vnode, no copy, getters
350
+ * survive into mount.
351
+ */
352
+ const buildStyledProps = (rest, refValue, asTag, extras) => {
353
+ const result = {};
354
+ const descriptors = Object.getOwnPropertyDescriptors(rest);
355
+ for (const key in descriptors) Object.defineProperty(result, key, descriptors[key]);
356
+ for (const key in DEV_PROPS) result[key] = DEV_PROPS[key];
357
+ result.ref = refValue;
358
+ result.as = asTag;
359
+ for (const key in extras) result[key] = extras[key];
360
+ return result;
361
+ };
337
362
  const OWN_KEYS = [
338
363
  "children",
339
364
  "tag",
@@ -349,14 +374,9 @@ const OWN_KEYS = [
349
374
  ];
350
375
  const Component$8 = (props) => {
351
376
  const [own, rest] = splitProps(props, OWN_KEYS);
352
- const commonProps = {
353
- ...rest,
354
- ...DEV_PROPS,
355
- ref: own.ref,
356
- as: own.tag
357
- };
358
377
  const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag);
359
378
  const isVoidTag = !own.dangerouslySetInnerHTML && getShouldBeEmpty(own.tag);
379
+ const innerHTML = own.dangerouslySetInnerHTML;
360
380
  if (!needsFix) {
361
381
  const bundle = internElementBundle({
362
382
  block: own.block,
@@ -366,15 +386,15 @@ const Component$8 = (props) => {
366
386
  equalCols: own.equalCols,
367
387
  extraStyles: own.extendCss
368
388
  });
369
- if (isVoidTag) return /* @__PURE__ */ jsx(styled_default$1, {
370
- ...commonProps,
371
- $element: bundle
372
- });
373
- return /* @__PURE__ */ jsx(styled_default$1, {
374
- ...commonProps,
389
+ if (isVoidTag) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, { $element: bundle }));
390
+ if (innerHTML) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
391
+ $element: bundle,
392
+ dangerouslySetInnerHTML: innerHTML
393
+ }));
394
+ return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
375
395
  $element: bundle,
376
396
  children: own.children
377
- });
397
+ }));
378
398
  }
379
399
  const asTag = own.isInline ? "span" : "div";
380
400
  const parentBundle = internElementBundle({
@@ -389,16 +409,24 @@ const Component$8 = (props) => {
389
409
  alignY: own.alignY,
390
410
  equalCols: own.equalCols
391
411
  });
392
- return /* @__PURE__ */ jsx(styled_default$1, {
393
- ...commonProps,
412
+ if (innerHTML) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
413
+ $element: parentBundle,
414
+ children: h(styled_default$1, {
415
+ as: asTag,
416
+ $childFix: true,
417
+ $element: childBundle,
418
+ dangerouslySetInnerHTML: innerHTML
419
+ })
420
+ }));
421
+ return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
394
422
  $element: parentBundle,
395
- children: /* @__PURE__ */ jsx(styled_default$1, {
423
+ children: h(styled_default$1, {
396
424
  as: asTag,
397
425
  $childFix: true,
398
426
  $element: childBundle,
399
427
  children: own.children
400
428
  })
401
- });
429
+ }));
402
430
  };
403
431
 
404
432
  //#endregion
@@ -499,7 +527,13 @@ const Component = (props) => {
499
527
  else if (externalRef != null) externalRef.current = node;
500
528
  };
501
529
  if (own.equalBeforeAfter && own.beforeContent && own.afterContent) onMount(() => {
502
- if (equalizeRef) equalize(equalizeRef, own.direction);
530
+ const node = equalizeRef;
531
+ if (!node) return void 0;
532
+ equalize(node, own.direction);
533
+ if (typeof ResizeObserver === "undefined") return void 0;
534
+ const observer = new ResizeObserver(() => equalize(node, own.direction));
535
+ observer.observe(node);
536
+ return () => observer.disconnect();
503
537
  });
504
538
  const WRAPPER_PROPS = {
505
539
  ref: mergedRef,
@@ -730,34 +764,28 @@ const Component$7 = (props) => {
730
764
  };
731
765
  return renderItems();
732
766
  };
733
- var component_default = Object.assign(Component$7, {
767
+ const Iterator = Object.assign(Component$7, {
734
768
  isIterator: true,
735
769
  RESERVED_PROPS
736
770
  });
737
771
 
738
772
  //#endregion
739
773
  //#region src/helpers/Iterator/index.ts
740
- var Iterator_default = component_default;
774
+ var Iterator_default = Iterator;
741
775
 
742
776
  //#endregion
743
777
  //#region src/List/component.tsx
744
- /**
745
- * List component that combines Iterator (data-driven rendering) with an
746
- * optional Element root wrapper. When `rootElement` is false (default),
747
- * it renders a bare Iterator as a fragment. When true, the Iterator output
748
- * is wrapped in an Element that receives all non-iterator props (e.g.,
749
- * layout, alignment, css), allowing the list to be styled as a single block.
750
- */
751
- const Component$1 = ((allProps) => {
778
+ const LooseIterator = Iterator_default;
779
+ const Component$1 = (allProps) => {
752
780
  const [own, props] = splitProps(allProps, ["rootElement", "ref"]);
753
- const renderedList = /* @__PURE__ */ jsx(Iterator_default, { ...pick(props, Iterator_default.RESERVED_PROPS) });
781
+ const renderedList = /* @__PURE__ */ jsx(LooseIterator, { ...pick(props, Iterator_default.RESERVED_PROPS) });
754
782
  if (!own.rootElement) return renderedList;
755
783
  return /* @__PURE__ */ jsx(Component, {
756
- ...own.ref ? { ref: own.ref } : {},
784
+ ref: own.ref,
757
785
  ...omit(props, Iterator_default.RESERVED_PROPS),
758
786
  children: renderedList
759
787
  });
760
- });
788
+ };
761
789
  const name$4 = `${PKG_NAME}/List`;
762
790
  Component$1.displayName = name$4;
763
791
  Component$1.pkgName = PKG_NAME;
@@ -1237,10 +1265,16 @@ nativeCompat(Component$2);
1237
1265
  //#endregion
1238
1266
  //#region src/Portal/component.tsx
1239
1267
  const Component$4 = (props) => {
1240
- const target = props.DOMLocation ?? (typeof document !== "undefined" ? document.body : void 0);
1241
- if (!target) return null;
1268
+ if (typeof document === "undefined") return null;
1269
+ const tag = props.tag ?? "div";
1270
+ const target = props.DOMLocation ?? document.body;
1271
+ const wrapper = document.createElement(tag);
1272
+ target.appendChild(wrapper);
1273
+ onUnmount(() => {
1274
+ wrapper.remove();
1275
+ });
1242
1276
  return /* @__PURE__ */ jsx(Portal, {
1243
- target,
1277
+ target: wrapper,
1244
1278
  children: props.children
1245
1279
  });
1246
1280
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/elements",
3
- "version": "0.15.0",
3
+ "version": "0.18.0",
4
4
  "description": "Foundational UI components for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,21 +42,21 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "devDependencies": {
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",
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",
50
50
  "@vitest/browser-playwright": "^4.1.4",
51
51
  "@vitus-labs/tools-rolldown": "^2.3.0"
52
52
  },
53
- "peerDependencies": {
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"
58
- },
59
53
  "engines": {
60
54
  "node": ">= 22"
55
+ },
56
+ "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"
61
61
  }
62
62
  }
@@ -137,9 +137,20 @@ const Component: PyreonElement = (props) => {
137
137
  }
138
138
 
139
139
  if (own.equalBeforeAfter && own.beforeContent && own.afterContent) {
140
+ // Run once on mount AND continue equalizing as the element resizes —
141
+ // catches async slot content (font swaps, lazy text, viewport resize)
142
+ // that a one-shot measurement would miss. Mirrors vitus-labs's Element
143
+ // useLayoutEffect + ResizeObserver pattern.
140
144
  onMount(() => {
141
- if (equalizeRef) equalize(equalizeRef, own.direction)
142
- return undefined
145
+ const node = equalizeRef
146
+ if (!node) return undefined
147
+
148
+ equalize(node, own.direction)
149
+
150
+ if (typeof ResizeObserver === 'undefined') return undefined
151
+ const observer = new ResizeObserver(() => equalize(node, own.direction))
152
+ observer.observe(node)
153
+ return () => observer.disconnect()
143
154
  })
144
155
  }
145
156
 
@@ -5,16 +5,25 @@
5
5
  * is wrapped in an Element that receives all non-iterator props (e.g.,
6
6
  * layout, alignment, css), allowing the list to be styled as a single block.
7
7
  */
8
+ import type { VNodeChild } from '@pyreon/core'
8
9
  import { splitProps } from '@pyreon/core'
9
10
  import { omit, pick } from '@pyreon/ui-core'
10
11
  import { PKG_NAME } from '../constants'
11
- import type { ElementProps, PyreonElement } from '../Element'
12
+ import type { ElementProps } from '../Element'
12
13
  import { Element } from '../Element'
13
- import type { Props as IteratorProps } from '../helpers/Iterator'
14
+ import type {
15
+ ChildrenProps as IteratorChildrenProps,
16
+ LooseProps as IteratorLooseProps,
17
+ ObjectProps as IteratorObjectProps,
18
+ Props as IteratorProps,
19
+ SimpleProps as IteratorSimpleProps,
20
+ ObjectValue,
21
+ SimpleValue,
22
+ } from '../helpers/Iterator'
14
23
  import Iterator from '../helpers/Iterator'
15
24
  import type { MergeTypes } from '../types'
16
25
 
17
- type ListProps = {
26
+ type ListOnly = {
18
27
  /**
19
28
  * A boolean value. When set to `false`, component returns fragment.
20
29
  * When set to `true`, component returns as the **root** element `Element`
@@ -24,32 +33,73 @@ type ListProps = {
24
33
  /**
25
34
  * Label prop from `Element` component is being ignored.
26
35
  */
27
- label: never
36
+ label?: never
28
37
  /**
29
38
  * Content prop from `Element` component is being ignored.
30
39
  */
31
- content: never
40
+ content?: never
32
41
  }
33
42
 
34
- export type Props = MergeTypes<[IteratorProps, ListProps]> & Partial<Omit<ElementProps, 'children' | 'content' | 'label'>>
43
+ /**
44
+ * Props that List accepts on top of the Iterator branch — the Element prop
45
+ * surface (so `tag`, `direction`, `alignX`, etc. forward when
46
+ * `rootElement` is true) plus the List-only toggle.
47
+ */
48
+ type ListExtras = Partial<Omit<ElementProps, 'children' | 'content' | 'label'>> & ListOnly
49
+
50
+ /**
51
+ * Public Props — generic over the data element type so callers get the same
52
+ * inference Iterator does, plus the List-specific `rootElement` toggle and
53
+ * Element prop forwarding.
54
+ *
55
+ * Props<string> → SimpleProps & ListExtras (valueName REQUIRED)
56
+ * Props<{ id; name }> → ObjectProps & ListExtras (valueName FORBIDDEN)
57
+ * Props<unknown> / Props → LooseProps & ListExtras (today's behavior)
58
+ */
59
+ export type Props<T = unknown> = MergeTypes<[IteratorProps<T>, ListExtras]>
35
60
 
36
- const Component: PyreonElement<Props> = ((allProps: Partial<Props & ElementProps>) => {
37
- const [own, props] = splitProps(allProps, ['rootElement', 'ref'])
38
- const renderedList = <Iterator {...pick(props, Iterator.RESERVED_PROPS)} />
61
+ // Internal spread runtime is correct, but the picked subset can't satisfy
62
+ // any specific Iterator overload statically (which is good — the public
63
+ // overloads enforce constraints). Cast Iterator to a loose callable for
64
+ // the internal forwarding only; public consumers still see the strict
65
+ // overloaded interface.
66
+ const LooseIterator = Iterator as unknown as (props: IteratorLooseProps) => VNodeChild
67
+
68
+ const Component = (allProps: IteratorLooseProps & ListExtras) => {
69
+ const [own, props] = splitProps(allProps as Record<string, unknown>, ['rootElement', 'ref'])
70
+ const renderedList = <LooseIterator {...pick(props, Iterator.RESERVED_PROPS)} />
39
71
 
40
72
  if (!own.rootElement) return renderedList
41
73
 
42
74
  return (
43
- <Element {...(own.ref ? { ref: own.ref } : {})} {...omit(props, Iterator.RESERVED_PROPS)}>
75
+ <Element ref={own.ref as ElementProps['ref']} {...omit(props, Iterator.RESERVED_PROPS)}>
44
76
  {renderedList}
45
77
  </Element>
46
78
  )
47
- }) as PyreonElement<Props>
79
+ }
48
80
 
49
81
  const name = `${PKG_NAME}/List` as const
50
82
 
51
- Component.displayName = name
52
- Component.pkgName = PKG_NAME
53
- Component.PYREON__COMPONENT = name
83
+ ;(Component as { displayName?: string }).displayName = name
84
+ ;(Component as { pkgName?: string }).pkgName = PKG_NAME
85
+ ;(Component as { PYREON__COMPONENT?: string }).PYREON__COMPONENT = name
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Public callable type — same overload pattern as Iterator so JSX-site
89
+ // inference flows through without callers having to spell out `<T>`.
90
+ // ---------------------------------------------------------------------------
91
+ export interface ListComponent {
92
+ // T inferred from `data`. Order: SimpleProps, ObjectProps, ChildrenProps,
93
+ // then a LooseProps fallback for forwarding patterns where derived
94
+ // `$$types['data']` is a wide union that doesn't bind to any narrow
95
+ // overload. See Iterator's IteratorComponent for the full rationale.
96
+ <T extends SimpleValue>(props: IteratorSimpleProps<T> & ListExtras): VNodeChild
97
+ <T extends ObjectValue>(props: IteratorObjectProps<T> & ListExtras): VNodeChild
98
+ (props: IteratorChildrenProps & ListExtras): VNodeChild
99
+ (props: IteratorLooseProps & ListExtras): VNodeChild
100
+ displayName?: string
101
+ pkgName?: string
102
+ PYREON__COMPONENT?: string
103
+ }
54
104
 
55
- export default Component
105
+ export default Component as unknown as ListComponent