@pyreon/elements 0.24.5 → 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
package/src/List/component.tsx
DELETED
|
@@ -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,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
|
package/src/Overlay/context.tsx
DELETED
|
@@ -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
|
package/src/Overlay/index.ts
DELETED
|
@@ -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
|
-
}
|