@pyreon/elements 0.24.4 → 0.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -12
- package/src/Element/component.tsx +0 -315
- package/src/Element/constants.ts +0 -96
- package/src/Element/index.ts +0 -6
- package/src/Element/types.ts +0 -168
- package/src/Element/utils.ts +0 -15
- package/src/List/component.tsx +0 -105
- package/src/List/index.ts +0 -5
- package/src/Overlay/component.tsx +0 -140
- package/src/Overlay/context.tsx +0 -36
- package/src/Overlay/index.ts +0 -7
- package/src/Overlay/positioning.ts +0 -191
- package/src/Overlay/useOverlay.tsx +0 -461
- package/src/Portal/component.tsx +0 -54
- package/src/Portal/index.ts +0 -5
- package/src/Text/component.tsx +0 -67
- package/src/Text/index.ts +0 -5
- package/src/Text/styled.ts +0 -30
- package/src/Util/component.tsx +0 -43
- package/src/Util/index.ts +0 -5
- package/src/__tests__/Content.test.tsx +0 -123
- package/src/__tests__/Element-slot-reactivity.browser.test.tsx +0 -152
- package/src/__tests__/Element.test.ts +0 -819
- package/src/__tests__/Iterator.test.ts +0 -492
- package/src/__tests__/Iterator.types.test.ts +0 -237
- package/src/__tests__/List.test.ts +0 -199
- package/src/__tests__/Overlay.test.ts +0 -492
- package/src/__tests__/Portal.test.ts +0 -156
- package/src/__tests__/Text.test.ts +0 -274
- package/src/__tests__/Util.test.ts +0 -63
- package/src/__tests__/Wrapper-innerhtml.test.tsx +0 -178
- package/src/__tests__/Wrapper.test.tsx +0 -196
- package/src/__tests__/elements.browser.test.tsx +0 -132
- package/src/__tests__/equalBeforeAfter.test.ts +0 -122
- package/src/__tests__/helpers.test.ts +0 -65
- package/src/__tests__/integration.test.tsx +0 -118
- package/src/__tests__/internElementBundle.test.ts +0 -102
- package/src/__tests__/iterator-function-children.test.tsx +0 -120
- package/src/__tests__/native-markers.test.ts +0 -13
- package/src/__tests__/overlayContext.test.tsx +0 -78
- package/src/__tests__/perf-stress.browser.test.tsx +0 -119
- package/src/__tests__/positioning.test.ts +0 -90
- package/src/__tests__/responsiveProps.test.ts +0 -328
- package/src/__tests__/slot-component-reference.test.tsx +0 -157
- package/src/__tests__/useOverlay.test.ts +0 -1336
- package/src/__tests__/utils.test.ts +0 -69
- package/src/__tests__/wrapper-block-cascade.test.ts +0 -121
- package/src/constants.ts +0 -1
- package/src/env.d.ts +0 -6
- package/src/helpers/Content/component.tsx +0 -75
- package/src/helpers/Content/index.ts +0 -3
- package/src/helpers/Content/styled.ts +0 -105
- package/src/helpers/Content/types.ts +0 -49
- package/src/helpers/Iterator/component.tsx +0 -316
- package/src/helpers/Iterator/index.ts +0 -30
- package/src/helpers/Iterator/types.ts +0 -138
- package/src/helpers/Wrapper/component.tsx +0 -180
- package/src/helpers/Wrapper/constants.ts +0 -10
- package/src/helpers/Wrapper/index.ts +0 -3
- package/src/helpers/Wrapper/styled.ts +0 -64
- package/src/helpers/Wrapper/types.ts +0 -56
- package/src/helpers/Wrapper/utils.ts +0 -7
- package/src/helpers/index.ts +0 -4
- package/src/helpers/internElementBundle.ts +0 -37
- package/src/helpers/isPyreonComponent.ts +0 -46
- package/src/index.ts +0 -42
- package/src/manifest.ts +0 -190
- package/src/tests/manifest-snapshot.test.ts +0 -45
- package/src/types.ts +0 -112
- package/src/utils.ts +0 -5
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data-driven list renderer that supports three input modes: children,
|
|
3
|
-
* an array of primitives, or an array of objects.
|
|
4
|
-
* Each item receives positional metadata (first, last, odd, even, position)
|
|
5
|
-
* and optional injected props via `itemProps`. Items can be individually
|
|
6
|
-
* wrapped with `wrapComponent`. Children always take priority over the
|
|
7
|
-
* component+data prop pattern.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { VNode, VNodeChild } from '@pyreon/core'
|
|
11
|
-
import { Fragment } from '@pyreon/core'
|
|
12
|
-
import { isEmpty, render } from '@pyreon/ui-core'
|
|
13
|
-
import type {
|
|
14
|
-
ChildrenProps,
|
|
15
|
-
ExtendedProps,
|
|
16
|
-
LooseProps,
|
|
17
|
-
ObjectProps,
|
|
18
|
-
ObjectValue,
|
|
19
|
-
SimpleProps,
|
|
20
|
-
SimpleValue,
|
|
21
|
-
} from './types'
|
|
22
|
-
|
|
23
|
-
type ClassifiedData =
|
|
24
|
-
| { type: 'simple'; data: SimpleValue[] }
|
|
25
|
-
| { type: 'complex'; data: ObjectValue[] }
|
|
26
|
-
| null
|
|
27
|
-
|
|
28
|
-
const classifyData = (data: unknown[]): ClassifiedData => {
|
|
29
|
-
const items = data.filter(
|
|
30
|
-
(item) =>
|
|
31
|
-
item != null && !(typeof item === 'object' && isEmpty(item as Record<string, unknown>)),
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
if (items.length === 0) return null
|
|
35
|
-
|
|
36
|
-
let isSimple = true
|
|
37
|
-
let isComplex = true
|
|
38
|
-
|
|
39
|
-
for (const item of items) {
|
|
40
|
-
if (typeof item === 'string' || typeof item === 'number') {
|
|
41
|
-
isComplex = false
|
|
42
|
-
} else if (typeof item === 'object') {
|
|
43
|
-
isSimple = false
|
|
44
|
-
} else {
|
|
45
|
-
isSimple = false
|
|
46
|
-
isComplex = false
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (isSimple) return { type: 'simple', data: items as SimpleValue[] }
|
|
51
|
-
if (isComplex) return { type: 'complex', data: items as ObjectValue[] }
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const RESERVED_PROPS = [
|
|
56
|
-
'children',
|
|
57
|
-
'component',
|
|
58
|
-
'wrapComponent',
|
|
59
|
-
'data',
|
|
60
|
-
'itemKey',
|
|
61
|
-
'valueName',
|
|
62
|
-
'itemProps',
|
|
63
|
-
'wrapProps',
|
|
64
|
-
] as const
|
|
65
|
-
|
|
66
|
-
type AttachItemProps = ({ i, length }: { i: number; length: number }) => ExtendedProps
|
|
67
|
-
|
|
68
|
-
const attachItemProps: AttachItemProps = ({ i, length }: { i: number; length: number }) => {
|
|
69
|
-
const position = i + 1
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
index: i,
|
|
73
|
-
first: position === 1,
|
|
74
|
-
last: position === length,
|
|
75
|
-
odd: position % 2 === 1,
|
|
76
|
-
even: position % 2 === 0,
|
|
77
|
-
position,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const Component = (props: LooseProps) => {
|
|
82
|
-
const {
|
|
83
|
-
itemKey,
|
|
84
|
-
valueName,
|
|
85
|
-
children: rawChildren,
|
|
86
|
-
component,
|
|
87
|
-
data,
|
|
88
|
-
wrapComponent: Wrapper,
|
|
89
|
-
wrapProps,
|
|
90
|
-
itemProps,
|
|
91
|
-
} = props
|
|
92
|
-
|
|
93
|
-
// Unwrap the Pyreon compiler's `() => x` accessor wrap. When the parent
|
|
94
|
-
// emits `<Iterator>{items}</Iterator>` and the compiler-emitted form is
|
|
95
|
-
// `Iterator({ children: () => items })`, the downstream `Array.isArray`
|
|
96
|
-
// check returns false, the Fragment check returns false (function is not
|
|
97
|
-
// an object), and the fallthrough `renderChild(function)` calls
|
|
98
|
-
// `render(function, props)` which interprets the function as a component
|
|
99
|
-
// function — wrong shape, lost per-item metadata. Resolving eagerly here
|
|
100
|
-
// keeps every downstream branch correct. Mirrors the kinetic Stagger /
|
|
101
|
-
// TransitionItem fix (PR #731 + parallel top-level fixes).
|
|
102
|
-
const children = typeof rawChildren === 'function'
|
|
103
|
-
? (rawChildren as () => VNodeChild)()
|
|
104
|
-
: rawChildren
|
|
105
|
-
|
|
106
|
-
const injectItemProps = typeof itemProps === 'function' ? itemProps : () => itemProps
|
|
107
|
-
|
|
108
|
-
const injectWrapItemProps = typeof wrapProps === 'function' ? wrapProps : () => wrapProps
|
|
109
|
-
|
|
110
|
-
const getKey = (item: string | number, index: number) => {
|
|
111
|
-
if (typeof itemKey === 'function') return itemKey(item, index)
|
|
112
|
-
return index
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const renderChild = (child: VNodeChild, total = 1, i = 0) => {
|
|
116
|
-
if (!itemProps && !Wrapper) return child
|
|
117
|
-
|
|
118
|
-
const extendedProps = attachItemProps({
|
|
119
|
-
i,
|
|
120
|
-
length: total,
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const finalItemProps = itemProps ? injectItemProps({}, extendedProps) : {}
|
|
124
|
-
|
|
125
|
-
if (Wrapper) {
|
|
126
|
-
const finalWrapProps = wrapProps ? injectWrapItemProps({}, extendedProps) : {}
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<Wrapper key={i} {...finalWrapProps}>
|
|
130
|
-
{render(child, finalItemProps)}
|
|
131
|
-
</Wrapper>
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return render(child, {
|
|
136
|
-
key: i,
|
|
137
|
-
...finalItemProps,
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// --------------------------------------------------------
|
|
142
|
-
// render children
|
|
143
|
-
// --------------------------------------------------------
|
|
144
|
-
const renderChildren = () => {
|
|
145
|
-
if (!children) return null
|
|
146
|
-
|
|
147
|
-
// if children is Array
|
|
148
|
-
if (Array.isArray(children)) {
|
|
149
|
-
return children.map((item, i) => renderChild(item, children.length, i))
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// if children is Fragment — check VNode type
|
|
153
|
-
if (
|
|
154
|
-
typeof children === 'object' &&
|
|
155
|
-
'type' in (children as VNode) &&
|
|
156
|
-
(children as VNode).type === Fragment
|
|
157
|
-
) {
|
|
158
|
-
const fragmentChildren = (children as VNode).children as VNodeChild[]
|
|
159
|
-
const childrenLength = fragmentChildren.length
|
|
160
|
-
|
|
161
|
-
return fragmentChildren.map((item, i) => renderChild(item, childrenLength, i))
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// if single child
|
|
165
|
-
return renderChild(children)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// --------------------------------------------------------
|
|
169
|
-
// render array of strings or numbers
|
|
170
|
-
// --------------------------------------------------------
|
|
171
|
-
const renderSimpleArray = (simpleData: SimpleValue[]) => {
|
|
172
|
-
const { length } = simpleData
|
|
173
|
-
|
|
174
|
-
if (length === 0) return null
|
|
175
|
-
|
|
176
|
-
return simpleData.map((item, i) => {
|
|
177
|
-
const key = getKey(item, i)
|
|
178
|
-
const keyName = valueName ?? 'children'
|
|
179
|
-
const extendedProps = attachItemProps({
|
|
180
|
-
i,
|
|
181
|
-
length,
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
const finalItemProps = {
|
|
185
|
-
...(itemProps ? injectItemProps({ [keyName]: item }, extendedProps) : {}),
|
|
186
|
-
[keyName]: item,
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (Wrapper) {
|
|
190
|
-
const finalWrapProps = wrapProps
|
|
191
|
-
? injectWrapItemProps({ [keyName]: item }, extendedProps)
|
|
192
|
-
: {}
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<Wrapper key={key} {...finalWrapProps}>
|
|
196
|
-
{render(component, finalItemProps)}
|
|
197
|
-
</Wrapper>
|
|
198
|
-
)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return render(component, { key, ...finalItemProps })
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// --------------------------------------------------------
|
|
206
|
-
// render array of objects
|
|
207
|
-
// --------------------------------------------------------
|
|
208
|
-
const getObjectKey = (item: ObjectValue, index: number) => {
|
|
209
|
-
if (!itemKey) return item.key ?? item.id ?? item.itemId ?? index
|
|
210
|
-
if (typeof itemKey === 'function') return itemKey(item, index)
|
|
211
|
-
if (typeof itemKey === 'string') return item[itemKey]
|
|
212
|
-
|
|
213
|
-
return index
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const renderComplexArray = (complexData: ObjectValue[]) => {
|
|
217
|
-
const { length } = complexData
|
|
218
|
-
|
|
219
|
-
if (length === 0) return null
|
|
220
|
-
|
|
221
|
-
return complexData.map((item, i) => {
|
|
222
|
-
const { component: itemComponent, ...restItem } = item
|
|
223
|
-
const renderItem = itemComponent ?? component
|
|
224
|
-
const key = getObjectKey(restItem, i)
|
|
225
|
-
const extendedProps = attachItemProps({
|
|
226
|
-
i,
|
|
227
|
-
length,
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const finalItemProps = {
|
|
231
|
-
...(itemProps ? injectItemProps(item, extendedProps) : {}),
|
|
232
|
-
...restItem,
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (Wrapper && !itemComponent) {
|
|
236
|
-
const finalWrapProps = wrapProps ? injectWrapItemProps(item, extendedProps) : {}
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<Wrapper key={key} {...finalWrapProps}>
|
|
240
|
-
{render(renderItem, finalItemProps)}
|
|
241
|
-
</Wrapper>
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return render(renderItem, { key, ...finalItemProps })
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// --------------------------------------------------------
|
|
250
|
-
// render list items
|
|
251
|
-
// --------------------------------------------------------
|
|
252
|
-
const renderItems = (): VNodeChild => {
|
|
253
|
-
// children have priority over props component + data
|
|
254
|
-
if (children) return renderChildren() as VNodeChild
|
|
255
|
-
|
|
256
|
-
// render props component + data
|
|
257
|
-
if (component && Array.isArray(data)) {
|
|
258
|
-
const classified = classifyData(data)
|
|
259
|
-
if (!classified) return null
|
|
260
|
-
if (classified.type === 'simple') return renderSimpleArray(classified.data) as VNodeChild
|
|
261
|
-
return renderComplexArray(classified.data) as VNodeChild
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return null
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return renderItems()
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ---------------------------------------------------------------------------
|
|
271
|
-
// Public callable type — overloads expose the generic `<T>` API at the JSX
|
|
272
|
-
// boundary while the impl stays loose-typed. TS picks the matching overload
|
|
273
|
-
// based on the props object passed:
|
|
274
|
-
//
|
|
275
|
-
// <Iterator data={['a','b']} valueName="text" component={Item} />
|
|
276
|
-
// ^ T inferred as string → SimpleProps<string> overload selected
|
|
277
|
-
//
|
|
278
|
-
// <Iterator data={users} component={UserCard} />
|
|
279
|
-
// ^ T inferred as User → ObjectProps<User> overload selected
|
|
280
|
-
//
|
|
281
|
-
// <Iterator>{...}</Iterator> → ChildrenProps overload selected
|
|
282
|
-
// ---------------------------------------------------------------------------
|
|
283
|
-
export interface IteratorComponent {
|
|
284
|
-
// T is inferred from the `data` prop at the JSX site — no explicit
|
|
285
|
-
// generic argument needed. Order matters: SimpleProps first (matches
|
|
286
|
-
// `data: SimpleValue[]`), then ObjectProps (object[]), then ChildrenProps,
|
|
287
|
-
// then a LooseProps fallback.
|
|
288
|
-
//
|
|
289
|
-
// The narrow overloads (Simple / Object / Children) drive per-mode T
|
|
290
|
-
// inference and stricter compile-time errors for direct callers (e.g.
|
|
291
|
-
// `valueName` required for primitive arrays, forbidden for object arrays).
|
|
292
|
-
// The LooseProps fallback exists for forwarding patterns where the props
|
|
293
|
-
// type is a wide union that doesn't bind to any single narrow overload —
|
|
294
|
-
// notably `Partial<(typeof Wrapper)['$$types']>` spread back into the JSX
|
|
295
|
-
// site after `@pyreon/rocketstyle`'s 4-overload-aware `ExtractProps`
|
|
296
|
-
// distributes the union across all of Iterator's call signatures. Without
|
|
297
|
-
// the loose binding home, the wide union has nowhere to land and TS
|
|
298
|
-
// reports "no overload matches this call" at every forwarding site.
|
|
299
|
-
//
|
|
300
|
-
// Direct callers still see the strict per-mode errors — the loose fallback
|
|
301
|
-
// only fires when none of the three narrow overloads match.
|
|
302
|
-
<T extends SimpleValue>(props: SimpleProps<T>): VNodeChild
|
|
303
|
-
<T extends ObjectValue>(props: ObjectProps<T>): VNodeChild
|
|
304
|
-
(props: ChildrenProps): VNodeChild
|
|
305
|
-
(props: LooseProps): VNodeChild
|
|
306
|
-
isIterator: true
|
|
307
|
-
RESERVED_PROPS: typeof RESERVED_PROPS
|
|
308
|
-
displayName?: string
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const Iterator = Object.assign(Component, {
|
|
312
|
-
isIterator: true as const,
|
|
313
|
-
RESERVED_PROPS,
|
|
314
|
-
}) as unknown as IteratorComponent
|
|
315
|
-
|
|
316
|
-
export default Iterator
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import component from './component'
|
|
2
|
-
import type {
|
|
3
|
-
ChildrenProps,
|
|
4
|
-
ElementType,
|
|
5
|
-
ExtendedProps,
|
|
6
|
-
LooseProps,
|
|
7
|
-
MaybeNull,
|
|
8
|
-
ObjectProps,
|
|
9
|
-
ObjectValue,
|
|
10
|
-
Props,
|
|
11
|
-
PropsCallback,
|
|
12
|
-
SimpleProps,
|
|
13
|
-
SimpleValue,
|
|
14
|
-
} from './types'
|
|
15
|
-
|
|
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
|
-
}
|
|
29
|
-
|
|
30
|
-
export default component
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { ComponentFn, VNodeChild } from '@pyreon/core'
|
|
2
|
-
import type { HTMLTags } from '@pyreon/ui-core'
|
|
3
|
-
|
|
4
|
-
export type MaybeNull = undefined | null
|
|
5
|
-
export type TObj = Record<string, unknown>
|
|
6
|
-
export type SimpleValue = string | number
|
|
7
|
-
export type ObjectValue = Partial<{
|
|
8
|
-
id: SimpleValue
|
|
9
|
-
key: SimpleValue
|
|
10
|
-
itemId: SimpleValue
|
|
11
|
-
component: ElementType
|
|
12
|
-
}> &
|
|
13
|
-
Record<string, unknown>
|
|
14
|
-
|
|
15
|
-
export type ElementType<T extends Record<string, unknown> = any> = ComponentFn<T> | HTMLTags
|
|
16
|
-
|
|
17
|
-
export type ExtendedProps = {
|
|
18
|
-
index: number
|
|
19
|
-
first: boolean
|
|
20
|
-
last: boolean
|
|
21
|
-
odd: boolean
|
|
22
|
-
even: boolean
|
|
23
|
-
position: number
|
|
24
|
-
}
|
|
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
|
-
|
|
100
|
-
export type PropsCallback =
|
|
101
|
-
| TObj
|
|
102
|
-
| ((
|
|
103
|
-
itemProps: Record<string, never> | Record<string, SimpleValue> | ObjectValue,
|
|
104
|
-
extendedProps: ExtendedProps,
|
|
105
|
-
) => TObj)
|
|
106
|
-
|
|
107
|
-
export type LooseProps = Partial<{
|
|
108
|
-
children: VNodeChild
|
|
109
|
-
data: Array<SimpleValue | ObjectValue | MaybeNull>
|
|
110
|
-
component: ElementType
|
|
111
|
-
valueName: string
|
|
112
|
-
wrapComponent: ElementType
|
|
113
|
-
itemProps: PropsCallback
|
|
114
|
-
wrapProps: PropsCallback
|
|
115
|
-
itemKey:
|
|
116
|
-
| keyof ObjectValue
|
|
117
|
-
| ((item: SimpleValue | Omit<ObjectValue, 'component'>, index: number) => SimpleValue)
|
|
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
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wrapper component that serves as the outermost styled container for Element.
|
|
3
|
-
* On web, it detects button/fieldset/legend tags and applies a two-layer flex
|
|
4
|
-
* fix (parent + child Styled) because these HTML elements do not natively
|
|
5
|
-
* support `display: flex` consistently across browsers.
|
|
6
|
-
*/
|
|
7
|
-
import { h, splitProps } from '@pyreon/core'
|
|
8
|
-
import { getShouldBeEmpty } from '../../Element/utils'
|
|
9
|
-
import { IS_DEVELOPMENT } from '../../utils'
|
|
10
|
-
import { internElementBundle } from '../internElementBundle'
|
|
11
|
-
import Styled from './styled'
|
|
12
|
-
import type { Props } from './types'
|
|
13
|
-
import { isWebFixNeeded } from './utils'
|
|
14
|
-
|
|
15
|
-
const DEV_PROPS: Record<string, string> = IS_DEVELOPMENT ? { 'data-pyr-element': 'Element' } : {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Build a props object for `h(Styled, ...)` by copying own property
|
|
19
|
-
* DESCRIPTORS from `rest`, then layering the additional fields. Compiler-
|
|
20
|
-
* emitted reactive props (`_rp(() => signal())` converted to getters by
|
|
21
|
-
* `makeReactiveProps`) survive end-to-end with their getter intact.
|
|
22
|
-
*
|
|
23
|
-
* Why we bypass JSX spread here: the standard JSX automatic-runtime
|
|
24
|
-
* compilation lowers `<Styled {...rest} foo={x}>` to roughly
|
|
25
|
-
* `jsx(Styled, { ...rest, foo: x })`. That `{...rest, foo: x}` object
|
|
26
|
-
* literal is evaluated at JS level — it fires every getter on `rest` and
|
|
27
|
-
* stores the resolved value before `jsx()` ever sees the object. No
|
|
28
|
-
* amount of in-runtime descriptor preservation can recover the getters
|
|
29
|
-
* once they've been collapsed by the surface-level spread. The fix is
|
|
30
|
-
* structural: don't use JSX spread for reactive-prop forwarding. Build
|
|
31
|
-
* the props object with descriptor preservation and pass it to `h()`
|
|
32
|
-
* directly — `h()` stores props as-is on the vnode, no copy, getters
|
|
33
|
-
* survive into mount.
|
|
34
|
-
*/
|
|
35
|
-
const buildStyledProps = (
|
|
36
|
-
rest: Record<string, unknown>,
|
|
37
|
-
refValue: unknown,
|
|
38
|
-
asTag: unknown,
|
|
39
|
-
extras: Record<string, unknown>,
|
|
40
|
-
): Record<string, unknown> => {
|
|
41
|
-
const result: Record<string, unknown> = {}
|
|
42
|
-
const descriptors = Object.getOwnPropertyDescriptors(rest)
|
|
43
|
-
for (const key in descriptors) {
|
|
44
|
-
Object.defineProperty(result, key, descriptors[key]!)
|
|
45
|
-
}
|
|
46
|
-
for (const key in DEV_PROPS) {
|
|
47
|
-
result[key] = DEV_PROPS[key]
|
|
48
|
-
}
|
|
49
|
-
result.ref = refValue
|
|
50
|
-
result.as = asTag
|
|
51
|
-
for (const key in extras) {
|
|
52
|
-
result[key] = extras[key]
|
|
53
|
-
}
|
|
54
|
-
return result
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Layout / ref keys consumed by Wrapper itself. Everything else is forwarded
|
|
58
|
-
// onto the underlying DOM node. Listed as a tuple so `splitProps` narrows
|
|
59
|
-
// `own` correctly while preserving reactive prop tracking on both halves.
|
|
60
|
-
const OWN_KEYS: Array<keyof Props | 'ref'> = [
|
|
61
|
-
'children',
|
|
62
|
-
'tag',
|
|
63
|
-
'block',
|
|
64
|
-
'extendCss',
|
|
65
|
-
'direction',
|
|
66
|
-
'alignX',
|
|
67
|
-
'alignY',
|
|
68
|
-
'equalCols',
|
|
69
|
-
'isInline',
|
|
70
|
-
'ref',
|
|
71
|
-
'dangerouslySetInnerHTML',
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
const Component = (props: Partial<Props> & { ref?: unknown }) => {
|
|
75
|
-
const [own, rest] = splitProps(props, OWN_KEYS)
|
|
76
|
-
|
|
77
|
-
const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag)
|
|
78
|
-
|
|
79
|
-
// Void HTML elements (hr, input, img, br, …) cannot have children. Even
|
|
80
|
-
// a falsy `{own.children}` slot becomes `[undefined]` in the vnode and
|
|
81
|
-
// trips runtime-dom's void-element warning. Element already skips passing
|
|
82
|
-
// children to Wrapper for void tags; this guard makes sure the empty
|
|
83
|
-
// slot is dropped here too instead of leaking into the JSX.
|
|
84
|
-
const isVoidTag = !own.dangerouslySetInnerHTML && getShouldBeEmpty(own.tag)
|
|
85
|
-
|
|
86
|
-
// dangerouslySetInnerHTML and children are mutually exclusive — both
|
|
87
|
-
// become inner content (per `runtime-server/src/index.ts:228` and
|
|
88
|
-
// `runtime-dom/src/props.ts:289`). Pre-fix the prop was in OWN_KEYS,
|
|
89
|
-
// moved into `own` by splitProps, and never re-attached to the rendered
|
|
90
|
-
// vnode — so `<Logo dangerouslySetInnerHTML={...} />` rendered an empty
|
|
91
|
-
// <div></div>. Forward the prop to the styled vnode and drop the
|
|
92
|
-
// children slot when innerHTML is set.
|
|
93
|
-
const innerHTML = own.dangerouslySetInnerHTML
|
|
94
|
-
|
|
95
|
-
if (!needsFix) {
|
|
96
|
-
const bundle = internElementBundle({
|
|
97
|
-
block: own.block,
|
|
98
|
-
direction: own.direction,
|
|
99
|
-
alignX: own.alignX,
|
|
100
|
-
alignY: own.alignY,
|
|
101
|
-
equalCols: own.equalCols,
|
|
102
|
-
extraStyles: own.extendCss,
|
|
103
|
-
})
|
|
104
|
-
if (isVoidTag) {
|
|
105
|
-
return h(
|
|
106
|
-
Styled,
|
|
107
|
-
buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
|
|
108
|
-
$element: bundle,
|
|
109
|
-
}),
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
if (innerHTML) {
|
|
113
|
-
return h(
|
|
114
|
-
Styled,
|
|
115
|
-
buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
|
|
116
|
-
$element: bundle,
|
|
117
|
-
dangerouslySetInnerHTML: innerHTML,
|
|
118
|
-
}),
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
return h(
|
|
122
|
-
Styled,
|
|
123
|
-
buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
|
|
124
|
-
$element: bundle,
|
|
125
|
-
children: own.children,
|
|
126
|
-
}),
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const asTag = own.isInline ? 'span' : 'div'
|
|
131
|
-
const parentBundle = internElementBundle({
|
|
132
|
-
parentFix: true as const,
|
|
133
|
-
block: own.block,
|
|
134
|
-
extraStyles: own.extendCss,
|
|
135
|
-
})
|
|
136
|
-
const childBundle = internElementBundle({
|
|
137
|
-
childFix: true as const,
|
|
138
|
-
direction: own.direction,
|
|
139
|
-
alignX: own.alignX,
|
|
140
|
-
alignY: own.alignY,
|
|
141
|
-
equalCols: own.equalCols,
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// needsFix path: innerHTML belongs on the INNER styled node (where the
|
|
145
|
-
// actual content lives), NOT on the outer flex-fix wrapper. The
|
|
146
|
-
// `needsFix` computation already excludes the innerHTML case
|
|
147
|
-
// (`!own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag)`), so this
|
|
148
|
-
// branch normally won't execute when innerHTML is set — but we keep
|
|
149
|
-
// the defensive forwarding so the contract is robust against future
|
|
150
|
-
// refactors of the needsFix gate.
|
|
151
|
-
if (innerHTML) {
|
|
152
|
-
return h(
|
|
153
|
-
Styled,
|
|
154
|
-
buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
|
|
155
|
-
$element: parentBundle,
|
|
156
|
-
children: h(Styled, {
|
|
157
|
-
as: asTag,
|
|
158
|
-
$childFix: true,
|
|
159
|
-
$element: childBundle,
|
|
160
|
-
dangerouslySetInnerHTML: innerHTML,
|
|
161
|
-
}),
|
|
162
|
-
}),
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return h(
|
|
167
|
-
Styled,
|
|
168
|
-
buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
|
|
169
|
-
$element: parentBundle,
|
|
170
|
-
children: h(Styled, {
|
|
171
|
-
as: asTag,
|
|
172
|
-
$childFix: true,
|
|
173
|
-
$element: childBundle,
|
|
174
|
-
children: own.children,
|
|
175
|
-
}),
|
|
176
|
-
}),
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export default Component
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML elements that need a two-layer DOM workaround because browsers do not
|
|
3
|
-
* fully support flexbox layout on button, fieldset, and legend elements.
|
|
4
|
-
* @see https://stackoverflow.com/questions/35464067/flexbox-not-working-on-button-or-fieldset-elements
|
|
5
|
-
*/
|
|
6
|
-
export const INLINE_ELEMENTS_FLEX_FIX = {
|
|
7
|
-
button: true,
|
|
8
|
-
fieldset: true,
|
|
9
|
-
legend: true,
|
|
10
|
-
}
|