@pyreon/elements 0.24.5 → 0.25.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.
Files changed (71) hide show
  1. package/lib/index.js +5 -1
  2. package/package.json +11 -13
  3. package/src/Element/component.tsx +0 -315
  4. package/src/Element/constants.ts +0 -96
  5. package/src/Element/index.ts +0 -6
  6. package/src/Element/types.ts +0 -168
  7. package/src/Element/utils.ts +0 -15
  8. package/src/List/component.tsx +0 -105
  9. package/src/List/index.ts +0 -5
  10. package/src/Overlay/component.tsx +0 -140
  11. package/src/Overlay/context.tsx +0 -36
  12. package/src/Overlay/index.ts +0 -7
  13. package/src/Overlay/positioning.ts +0 -191
  14. package/src/Overlay/useOverlay.tsx +0 -461
  15. package/src/Portal/component.tsx +0 -54
  16. package/src/Portal/index.ts +0 -5
  17. package/src/Text/component.tsx +0 -67
  18. package/src/Text/index.ts +0 -5
  19. package/src/Text/styled.ts +0 -30
  20. package/src/Util/component.tsx +0 -43
  21. package/src/Util/index.ts +0 -5
  22. package/src/__tests__/Content.test.tsx +0 -123
  23. package/src/__tests__/Element-slot-reactivity.browser.test.tsx +0 -152
  24. package/src/__tests__/Element.test.ts +0 -819
  25. package/src/__tests__/Iterator.test.ts +0 -492
  26. package/src/__tests__/Iterator.types.test.ts +0 -237
  27. package/src/__tests__/List.test.ts +0 -199
  28. package/src/__tests__/Overlay.test.ts +0 -492
  29. package/src/__tests__/Portal.test.ts +0 -156
  30. package/src/__tests__/Text.test.ts +0 -274
  31. package/src/__tests__/Util.test.ts +0 -63
  32. package/src/__tests__/Wrapper-innerhtml.test.tsx +0 -178
  33. package/src/__tests__/Wrapper.test.tsx +0 -196
  34. package/src/__tests__/elements.browser.test.tsx +0 -132
  35. package/src/__tests__/equalBeforeAfter.test.ts +0 -122
  36. package/src/__tests__/helpers.test.ts +0 -65
  37. package/src/__tests__/integration.test.tsx +0 -118
  38. package/src/__tests__/internElementBundle.test.ts +0 -102
  39. package/src/__tests__/iterator-function-children.test.tsx +0 -120
  40. package/src/__tests__/native-markers.test.ts +0 -13
  41. package/src/__tests__/overlayContext.test.tsx +0 -78
  42. package/src/__tests__/perf-stress.browser.test.tsx +0 -119
  43. package/src/__tests__/positioning.test.ts +0 -90
  44. package/src/__tests__/responsiveProps.test.ts +0 -328
  45. package/src/__tests__/slot-component-reference.test.tsx +0 -157
  46. package/src/__tests__/useOverlay.test.ts +0 -1336
  47. package/src/__tests__/utils.test.ts +0 -69
  48. package/src/__tests__/wrapper-block-cascade.test.ts +0 -121
  49. package/src/constants.ts +0 -1
  50. package/src/env.d.ts +0 -6
  51. package/src/helpers/Content/component.tsx +0 -75
  52. package/src/helpers/Content/index.ts +0 -3
  53. package/src/helpers/Content/styled.ts +0 -105
  54. package/src/helpers/Content/types.ts +0 -49
  55. package/src/helpers/Iterator/component.tsx +0 -316
  56. package/src/helpers/Iterator/index.ts +0 -30
  57. package/src/helpers/Iterator/types.ts +0 -138
  58. package/src/helpers/Wrapper/component.tsx +0 -180
  59. package/src/helpers/Wrapper/constants.ts +0 -10
  60. package/src/helpers/Wrapper/index.ts +0 -3
  61. package/src/helpers/Wrapper/styled.ts +0 -64
  62. package/src/helpers/Wrapper/types.ts +0 -56
  63. package/src/helpers/Wrapper/utils.ts +0 -7
  64. package/src/helpers/index.ts +0 -4
  65. package/src/helpers/internElementBundle.ts +0 -37
  66. package/src/helpers/isPyreonComponent.ts +0 -46
  67. package/src/index.ts +0 -42
  68. package/src/manifest.ts +0 -190
  69. package/src/tests/manifest-snapshot.test.ts +0 -45
  70. package/src/types.ts +0 -112
  71. package/src/utils.ts +0 -5
@@ -1,123 +0,0 @@
1
- import type { VNode } from '@pyreon/core'
2
- import { beforeEach, describe, expect, it, vi } from 'vitest'
3
-
4
- // ---------------------------------------------------------------------------
5
- // Mocks
6
- // ---------------------------------------------------------------------------
7
- const mocks = vi.hoisted(() => ({
8
- render: vi.fn((children: unknown) => children),
9
- }))
10
-
11
- vi.mock('@pyreon/ui-core', async (importOriginal) => {
12
- const actual = (await importOriginal()) as Record<string, unknown>
13
- return {
14
- ...actual,
15
- render: mocks.render,
16
- }
17
- })
18
-
19
- vi.mock('~/utils', () => ({
20
- IS_DEVELOPMENT: true,
21
- }))
22
-
23
- import Content from '../helpers/Content/component'
24
- import Styled from '../helpers/Content/styled'
25
-
26
- const asVNode = (v: unknown) => v as VNode
27
-
28
- describe('Content component', () => {
29
- beforeEach(() => {
30
- vi.clearAllMocks()
31
- })
32
-
33
- it('returns a VNode whose type is the Styled component', () => {
34
- const result = asVNode(Content({ contentType: 'content' }))
35
- expect(result.type).toBe(Styled)
36
- })
37
-
38
- it("passes tag as the 'as' prop to Styled", () => {
39
- const result = asVNode(Content({ tag: 'span' }))
40
- expect(result.props.as).toBe('span')
41
- })
42
-
43
- it('passes contentType as $contentType prop', () => {
44
- const result = asVNode(Content({ contentType: 'before' }))
45
- expect(result.props.$contentType).toBe('before')
46
- })
47
-
48
- it('passes styling props bundled as $element prop', () => {
49
- const result = asVNode(
50
- Content({
51
- contentType: 'content',
52
- parentDirection: 'inline',
53
- direction: 'rows',
54
- alignX: 'center',
55
- alignY: 'top',
56
- equalCols: true,
57
- gap: 8,
58
- extendCss: 'color: red;',
59
- }),
60
- )
61
-
62
- expect(result.props.$element).toEqual({
63
- contentType: 'content',
64
- parentDirection: 'inline',
65
- direction: 'rows',
66
- alignX: 'center',
67
- alignY: 'top',
68
- equalCols: true,
69
- gap: 8,
70
- extraStyles: 'color: red;',
71
- })
72
- })
73
-
74
- it('adds data-pyr-element attribute in development mode', () => {
75
- const result = asVNode(Content({ contentType: 'after' }))
76
- expect(result.props['data-pyr-element']).toBe('after')
77
- })
78
-
79
- it('passes children through render() when the slot accessor is invoked', () => {
80
- // Content wraps its children in a reactive accessor `() => resolveSlot(...)`
81
- // — render() is no longer called synchronously at component setup. The
82
- // accessor is invoked by the runtime when the JSX child position mounts;
83
- // here we invoke it directly to assert the wiring. The
84
- // function-unwrap-then-render shape is what keeps `content={() => <X/>}`
85
- // slot reactivity working (see Element-slot-reactivity.browser.test.tsx).
86
- const children = 'Some text'
87
- const result = asVNode(Content({ children }))
88
- expect(typeof result.props.children).toBe('function')
89
- ;(result.props.children as () => unknown)()
90
- expect(mocks.render).toHaveBeenCalledWith(children)
91
- })
92
-
93
- it('spreads remaining props to Styled', () => {
94
- const result = asVNode(Content({ id: 'test-id', className: 'custom' } as any))
95
- expect(result.props.id).toBe('test-id')
96
- expect(result.props.className).toBe('custom')
97
- })
98
-
99
- it('maps extendCss to extraStyles in $element', () => {
100
- const result = asVNode(Content({ extendCss: 'font-size: 14px;' }))
101
- expect((result.props.$element as any).extraStyles).toBe('font-size: 14px;')
102
- })
103
- })
104
-
105
- describe('Content component (production mode)', () => {
106
- it('does not add data-pyr-element when IS_DEVELOPMENT is false', async () => {
107
- // Reset module registry so the new mock takes effect
108
- vi.resetModules()
109
- vi.doMock('../utils', () => ({
110
- IS_DEVELOPMENT: false,
111
- }))
112
- // Re-mock @pyreon/ui-core after resetModules
113
- vi.doMock('@pyreon/ui-core', async (importOriginal) => {
114
- const actual = (await importOriginal()) as Record<string, unknown>
115
- return { ...actual, render: (children: unknown) => children }
116
- })
117
-
118
- const { default: ContentProd } = await import('../helpers/Content/component')
119
- const result = asVNode(ContentProd({ contentType: 'before' }))
120
-
121
- expect(result.props['data-pyr-element']).toBeUndefined()
122
- })
123
- })
@@ -1,152 +0,0 @@
1
- /** @jsxImportSource @pyreon/core */
2
- /**
3
- * Regression specs for the Element slot reactivity bug.
4
- *
5
- * Pre-fix: `<Element content={() => <Icon name={signal()} />}>` evaluated the
6
- * function once at mount and baked in the result. Signal changes inside the
7
- * function body did NOT cause the slot to re-render — even though the
8
- * `getChildren` helper in Element/component.tsx had a getter shape intended
9
- * to preserve reactivity.
10
- *
11
- * Root cause: the JSX child position read the resolved slot value at
12
- * component-setup time. The runtime's `mountChild` reactive-function-child
13
- * handling (`mountReactive`) was never reached because the function was
14
- * passed to `render()` which treated it as a component (one-shot mount),
15
- * not as a reactive accessor.
16
- *
17
- * Fix: wrap the JSX child position in `{() => ...}` so it becomes a
18
- * reactive accessor that mountChild routes through mountReactive.
19
- * Slot values that are themselves functions get unwrapped (called) inside
20
- * the accessor so their body's signal reads are tracked by the effect.
21
- *
22
- * Bisect-verify-with-restore: revert the wrap → these tests fail with
23
- * stuck slot content; restore → tests pass.
24
- */
25
- import { describe, expect, it } from 'vitest'
26
- import { signal } from '@pyreon/reactivity'
27
- import { flush, mountInBrowser } from '@pyreon/test-utils/browser'
28
- import { Element } from '../Element'
29
-
30
- describe('Element slot reactivity — function-valued slot props', () => {
31
- it('content={() => <X />} re-renders when a signal inside the function body changes', async () => {
32
- const dark = signal(false)
33
- const { container, unmount } = mountInBrowser(
34
- <Element
35
- tag="div"
36
- data-id="root"
37
- content={() => <span data-id="icon">{dark() ? 'moon' : 'sun'}</span>}
38
- />,
39
- )
40
-
41
- expect(container.querySelector('[data-id="icon"]')?.textContent).toBe('sun')
42
- dark.set(true)
43
- await flush()
44
- expect(container.querySelector('[data-id="icon"]')?.textContent).toBe('moon')
45
- dark.set(false)
46
- await flush()
47
- expect(container.querySelector('[data-id="icon"]')?.textContent).toBe('sun')
48
- unmount()
49
- })
50
-
51
- it('beforeContent={() => <X />} re-renders when a signal inside changes', async () => {
52
- const count = signal(0)
53
- const { container, unmount } = mountInBrowser(
54
- <Element
55
- tag="div"
56
- data-id="root"
57
- beforeContent={() => <span data-id="badge">{`#${count()}`}</span>}
58
- content={<span data-id="main">main</span>}
59
- />,
60
- )
61
-
62
- expect(container.querySelector('[data-id="badge"]')?.textContent).toBe('#0')
63
- count.set(5)
64
- await flush()
65
- expect(container.querySelector('[data-id="badge"]')?.textContent).toBe('#5')
66
- unmount()
67
- })
68
-
69
- it('afterContent={() => <X />} re-renders when a signal inside changes', async () => {
70
- const tag = signal('draft')
71
- const { container, unmount } = mountInBrowser(
72
- <Element
73
- tag="div"
74
- data-id="root"
75
- content={<span data-id="main">main</span>}
76
- afterContent={() => <span data-id="status">{tag()}</span>}
77
- />,
78
- )
79
-
80
- expect(container.querySelector('[data-id="status"]')?.textContent).toBe('draft')
81
- tag.set('published')
82
- await flush()
83
- expect(container.querySelector('[data-id="status"]')?.textContent).toBe('published')
84
- unmount()
85
- })
86
-
87
- it('static VNode content (non-function) still works unchanged', () => {
88
- // Regression guard for the static path — no function unwrap should
89
- // happen here.
90
- const { container, unmount } = mountInBrowser(
91
- <Element tag="div" content={<span data-id="static">hello</span>} />,
92
- )
93
- expect(container.querySelector('[data-id="static"]')?.textContent).toBe('hello')
94
- unmount()
95
- })
96
-
97
- it('static beforeContent + afterContent still render (compound path)', () => {
98
- const { container, unmount } = mountInBrowser(
99
- <Element
100
- tag="div"
101
- beforeContent={<span data-id="b">before</span>}
102
- content={<span data-id="c">main</span>}
103
- afterContent={<span data-id="a">after</span>}
104
- />,
105
- )
106
- expect(container.querySelector('[data-id="b"]')?.textContent).toBe('before')
107
- expect(container.querySelector('[data-id="c"]')?.textContent).toBe('main')
108
- expect(container.querySelector('[data-id="a"]')?.textContent).toBe('after')
109
- unmount()
110
- })
111
-
112
- it('null slot stays unrendered; flipping a signal can introduce it', async () => {
113
- const show = signal(false)
114
- const { container, unmount } = mountInBrowser(
115
- <Element
116
- tag="div"
117
- data-id="root"
118
- content={() => (show() ? <span data-id="present">shown</span> : null)}
119
- />,
120
- )
121
- expect(container.querySelector('[data-id="present"]')).toBeNull()
122
- show.set(true)
123
- await flush()
124
- expect(container.querySelector('[data-id="present"]')?.textContent).toBe('shown')
125
- show.set(false)
126
- await flush()
127
- expect(container.querySelector('[data-id="present"]')).toBeNull()
128
- unmount()
129
- })
130
-
131
- it('children prop (priority over content) — function form is reactive in compound layout', async () => {
132
- // children takes priority over content per getChildren's `??` chain.
133
- // Compound layout (when beforeContent OR afterContent exists) routes
134
- // through a different render path than the simple-element fast path —
135
- // both must handle function children reactively.
136
- const text = signal('first')
137
- const { container, unmount } = mountInBrowser(
138
- <Element
139
- tag="div"
140
- beforeContent={<span data-id="b">b</span>}
141
- afterContent={<span data-id="a">a</span>}
142
- >
143
- {() => <span data-id="kid">{text()}</span>}
144
- </Element>,
145
- )
146
- expect(container.querySelector('[data-id="kid"]')?.textContent).toBe('first')
147
- text.set('second')
148
- await flush()
149
- expect(container.querySelector('[data-id="kid"]')?.textContent).toBe('second')
150
- unmount()
151
- })
152
- })