@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.
Files changed (70) hide show
  1. package/package.json +10 -12
  2. package/src/Element/component.tsx +0 -315
  3. package/src/Element/constants.ts +0 -96
  4. package/src/Element/index.ts +0 -6
  5. package/src/Element/types.ts +0 -168
  6. package/src/Element/utils.ts +0 -15
  7. package/src/List/component.tsx +0 -105
  8. package/src/List/index.ts +0 -5
  9. package/src/Overlay/component.tsx +0 -140
  10. package/src/Overlay/context.tsx +0 -36
  11. package/src/Overlay/index.ts +0 -7
  12. package/src/Overlay/positioning.ts +0 -191
  13. package/src/Overlay/useOverlay.tsx +0 -461
  14. package/src/Portal/component.tsx +0 -54
  15. package/src/Portal/index.ts +0 -5
  16. package/src/Text/component.tsx +0 -67
  17. package/src/Text/index.ts +0 -5
  18. package/src/Text/styled.ts +0 -30
  19. package/src/Util/component.tsx +0 -43
  20. package/src/Util/index.ts +0 -5
  21. package/src/__tests__/Content.test.tsx +0 -123
  22. package/src/__tests__/Element-slot-reactivity.browser.test.tsx +0 -152
  23. package/src/__tests__/Element.test.ts +0 -819
  24. package/src/__tests__/Iterator.test.ts +0 -492
  25. package/src/__tests__/Iterator.types.test.ts +0 -237
  26. package/src/__tests__/List.test.ts +0 -199
  27. package/src/__tests__/Overlay.test.ts +0 -492
  28. package/src/__tests__/Portal.test.ts +0 -156
  29. package/src/__tests__/Text.test.ts +0 -274
  30. package/src/__tests__/Util.test.ts +0 -63
  31. package/src/__tests__/Wrapper-innerhtml.test.tsx +0 -178
  32. package/src/__tests__/Wrapper.test.tsx +0 -196
  33. package/src/__tests__/elements.browser.test.tsx +0 -132
  34. package/src/__tests__/equalBeforeAfter.test.ts +0 -122
  35. package/src/__tests__/helpers.test.ts +0 -65
  36. package/src/__tests__/integration.test.tsx +0 -118
  37. package/src/__tests__/internElementBundle.test.ts +0 -102
  38. package/src/__tests__/iterator-function-children.test.tsx +0 -120
  39. package/src/__tests__/native-markers.test.ts +0 -13
  40. package/src/__tests__/overlayContext.test.tsx +0 -78
  41. package/src/__tests__/perf-stress.browser.test.tsx +0 -119
  42. package/src/__tests__/positioning.test.ts +0 -90
  43. package/src/__tests__/responsiveProps.test.ts +0 -328
  44. package/src/__tests__/slot-component-reference.test.tsx +0 -157
  45. package/src/__tests__/useOverlay.test.ts +0 -1336
  46. package/src/__tests__/utils.test.ts +0 -69
  47. package/src/__tests__/wrapper-block-cascade.test.ts +0 -121
  48. package/src/constants.ts +0 -1
  49. package/src/env.d.ts +0 -6
  50. package/src/helpers/Content/component.tsx +0 -75
  51. package/src/helpers/Content/index.ts +0 -3
  52. package/src/helpers/Content/styled.ts +0 -105
  53. package/src/helpers/Content/types.ts +0 -49
  54. package/src/helpers/Iterator/component.tsx +0 -316
  55. package/src/helpers/Iterator/index.ts +0 -30
  56. package/src/helpers/Iterator/types.ts +0 -138
  57. package/src/helpers/Wrapper/component.tsx +0 -180
  58. package/src/helpers/Wrapper/constants.ts +0 -10
  59. package/src/helpers/Wrapper/index.ts +0 -3
  60. package/src/helpers/Wrapper/styled.ts +0 -64
  61. package/src/helpers/Wrapper/types.ts +0 -56
  62. package/src/helpers/Wrapper/utils.ts +0 -7
  63. package/src/helpers/index.ts +0 -4
  64. package/src/helpers/internElementBundle.ts +0 -37
  65. package/src/helpers/isPyreonComponent.ts +0 -46
  66. package/src/index.ts +0 -42
  67. package/src/manifest.ts +0 -190
  68. package/src/tests/manifest-snapshot.test.ts +0 -45
  69. package/src/types.ts +0 -112
  70. package/src/utils.ts +0 -5
@@ -1,105 +0,0 @@
1
- /**
2
- * List component that combines Iterator (data-driven rendering) with an
3
- * optional Element root wrapper. When `rootElement` is false (default),
4
- * it renders a bare Iterator as a fragment. When true, the Iterator output
5
- * is wrapped in an Element that receives all non-iterator props (e.g.,
6
- * layout, alignment, css), allowing the list to be styled as a single block.
7
- */
8
- import type { VNodeChild } from '@pyreon/core'
9
- import { splitProps } from '@pyreon/core'
10
- import { omit, pick } from '@pyreon/ui-core'
11
- import { PKG_NAME } from '../constants'
12
- import type { ElementProps } from '../Element'
13
- import { Element } from '../Element'
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'
23
- import Iterator from '../helpers/Iterator'
24
- import type { MergeTypes } from '../types'
25
-
26
- type ListOnly = {
27
- /**
28
- * A boolean value. When set to `false`, component returns fragment.
29
- * When set to `true`, component returns as the **root** element `Element`
30
- * component.
31
- */
32
- rootElement?: boolean
33
- /**
34
- * Label prop from `Element` component is being ignored.
35
- */
36
- label?: never
37
- /**
38
- * Content prop from `Element` component is being ignored.
39
- */
40
- content?: never
41
- }
42
-
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]>
60
-
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)} />
71
-
72
- if (!own.rootElement) return renderedList
73
-
74
- return (
75
- <Element ref={own.ref as ElementProps['ref']} {...omit(props, Iterator.RESERVED_PROPS)}>
76
- {renderedList}
77
- </Element>
78
- )
79
- }
80
-
81
- const name = `${PKG_NAME}/List` as const
82
-
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
- }
104
-
105
- export default Component as unknown as ListComponent
package/src/List/index.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { Props } from './component'
2
- import component from './component'
3
-
4
- export type { Props as ListProps }
5
- export { component as List }
@@ -1,140 +0,0 @@
1
- /**
2
- * Overlay component that renders a trigger element and conditionally shows
3
- * content via a Portal. The trigger receives a ref and optional show/hide
4
- * callbacks; the content is positioned and managed by the useOverlay hook.
5
- * A context Provider wraps the content to support nested overlays (e.g.,
6
- * a dropdown inside another dropdown) via blocked-state propagation.
7
- */
8
-
9
- import type { VNodeChild } from '@pyreon/core'
10
- import { nativeCompat, onMount, Portal, splitProps } from '@pyreon/core'
11
- import { render } from '@pyreon/ui-core'
12
- import { PKG_NAME } from '../constants'
13
- import type { Content, PyreonComponent } from '../types'
14
- import useOverlay, { type UseOverlayProps } from './useOverlay'
15
-
16
- const IS_BROWSER = typeof window !== 'undefined'
17
-
18
- type Align = 'bottom' | 'top' | 'left' | 'right'
19
- type AlignX = 'left' | 'center' | 'right'
20
- type AlignY = 'bottom' | 'top' | 'center'
21
-
22
- type TriggerRenderer = (
23
- props: Partial<{
24
- active: boolean
25
- showContent: () => void
26
- hideContent: () => void
27
- }>,
28
- ) => VNodeChild
29
-
30
- type ContentRenderer = (
31
- props: Partial<{
32
- active: boolean
33
- showContent: () => void
34
- hideContent: () => void
35
- align: Align
36
- alignX: AlignX
37
- alignY: AlignY
38
- }>,
39
- ) => VNodeChild
40
-
41
- export type Props = {
42
- children: ContentRenderer | Content
43
- trigger: TriggerRenderer | Content
44
- DOMLocation?: HTMLElement
45
- triggerRefName?: string
46
- contentRefName?: string
47
- } & UseOverlayProps
48
-
49
- const Component: PyreonComponent<Props> = (props) => {
50
- const [own, overlayProps] = splitProps(props, [
51
- 'children',
52
- 'trigger',
53
- 'DOMLocation',
54
- 'triggerRefName',
55
- 'contentRefName',
56
- ])
57
-
58
- const triggerRefName = own.triggerRefName ?? 'ref'
59
- const contentRefName = own.contentRefName ?? 'ref'
60
-
61
- const {
62
- active,
63
- triggerRef,
64
- contentRef,
65
- showContent,
66
- hideContent,
67
- align,
68
- alignX,
69
- alignY,
70
- setupListeners,
71
- Provider,
72
- ...ctx
73
- } = useOverlay(overlayProps)
74
-
75
- const { openOn, closeOn, type } = overlayProps
76
-
77
- const passHandlers =
78
- openOn === 'manual' || closeOn === 'manual' || closeOn === 'clickOutsideContent'
79
-
80
- const ariaHasPopup = (() => {
81
- switch (type) {
82
- case 'modal':
83
- return 'dialog' as const
84
- case 'tooltip':
85
- return 'true' as const
86
- default:
87
- return 'menu' as const
88
- }
89
- })()
90
-
91
- // Set up event listeners on mount
92
- onMount(() => {
93
- const cleanup = setupListeners()
94
- return cleanup
95
- })
96
-
97
- return (
98
- <>
99
- {render(own.trigger, {
100
- [triggerRefName]: triggerRef,
101
- active: active(),
102
- 'aria-expanded': active(),
103
- 'aria-haspopup': ariaHasPopup,
104
- ...(passHandlers ? { showContent, hideContent } : {}),
105
- })}
106
-
107
- {() =>
108
- IS_BROWSER && active() ? (
109
- <Portal target={own.DOMLocation ?? document.body}>
110
- <Provider {...ctx}>
111
- {render(own.children, {
112
- [contentRefName]: contentRef,
113
- role: type === 'modal' ? 'dialog' : undefined,
114
- 'aria-modal': type === 'modal' ? true : undefined,
115
- active: active(),
116
- align,
117
- alignX: alignX(),
118
- alignY: alignY(),
119
- ...(passHandlers ? { showContent, hideContent } : {}),
120
- })}
121
- </Provider>
122
- </Portal>
123
- ) : null
124
- }
125
- </>
126
- )
127
- }
128
-
129
- const name = `${PKG_NAME}/Overlay` as const
130
-
131
- Component.displayName = name
132
- Component.pkgName = PKG_NAME
133
- Component.PYREON__COMPONENT = name
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
-
140
- export default Component
@@ -1,36 +0,0 @@
1
- /**
2
- * Context for nested overlay coordination. When a child overlay opens, it
3
- * sets the parent's blocked state to true, preventing the parent from
4
- * closing in response to click/hover events that belong to the child.
5
- */
6
-
7
- import type { VNodeChild } from '@pyreon/core'
8
- import { createContext, nativeCompat, provide, useContext } from '@pyreon/core'
9
-
10
- export interface OverlayContext {
11
- blocked: boolean | (() => boolean)
12
- setBlocked: () => void
13
- setUnblocked: () => void
14
- }
15
-
16
- const context = createContext<OverlayContext>({} as OverlayContext)
17
-
18
- export const useOverlayContext = () => useContext(context)
19
-
20
- const Component = (props: OverlayContext & { children: VNodeChild }) => {
21
- const ctx = {
22
- blocked: props.blocked,
23
- setBlocked: props.setBlocked,
24
- setUnblocked: props.setUnblocked,
25
- }
26
-
27
- provide(context, ctx)
28
-
29
- return <>{props.children}</>
30
- }
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
-
36
- export default Component
@@ -1,7 +0,0 @@
1
- import component, { type Props } from './component'
2
- import OverlayProvider from './context'
3
- import useOverlay, { type UseOverlayProps } from './useOverlay'
4
-
5
- export type { Props as OverlayProps, UseOverlayProps }
6
-
7
- export { component as Overlay, OverlayProvider, useOverlay }
@@ -1,191 +0,0 @@
1
- /**
2
- * Pure positioning helpers for the Overlay component. Split out from
3
- * `useOverlay.tsx` so the SSR-fallback branches (`typeof window === 'undefined'`)
4
- * can be exercised directly by tests that stub `globalThis.window` — the
5
- * `useOverlay` hook itself runs these via event handlers registered inside
6
- * `onMount`, which are unreachable during module-level test imports in
7
- * happy-dom (where `window` is always defined).
8
- */
9
-
10
- export type OverlayPosition = Partial<{
11
- top: number | string
12
- bottom: number | string
13
- left: number | string
14
- right: number | string
15
- }>
16
-
17
- export type Align = 'bottom' | 'top' | 'left' | 'right'
18
- export type AlignX = 'left' | 'center' | 'right'
19
- export type AlignY = 'bottom' | 'top' | 'center'
20
-
21
- export type PositionResult = {
22
- pos: OverlayPosition
23
- resolvedAlignX: AlignX
24
- resolvedAlignY: AlignY
25
- }
26
-
27
- const sel = <T,>(cond: boolean, a: T, b: T): T => (cond ? a : b)
28
-
29
- export const calcDropdownVertical = (
30
- c: DOMRect,
31
- t: DOMRect,
32
- align: 'top' | 'bottom',
33
- alignX: AlignX,
34
- offsetX: number,
35
- offsetY: number,
36
- ): PositionResult => {
37
- // SSR-fallback: positioning only runs in the mounted browser context, but
38
- // the explicit guard documents the SSR-safety contract at the callsite
39
- // and lets `no-window-in-ssr` prove it locally. Return shape mirrors the
40
- // "no element" path below (empty `pos`, alignment preserved).
41
- if (typeof window === 'undefined') return { pos: {}, resolvedAlignX: alignX, resolvedAlignY: align }
42
- const pos: OverlayPosition = {}
43
-
44
- const topPos = t.top - offsetY - c.height
45
- const bottomPos = t.bottom + offsetY
46
- const leftPos = t.left + offsetX
47
- const rightPos = t.right - offsetX - c.width
48
-
49
- const fitsTop = topPos >= 0
50
- const fitsBottom = bottomPos + c.height <= window.innerHeight
51
- const fitsLeft = leftPos + c.width <= window.innerWidth
52
- const fitsRight = rightPos >= 0
53
-
54
- const useTop = sel(align === 'top', fitsTop, !fitsBottom)
55
- pos.top = sel(useTop, topPos, bottomPos)
56
- const resolvedAlignY: AlignY = sel(useTop, 'top', 'bottom')
57
-
58
- let resolvedAlignX: AlignX = alignX
59
- if (alignX === 'left') {
60
- pos.left = sel(fitsLeft, leftPos, rightPos)
61
- resolvedAlignX = sel(fitsLeft, 'left', 'right')
62
- } else if (alignX === 'right') {
63
- pos.left = sel(fitsRight, rightPos, leftPos)
64
- resolvedAlignX = sel(fitsRight, 'right', 'left')
65
- } else {
66
- const center = t.left + (t.right - t.left) / 2 - c.width / 2
67
- const fitsCL = center >= 0
68
- const fitsCR = center + c.width <= window.innerWidth
69
-
70
- if (fitsCL && fitsCR) {
71
- resolvedAlignX = 'center'
72
- pos.left = center
73
- } else if (fitsCL) {
74
- resolvedAlignX = 'left'
75
- pos.left = leftPos
76
- } else if (fitsCR) {
77
- resolvedAlignX = 'right'
78
- pos.left = rightPos
79
- }
80
- }
81
-
82
- return { pos, resolvedAlignX, resolvedAlignY }
83
- }
84
-
85
- export const calcDropdownHorizontal = (
86
- c: DOMRect,
87
- t: DOMRect,
88
- align: 'left' | 'right',
89
- alignY: AlignY,
90
- offsetX: number,
91
- offsetY: number,
92
- ): PositionResult => {
93
- if (typeof window === 'undefined') return { pos: {}, resolvedAlignX: align, resolvedAlignY: alignY }
94
- const pos: OverlayPosition = {}
95
-
96
- const leftPos = t.left - offsetX - c.width
97
- const rightPos = t.right + offsetX
98
- const topPos = t.top + offsetY
99
- const bottomPos = t.bottom - offsetY - c.height
100
-
101
- const fitsLeft = leftPos >= 0
102
- const fitsRight = rightPos + c.width <= window.innerWidth
103
- const fitsTop = topPos + c.height <= window.innerHeight
104
- const fitsBottom = bottomPos >= 0
105
-
106
- const useLeft = sel(align === 'left', fitsLeft, !fitsRight)
107
- pos.left = sel(useLeft, leftPos, rightPos)
108
- const resolvedAlignX: AlignX = sel(useLeft, 'left', 'right')
109
-
110
- let resolvedAlignY: AlignY = alignY
111
- if (alignY === 'top') {
112
- pos.top = sel(fitsTop, topPos, bottomPos)
113
- resolvedAlignY = sel(fitsTop, 'top', 'bottom')
114
- } else if (alignY === 'bottom') {
115
- pos.top = sel(fitsBottom, bottomPos, topPos)
116
- resolvedAlignY = sel(fitsBottom, 'bottom', 'top')
117
- } else {
118
- const center = t.top + (t.bottom - t.top) / 2 - c.height / 2
119
- const fitsCT = center >= 0
120
- const fitsCB = center + c.height <= window.innerHeight
121
-
122
- if (fitsCT && fitsCB) {
123
- resolvedAlignY = 'center'
124
- pos.top = center
125
- } else if (fitsCT) {
126
- resolvedAlignY = 'top'
127
- pos.top = topPos
128
- } else if (fitsCB) {
129
- resolvedAlignY = 'bottom'
130
- pos.top = bottomPos
131
- }
132
- }
133
-
134
- return { pos, resolvedAlignX, resolvedAlignY }
135
- }
136
-
137
- export const calcModalPos = (
138
- c: DOMRect,
139
- alignX: AlignX,
140
- alignY: AlignY,
141
- offsetX: number,
142
- offsetY: number,
143
- ): OverlayPosition => {
144
- if (typeof window === 'undefined') return {}
145
- const pos: OverlayPosition = {}
146
-
147
- switch (alignX) {
148
- case 'right':
149
- pos.right = offsetX
150
- break
151
- case 'left':
152
- pos.left = offsetX
153
- break
154
- case 'center':
155
- pos.left = window.innerWidth / 2 - c.width / 2
156
- break
157
- default:
158
- pos.right = offsetX
159
- }
160
-
161
- switch (alignY) {
162
- case 'top':
163
- pos.top = offsetY
164
- break
165
- case 'center':
166
- pos.top = window.innerHeight / 2 - c.height / 2
167
- break
168
- case 'bottom':
169
- pos.bottom = offsetY
170
- break
171
- default:
172
- pos.top = offsetY
173
- }
174
-
175
- return pos
176
- }
177
-
178
- export const adjustForAncestor = (
179
- pos: OverlayPosition,
180
- ancestor: { top: number; left: number },
181
- ): OverlayPosition => {
182
- if (ancestor.top === 0 && ancestor.left === 0) return pos
183
-
184
- const result = { ...pos }
185
- if (typeof result.top === 'number') result.top -= ancestor.top
186
- if (typeof result.bottom === 'number') result.bottom += ancestor.top
187
- if (typeof result.left === 'number') result.left -= ancestor.left
188
- if (typeof result.right === 'number') result.right += ancestor.left
189
-
190
- return result
191
- }