@exponential_app/eui-react 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/NOTICE +54 -0
- package/README.md +360 -2
- package/dist/chunks/index-5yzzfez5.js +126 -0
- package/dist/chunks/index-5yzzfez5.js.map +11 -0
- package/dist/exponential-ui-react.css +2 -0
- package/dist/index.js +7700 -0
- package/dist/index.js.map +46 -0
- package/dist/primitives/index.js +3154 -0
- package/dist/primitives/index.js.map +59 -0
- package/dist/types/context.d.ts +96 -0
- package/dist/types/extensions.d.ts +25 -0
- package/dist/types/form.d.ts +65 -0
- package/dist/types/host-surface.d.ts +32 -0
- package/dist/types/host.d.ts +37 -0
- package/dist/types/icons.d.ts +31 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/inputs.d.ts +25 -0
- package/dist/types/list.d.ts +31 -0
- package/dist/types/live.d.ts +18 -0
- package/dist/types/markdown.d.ts +9 -0
- package/dist/types/media.d.ts +15 -0
- package/dist/types/natives/actions.d.ts +10 -0
- package/dist/types/natives/bound.d.ts +2 -0
- package/dist/types/natives/chart.d.ts +2 -0
- package/dist/types/natives/chips.d.ts +5 -0
- package/dist/types/natives/choice-tiles.d.ts +2 -0
- package/dist/types/natives/code.d.ts +2 -0
- package/dist/types/natives/dates.d.ts +29 -0
- package/dist/types/natives/feedback.d.ts +29 -0
- package/dist/types/natives/index.d.ts +4 -0
- package/dist/types/natives/inputs.d.ts +19 -0
- package/dist/types/natives/layout.d.ts +15 -0
- package/dist/types/natives/listbox.d.ts +15 -0
- package/dist/types/natives/media.d.ts +13 -0
- package/dist/types/natives/nav.d.ts +17 -0
- package/dist/types/natives/overlays.d.ts +17 -0
- package/dist/types/natives/qr.d.ts +3 -0
- package/dist/types/natives/resizable.d.ts +2 -0
- package/dist/types/natives/search.d.ts +46 -0
- package/dist/types/natives/select.d.ts +8 -0
- package/dist/types/natives/shared.d.ts +35 -0
- package/dist/types/natives/table.d.ts +5 -0
- package/dist/types/natives/text.d.ts +4 -0
- package/dist/types/node-view.d.ts +73 -0
- package/dist/types/platform.d.ts +12 -0
- package/dist/types/primitives/alert-dialog.d.ts +14 -0
- package/dist/types/primitives/alert.d.ts +7 -0
- package/dist/types/primitives/avatar-color.d.ts +1 -0
- package/dist/types/primitives/avatar.d.ts +8 -0
- package/dist/types/primitives/badge.d.ts +8 -0
- package/dist/types/primitives/breadcrumb.d.ts +11 -0
- package/dist/types/primitives/button.d.ts +7 -0
- package/dist/types/primitives/calendar.d.ts +8 -0
- package/dist/types/primitives/card.d.ts +9 -0
- package/dist/types/primitives/checkbox.d.ts +4 -0
- package/dist/types/primitives/choice-tiles.d.ts +60 -0
- package/dist/types/primitives/cn.d.ts +1 -0
- package/dist/types/primitives/code-block.d.ts +32 -0
- package/dist/types/primitives/collapsible.d.ts +6 -0
- package/dist/types/primitives/command.d.ts +49 -0
- package/dist/types/primitives/context-ring.d.ts +15 -0
- package/dist/types/primitives/dialog-arms.d.ts +2 -0
- package/dist/types/primitives/dialog.d.ts +32 -0
- package/dist/types/primitives/disclosure-header.d.ts +7 -0
- package/dist/types/primitives/dock-card.d.ts +43 -0
- package/dist/types/primitives/dropdown-menu.d.ts +26 -0
- package/dist/types/primitives/empty-state.d.ts +19 -0
- package/dist/types/primitives/glass-card.d.ts +3 -0
- package/dist/types/primitives/hover-card.d.ts +6 -0
- package/dist/types/primitives/icon-disc.d.ts +10 -0
- package/dist/types/primitives/index.d.ts +52 -0
- package/dist/types/primitives/input.d.ts +4 -0
- package/dist/types/primitives/kbd.d.ts +4 -0
- package/dist/types/primitives/label.d.ts +4 -0
- package/dist/types/primitives/menu-surface.d.ts +1 -0
- package/dist/types/primitives/meter.d.ts +10 -0
- package/dist/types/primitives/popover.d.ts +7 -0
- package/dist/types/primitives/progress-ring.d.ts +12 -0
- package/dist/types/primitives/progress.d.ts +4 -0
- package/dist/types/primitives/scroll-area.d.ts +5 -0
- package/dist/types/primitives/segmented-control.d.ts +25 -0
- package/dist/types/primitives/select.d.ts +15 -0
- package/dist/types/primitives/separator.d.ts +4 -0
- package/dist/types/primitives/sheet-chrome.d.ts +13 -0
- package/dist/types/primitives/sheet.d.ts +18 -0
- package/dist/types/primitives/skeleton.d.ts +3 -0
- package/dist/types/primitives/spinner.d.ts +8 -0
- package/dist/types/primitives/switch.d.ts +6 -0
- package/dist/types/primitives/table.d.ts +11 -0
- package/dist/types/primitives/tabs.d.ts +8 -0
- package/dist/types/primitives/textarea.d.ts +3 -0
- package/dist/types/primitives/toast.d.ts +16 -0
- package/dist/types/primitives/tooltip.d.ts +7 -0
- package/dist/types/primitives/truncate.d.ts +1 -0
- package/dist/types/primitives/use-media-query.d.ts +3 -0
- package/dist/types/primitives/use-mobile.d.ts +1 -0
- package/dist/types/primitives/use-sheet-drag.d.ts +20 -0
- package/dist/types/primitives/voice-wave.d.ts +22 -0
- package/dist/types/surface.d.ts +117 -0
- package/dist/types/use-surface.d.ts +57 -0
- package/package.json +60 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Dennis Strähhuber
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Exponential
|
|
2
|
+
Copyright 2026 Dennis Strähhuber
|
|
3
|
+
|
|
4
|
+
This product includes software developed as part of the Exponential project,
|
|
5
|
+
licensed under the Apache License, Version 2.0 (see LICENSE at the repo root).
|
|
6
|
+
|
|
7
|
+
Third-party code vendored into this repository keeps its own NOTICE, next to
|
|
8
|
+
the vendored source:
|
|
9
|
+
|
|
10
|
+
apps/desktop/crates/ui/NOTICE
|
|
11
|
+
apps/desktop/crates/gpui-markdown-editor/NOTICE
|
|
12
|
+
|
|
13
|
+
This file records the attributions that travel with a distributed artifact.
|
|
14
|
+
It is not the per-client dependency inventory; that is generated separately.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
================================================================================
|
|
18
|
+
Feedback widget
|
|
19
|
+
================================================================================
|
|
20
|
+
|
|
21
|
+
The feedback widget (packages/widget, emitted to
|
|
22
|
+
apps/web/public/widget/v1/loader.js and widget.js) is served cross-origin to
|
|
23
|
+
third-party websites, so its bundle redistributes the software below. Both
|
|
24
|
+
emitted files carry a short banner naming these packages and pointing here.
|
|
25
|
+
|
|
26
|
+
preact
|
|
27
|
+
Copyright (c) 2015-present Jason Miller
|
|
28
|
+
https://github.com/preactjs/preact
|
|
29
|
+
MIT License
|
|
30
|
+
|
|
31
|
+
@zumer/snapdom
|
|
32
|
+
Copyright (c) 2025 ZumerLab
|
|
33
|
+
https://github.com/zumerlab/snapdom
|
|
34
|
+
MIT License
|
|
35
|
+
|
|
36
|
+
Both are made available under the following terms:
|
|
37
|
+
|
|
38
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
39
|
+
copy of this software and associated documentation files (the "Software"),
|
|
40
|
+
to deal in the Software without restriction, including without limitation
|
|
41
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
42
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
|
43
|
+
Software is furnished to do so, subject to the following conditions:
|
|
44
|
+
|
|
45
|
+
The above copyright notice and this permission notice shall be included in
|
|
46
|
+
all copies or substantial portions of the Software.
|
|
47
|
+
|
|
48
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
49
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
50
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
51
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
52
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
53
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
54
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,361 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@exponential_app/eui-react` — the Exponential UI React renderer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The REFERENCE renderer of Exponential UI (VAPP-84): an A2UI surface in the
|
|
4
|
+
core catalog (`@exponential_app/eui`) painted with real CSS on shadcn/Radix.
|
|
5
|
+
Themes load at runtime, extensions register their own components, overlays
|
|
6
|
+
ride Radix portals, inputs are host-owned, and the layout is the one the
|
|
7
|
+
Rust core computes (the geometry fixture holds the browser to taffy's
|
|
8
|
+
frames within a pixel). Published to npm from `release/` (VAPP-91); the
|
|
9
|
+
workspace package.json stays private on the sources.
|
|
10
|
+
Its SolidJS twin, `@exponential_app/eui-solid` (`packages/exponential-ui-solid`),
|
|
11
|
+
paints the identical DOM, renderer and primitives, and is gated against this
|
|
12
|
+
renderer by the web-parity check (`@exponential_app/eui/conformance/parity.ts`).
|
|
13
|
+
|
|
14
|
+
It depends on `@exponential_app/eui`, React 19 (React 18 would keep
|
|
15
|
+
`javascript:` hrefs clickable), `radix-ui` and
|
|
16
|
+
`lucide-react` (its own chrome glyphs) — nothing from the Exponential app, no
|
|
17
|
+
TanStack, no Electric, no tRPC. Tailwind is an internal build detail of the
|
|
18
|
+
primitive set (below); the renderer itself ships no stylesheet an embedder
|
|
19
|
+
has to wire up.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { ExponentialSurface, useSurface } from "@exponential_app/eui-react"
|
|
23
|
+
|
|
24
|
+
function Chat({ messages }) {
|
|
25
|
+
const surface = useSurface({ surfaceId: "main" })
|
|
26
|
+
useEffect(() => messages.forEach(surface.apply), [messages]) // A2UI v0.9 server messages
|
|
27
|
+
return (
|
|
28
|
+
<ExponentialSurface
|
|
29
|
+
surface={surface}
|
|
30
|
+
theme="exponential" // or "neutral" | "playful" | "orange" | a theme JSON | a ResolvedTheme
|
|
31
|
+
mode="system" // light | dark | system (default; follows the platform live)
|
|
32
|
+
locale="de-AT" // formatting, calendar names, week start, text direction
|
|
33
|
+
timeZone="Europe/Vienna" // dates and relative times (default: the platform's)
|
|
34
|
+
strings={{ search: "Suchen…" }} // built-in copy overrides (catalog/strings.json ids)
|
|
35
|
+
density="compact" contrast="system" // the theme's density multiplier; high-contrast overlays
|
|
36
|
+
host={{ icons, onAction, onInput, onUpload }} // the host plugin (below)
|
|
37
|
+
handleRef={ref} // ref.current.run({ focus: { id } } | { announce } | { scrollIntoView } | { scrollToIndex })
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Layout
|
|
44
|
+
|
|
45
|
+
The stylesheet and the bind pass come from the core's web layer, `@exponential_app/eui/web` (FEED-128: `compileTheme`, `BASE_CSS`, the node sheet, `bindTree`, the URL/media policy, the natives' pure helpers), so every web renderer paints with byte-identical CSS; this package re-exports those names (`compileTheme`, `BASE_CSS`, `bindTree`, `linkHref`…) and keeps only the React half.
|
|
46
|
+
|
|
47
|
+
| path | what |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `src/surface.tsx` | `<ExponentialSurface>`: root (theme scope + variables, `data-xui-mode`, `dir`, `data-xq`), the two `<style>`s, the `xui` container with the tree + the overlay and toast LAYERS, the live regions, the settings (locale, strings, mode/density/contrast) and the host command handle |
|
|
50
|
+
| `src/use-surface.ts` | `useSurface`: the TS reducer as React state; A2UI messages in, normalized tree + data model out |
|
|
51
|
+
| `src/node-view.tsx` | the BIND pass per node (`visible`, props, dynamic style values, recipe props, `$string`, responsive native props), painter picked, root attributes, the action runner (`set` then the event), keyed templates |
|
|
52
|
+
| `src/natives/` | the 46 native painters (Box, Text, Button… Form, NumberField, ChipInput, Date/Range/TimePicker, FileUpload, CodeBlock, Table, Chart, Menu, Segmented, Toast, Resizable, TreeGuides, Unknown); `natives.test.ts` gates them against the catalog |
|
|
53
|
+
| `src/form.tsx` | the Form native + the field protocol every named control speaks (`useField`) |
|
|
54
|
+
| `src/platform.ts` | live platform preferences (`matchMedia`) and the measured surface box |
|
|
55
|
+
| `src/inputs.ts` | `useHostOwnedValue`: the hook around the web layer's `HostOwnedController` (150 ms debounce, revisions, echo rule) |
|
|
56
|
+
| `src/list.tsx` | `WindowedList`: one axis, item key, estimated extent, gap + dividers, overscan, sticky rows, one ResizeObserver, `scrollToIndex` |
|
|
57
|
+
| `src/extensions.ts` | `registerExtension` / `defineReactExtension` |
|
|
58
|
+
| `src/primitives/` | the shadcn set (moved from `@exp/ui`, which re-exports it) as thin React wrappers over the class recipes of `@exponential_app/eui/web/primitives` (FEED-131: no class literal here, the Solid twin reads the same recipes) + `tailwind.css` (scans the recipes too); `bun run build:css` → `dist/exponential-ui-react.css` |
|
|
59
|
+
| `harness/` | the browser harness the Chromium suites and the screenshot script drive (`dev:harness` → :4181; views kitchen-sink, geometry, overlay, catalog, conditions, tree, round2, bench) |
|
|
60
|
+
| `browser/` | the headless-Chromium suites: geometry, overlays, typing, round 1 (conditions, overlays × breakpoints, kitchen sink × themes × widths, keyboard) (`test:browser`) |
|
|
61
|
+
| `fixtures/kitchen-sink.data.json` | the data model the kitchen sink renders against (harness + the app's view) |
|
|
62
|
+
| `scripts/` | `build-css.ts`, `shots.ts` (the stored kitchen-sink shots), `pack-smoke.ts` (a plain Vite app on the packed tarballs) |
|
|
63
|
+
|
|
64
|
+
## How a theme becomes CSS
|
|
65
|
+
|
|
66
|
+
`compileTheme(theme)` emits, scoped by a class derived from the theme's
|
|
67
|
+
content (`xui-t-<id>-<hash>`, on the root AND on every painted element, so two
|
|
68
|
+
surfaces on one page wear two themes and a host page's own variables are
|
|
69
|
+
never touched):
|
|
70
|
+
|
|
71
|
+
1. **Variables** on `.xui-surface.<scope>`: every token as `--xui-<group>-<name>`
|
|
72
|
+
(`--xui-spacing-md: 12px`, `--xui-type-lineHeight-sm`, `--xui-font-sans: "Inter", …`),
|
|
73
|
+
the colours and shadows per `[data-xui-mode="light|dark"]`, plus the shadcn
|
|
74
|
+
names (`--primary`, `--radius`, `--font-sans`…) and the app's `--glass-*`
|
|
75
|
+
set, so a Tailwind-built primitive inside a surface wears the theme too.
|
|
76
|
+
2. **Recipe classes**: `recipes.<Component>.<part>` → `.xui-<Component>-<part>`.
|
|
77
|
+
A rule's `when` on recipe props becomes `[data-r-<prop>="…"]` (natives) or
|
|
78
|
+
`[data-m-<prop>="…"]` (macro parts: a Badge's label is a Text, both key on
|
|
79
|
+
`variant`); `state` becomes the pseudo-class or Radix attribute AND the
|
|
80
|
+
forced marker `[data-xs~="<state>"]` the builder's recipe sheet sets.
|
|
81
|
+
Values stay `var(--xui-…)`, so a mode switch is one attribute flip.
|
|
82
|
+
3. **Layers**: `@layer xui-base, xui-recipe, xui-node, xui-part, xui-author` IS
|
|
83
|
+
the painter precedence of `resolveNodeStyle`: a native's own recipe < the
|
|
84
|
+
node's `style` < the macro part's recipe < the style keys authored on the
|
|
85
|
+
macro node (`recipe.authored`).
|
|
86
|
+
|
|
87
|
+
The node sheet (`compileNodeSheet`) is per surface: `.xui-s-<id> .xui-n-<nodeId>`
|
|
88
|
+
rules from each node's whitelisted style (tokens as variables), then each
|
|
89
|
+
`@media` block in SOURCE order (round-1 contract §2; later wins):
|
|
90
|
+
|
|
91
|
+
| condition | CSS |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `min-width: N` / `max-width: N` (`N` px or `$breakpoint.*`, resolved from the theme) | `@container xui (width >= N)` / `(width < N)` — strict, so `min md` and `max md` never overlap |
|
|
94
|
+
| `min-height` / `max-height` / `orientation` | `.xui-s-<id>:where([data-xq~="<token>"]) …` — the surface lists the matching conditions in `data-xq` (an inline-size container cannot answer height). The height is the VIEWPORT's, like the Rust core's `set_viewport` height: the `viewportHeight` prop, else an explicit inline `style.height` on the surface, else UNKNOWN (landscape, no min/max-height match — what the gpui host passes today); never the content height (no feedback loop) |
|
|
95
|
+
| `hover: hover\|none`, `prefers-reduced-motion` | real `@media` |
|
|
96
|
+
| `:hover`, `:focus-visible`, `:pressed` | after every media rule, in that order: `:hover`/`[data-xs~=hover]`, `:focus-visible`, `:active`/`[data-xs~=pressed]` |
|
|
97
|
+
|
|
98
|
+
A value the macro expander left DYNAMIC (Progress `width: percent{…}`) is
|
|
99
|
+
emitted as `var(--xd-<n>)` and the node sets the resolved value inline, so the
|
|
100
|
+
layer order holds; a value that resolves to nothing is set to `initial`, so the
|
|
101
|
+
property falls back instead of inheriting an ancestor's `--xd-<n>`. Template subtrees are part of the sheet and their items
|
|
102
|
+
keep the template node's class (ids get the item's suffix), so a templated row
|
|
103
|
+
wears its author and macro styles. New style keys map like `styleToCss`:
|
|
104
|
+
`transition` → `transition-property: all` + `transition-duration` (the easing
|
|
105
|
+
is `transition-timing-function`, `$ease.*` → `var(--xui-ease-*)`), gradients
|
|
106
|
+
→ `background-image`, per-side widths default to `solid`, `overflowX/Y:
|
|
107
|
+
hidden` → `clip`. Reduced motion (the media query or `data-xui-motion`)
|
|
108
|
+
zeroes every duration.
|
|
109
|
+
|
|
110
|
+
### The three rules that make CSS agree with taffy (VAPP-4)
|
|
111
|
+
|
|
112
|
+
Every painted element restates `box-sizing: border-box; display: flex;
|
|
113
|
+
flex-direction: row; position: relative` and leaves `min-width` AND
|
|
114
|
+
`min-height` at `auto` (taffy's automatic minimum size; round 1 dropped the
|
|
115
|
+
old `min-height: 0`, the geometry replay stays within 1 px); chrome
|
|
116
|
+
is paint or inside the measured size (borders start at 0, a recipe's
|
|
117
|
+
`borderWidth` is layout in both engines); `overflow: hidden` is emitted as
|
|
118
|
+
`clip`. `fixtures/layout-geometry.json` (the spike's kitchen sink, taffy's
|
|
119
|
+
frames at 900/390 px × LTR/RTL, a fixed fake measure) is replayed in headless
|
|
120
|
+
Chromium by `browser/geometry.test.ts`: every node within 1 px. In geometry
|
|
121
|
+
mode (`measure` prop) a leaf renders as a fixed box with no recipe on it.
|
|
122
|
+
|
|
123
|
+
## The host plugin
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
interface HostPlugin {
|
|
127
|
+
icons?: IconMap // registry name → component (the app passes its Lucide map)
|
|
128
|
+
onAction?(e: SurfaceActionEvent): void | Promise<void> // on.<event> = {event: {name, context}}; a promise keeps the Button pending
|
|
129
|
+
onInput?(e: SurfaceInputEvent): void | Promise<void> // host-owned edits: {name, path?, value, revision, kind: change|commit}
|
|
130
|
+
openUrl?(url: string): void // opens an href the URL policy allowed
|
|
131
|
+
urls?: UrlPolicy // EVERY href (Link, markdown, FileUpload, openUrl); denied = text
|
|
132
|
+
media?: MediaOptions // EVERY src without a mediaRequest (schemes, hosts, rules)
|
|
133
|
+
mediaRequest?(src: string): MediaRequest | null // null = denied; re-checked against media's schemes/hosts
|
|
134
|
+
onPaintError?(e: { surfaceId, componentId, message }): void // a painter threw (it paints an empty box)
|
|
135
|
+
functions?: Record<string, ClientFunction> // adds to / overrides the catalog functions
|
|
136
|
+
Markdown?: ComponentType<{ text: string }> // a richer renderer than the built-in GFM subset
|
|
137
|
+
resolveUrl?(src: string): string // rewrites a media src BEFORE the media policy
|
|
138
|
+
onUnknown?(node: UiNode): void
|
|
139
|
+
onUpload?(files: File[], target: { nodeId, name }): void | Promise<void> // FileUpload bytes (the event carries metadata only)
|
|
140
|
+
optionSource?(source: string, query: string): Option[] | Promise<Option[]> // Select `source` (a host list)
|
|
141
|
+
qrScanner?: { start(feed: HTMLElement, formats: readonly string[], onValue: (value: string) => void): (() => void) | false | void } // QrScanner's camera
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **Actions** are optimistic: a Button is disabled (`aria-busy`,
|
|
146
|
+
`data-xs~="pending"`) from the press until `onAction`'s promise settles.
|
|
147
|
+
An action resolves its function args AND event context against the data
|
|
148
|
+
as it is, then runs the function (`set` writes the data model, two-way
|
|
149
|
+
binding for macros; `openUrl` → `host.openUrl`), then sends the event with
|
|
150
|
+
the component's payload merged OVER the author's context (the payload wins
|
|
151
|
+
a clashing key, like the Rust core's `fire`).
|
|
152
|
+
- **Re-renders**: the surface keeps its measured box in a ref and re-renders
|
|
153
|
+
only when the active breakpoint or `data-xq` changes; nodes are memoized,
|
|
154
|
+
so a parent re-render (or a resize inside a breakpoint) repaints nothing.
|
|
155
|
+
- **Template instance ids** accumulate: an item's nodes are
|
|
156
|
+
`<id><outer suffixes>.<key>` (`cell.a.0`), the key = the `template.key`
|
|
157
|
+
value, the index without a key, `#<index>` when the key is missing, null,
|
|
158
|
+
empty or a duplicate. A Table slot cell appends the row key the same way.
|
|
159
|
+
- **Host-owned inputs** (`Input`, `Textarea`, `Composer`): the value lives in
|
|
160
|
+
local state; every edit bumps a revision and, 150 ms later, sends ONE
|
|
161
|
+
`change` with the latest value; `commit` goes out on blur/Enter. A value
|
|
162
|
+
the host pushes (the data model, a new prop) is adopted only when the
|
|
163
|
+
field is not focused and every sent revision is acknowledged (the promise
|
|
164
|
+
settled). A bound `value` (`{path}`) is also written through to the
|
|
165
|
+
surface's data model. `browser/typing.test.ts`: 40 keys at 150 ms RTT,
|
|
166
|
+
none dropped, three runs.
|
|
167
|
+
- **Bindings**: `{path}` is a JSON pointer into `surface.data` (relative
|
|
168
|
+
inside a template item); `{call, args}` runs a client function (the bind
|
|
169
|
+
table `bindFunctionNames`; truthiness = the catalog's, `0` is true; the
|
|
170
|
+
format functions run through the surface Formatter);
|
|
171
|
+
`$string.<id>` resolves through the surface's string table. A falsy
|
|
172
|
+
`visible` drops the node. `bindTree` is the whole-tree form, replayed
|
|
173
|
+
against `fixtures/bind-time.json`.
|
|
174
|
+
- **Bindable state**: a native whose state prop is bound (Dialog `open`,
|
|
175
|
+
Tabs `value`, Checkbox `checked`, Table `sort`/`selected`, ChipInput
|
|
176
|
+
`values`, …) writes the user's change to the path itself AND fires
|
|
177
|
+
`change`.
|
|
178
|
+
- **Forms**: every named control registers with the nearest `Form`; a
|
|
179
|
+
submit (a `submit` Button, Enter in a single-line field) runs every
|
|
180
|
+
field's checks; a failure fires `invalid {errors}`, shows ALL failing
|
|
181
|
+
messages under each field (`<id>.error`, `role=alert`, linked by
|
|
182
|
+
`aria-describedby`, the `invalid` recipe state), the `summary`, moves
|
|
183
|
+
focus to the first invalid field and announces `invalidFields`.
|
|
184
|
+
- **Templates**: `children: {componentId, path, key?}` renders the component
|
|
185
|
+
once per item at `path`, keyed by `key` (reordering keeps the item's
|
|
186
|
+
state). The component comes from the reducer's LIFTED `templates`
|
|
187
|
+
(`useSurface` keeps them; a fixture passes `templates` beside `root`); it
|
|
188
|
+
never renders in place.
|
|
189
|
+
|
|
190
|
+
### The host API (VAPP-91)
|
|
191
|
+
|
|
192
|
+
An `ExponentialHost` from `@exponential_app/eui` (transport, router, sources,
|
|
193
|
+
functions, policy; see that package's README) feeds `<HostSurface host
|
|
194
|
+
surfaceId plugin? …ExponentialSurface props>`; `hostPlugin(host, base)`
|
|
195
|
+
routes actions to A2UI client messages, `functionCall`s to
|
|
196
|
+
`host.callFunction` (the gate + consent), every href to the URL policy,
|
|
197
|
+
every src to `host.mediaRequest` (fetched under the contract's
|
|
198
|
+
`media.limits`: bytes, timeout, header pixels) and `onPaintError` to
|
|
199
|
+
`host.paintError` (an A2UI `RENDER_FAILED` error). `useHostSurfaceIds(host)` and
|
|
200
|
+
`useHostStatus(host)` (`status`, `unsupportedCatalog`) drive a host's own
|
|
201
|
+
chrome (`host_offline`, the catalog-update banner). Two plugin members
|
|
202
|
+
joined: `onFunctionCall(call)` and `mediaRequest(src)` (a request with
|
|
203
|
+
headers is fetched once and shown as a blob url). Template items (a List's
|
|
204
|
+
`{componentId, path}` rows) wear their component's node styles.
|
|
205
|
+
|
|
206
|
+
`browser/suites.test.ts` is the renderer's conformance runner (every
|
|
207
|
+
suite of the manifest; `test:conformance`).
|
|
208
|
+
|
|
209
|
+
## Overlays
|
|
210
|
+
|
|
211
|
+
Dialog, Drawer, Popover, Tooltip, Menu, Select and the
|
|
212
|
+
pickers portal into the surface's OVERLAY LAYER inside the `xui` container
|
|
213
|
+
(not `document.body`): the theme's variables and scope reach them, container
|
|
214
|
+
breakpoints still match inside a Dialog (round 1), and an inline-size
|
|
215
|
+
container does not trap `position: fixed`. Toasts portal into the TOAST layer
|
|
216
|
+
above it (bottom-centre below `md`, bottom-end from `md`, newest at the edge,
|
|
217
|
+
three visible; the duration pauses on hover/focus). Enter/exit animate on the
|
|
218
|
+
theme's motion and easing tokens. Placement follows the core's
|
|
219
|
+
rule (`placeOverlay` in `@exponential_app/eui`: `OVERLAY_OFFSET` 4 px, centred,
|
|
220
|
+
flip when the preferred side lacks room, shift `OVERLAY_PADDING` 8 px inside
|
|
221
|
+
the viewport); `browser/overlay.test.ts` holds Radix's popper to
|
|
222
|
+
`fixtures/overlay-geometry.json` within 2 px, same side and flip. A trigger
|
|
223
|
+
slot is wrapped in an inline-flex span the popper measures. `open` is a
|
|
224
|
+
controlled prop with a local mirror (`change` reports the user's toggles).
|
|
225
|
+
|
|
226
|
+
## Round 1 natives (contract §3)
|
|
227
|
+
|
|
228
|
+
| native | behaviour |
|
|
229
|
+
|---|---|
|
|
230
|
+
| Form | field collection, submit gating, every failing message, summary, focus + announce, `busy`, `disabled` (a disabled fieldset) |
|
|
231
|
+
| NumberField | `role=spinbutton`, steppers, Arrow ± step (Shift ×10), PageUp/Down, Home/End, clamp on commit, locale formatting/parsing |
|
|
232
|
+
| ChipInput | Enter / comma add, Backspace focuses then removes the last chip, arrows between chips, suggestions listbox |
|
|
233
|
+
| DatePicker, DateRangePicker | one keyboard grid (arrows, PageUp/Down ± month, Shift ± year, Home/End by the locale week start), Intl month/weekday names, two-step range |
|
|
234
|
+
| TimePicker | a listbox of `step`-minute times in the locale's clock, type-ahead |
|
|
235
|
+
| Select | Radix when plain; searchable/multiple = the combobox (aria-activedescendant, `search {query}` debounced, bound options = async source, `host.optionSource`) |
|
|
236
|
+
| FileUpload | drop zone button, `dragover` state, accept/maxSize refusals, bytes → `host.onUpload`, removable files |
|
|
237
|
+
| CodeBlock | the catalog tokenizer (`CodeBlock/token` per kind), gutter, highlighted lines, wrap, `maxLines`, copy + `copied` announced |
|
|
238
|
+
| Table | typed cells (locale number/date, tick, badge, slot with the ROW as scope), sort (local or bound), single/multiple selection with select-all, striped odd rows, sticky header, windowing past 50; ONE roving row tab stop (focused, else first selected, else first), ArrowUp/Down + Home/End by index (through a windowed body), Enter presses, Space selects; slot edits over LITERAL rows stay table-local (never the data model, never a host path) |
|
|
239
|
+
| Chart | measured width, `niceTicks` axes + grid, bar/stackedBar/line/area/pie/donut (a ring per series)/sparkline, values, legend (2+), tooltip on hover and ArrowLeft/Right, an sr-only data table; cartesian kinds MIRROR in RTL (first category on the right) |
|
|
240
|
+
| Menu | item / checkbox (bound `checked` written) / separator / label / submenu (bound `items` render like literal ones), shortcuts; `openOn: press` = its child (or a default outline button) is the trigger, `contextmenu` = right-click / long-press / Shift+F10 / Menu key over the child |
|
|
241
|
+
| Toast | the toast layer, duration paused on hover/focus, `open` written false, `dismiss` + `change`, error = assertive |
|
|
242
|
+
| Image / Textarea / Drawer / Popover / Text / Markdown | fallback glyph + focal point + `loading`; autosize; `dismissible` + drag-to-dismiss + responsive `side`; `openOn: hover` (+ keyboard focus); `live`; `lines` |
|
|
243
|
+
|
|
244
|
+
Owned parts draw the catalog's `builtinIcons` glyph (host registry first,
|
|
245
|
+
then the same Lucide glyphs icons.json maps), and every piece of built-in
|
|
246
|
+
copy comes from the string table.
|
|
247
|
+
|
|
248
|
+
## Extensions
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { registerExtension } from "@exponential_app/eui-react"
|
|
252
|
+
registerExtension({ catalog: myExtensionJson, components: { TrendLine } }) // page-wide
|
|
253
|
+
<ExponentialSurface extensions={[defineReactExtension({ catalog, components })]} /> // per surface
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
A component receives `{ node, props (resolved), theme, mode, tokens, children,
|
|
257
|
+
slots, emit, rootProps }` and spreads `rootProps` on its root element (the node
|
|
258
|
+
class, the theme scope, the part classes, the recipe `data-*`). Macros of the
|
|
259
|
+
extension expand in the reducer and need no component; their parts go in the
|
|
260
|
+
`xui-part` layer. An entry for a CORE native (`{ Switch: MySwitch }`) is the
|
|
261
|
+
VAPP-92 painter override.
|
|
262
|
+
|
|
263
|
+
## Lists
|
|
264
|
+
|
|
265
|
+
`List` renders its children, then the template's items. Past the catalog's
|
|
266
|
+
`WINDOW_THRESHOLD` (50), or with `stickyHeaders`, it windows
|
|
267
|
+
(`WindowedList`): one axis (`direction` vertical or horizontal), rows end to
|
|
268
|
+
end with the gap (+ the hairline when `divided`, the divider centred in it),
|
|
269
|
+
unmeasured rows at `$control.row`, ONE `ResizeObserver`. The window follows
|
|
270
|
+
the nearest ancestor that actually scrolls on that axis, else the viewport.
|
|
271
|
+
The numbers are the core's (`virtualWindow`, `scrollOffsetForIndex`,
|
|
272
|
+
`stickyHeader`). Fewer items render flat, so fixtures snapshot every row.
|
|
273
|
+
|
|
274
|
+
| feature | web |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `sectionBy` | consecutive items share a header: the `section` slot bound to `{value, count, index}`, else the value; `role=heading` level 3, part `<id>.section.<i>` |
|
|
277
|
+
| `stickyHeaders` | the current header pins at the top, pushed back by the next one; drawn even outside the window |
|
|
278
|
+
| a11y | every item `role=listitem` with `aria-setsize` / `aria-posinset` of the WHOLE list |
|
|
279
|
+
| `scrollToIndex` | host command `{scrollToIndex: {id, index, align}}` on List and Table (data index); re-aimed once the revealed rows are measured |
|
|
280
|
+
|
|
281
|
+
Bench (`fixtures/bench-list.json`: 100,000 `Row`s, 390 × 800, neutral;
|
|
282
|
+
i9-13900K, Linux, Playwright 1.59 headless Chromium / jsdom 27; median of 3):
|
|
283
|
+
|
|
284
|
+
| renderer | firstPaintMs | scrollStepMs | scrollToIndexMs | renderedItems |
|
|
285
|
+
|---|---|---|---|---|
|
|
286
|
+
| Chromium (`bun run bench:list`) | 147 | 4.9 | 38 | 30 |
|
|
287
|
+
| jsdom (`XUI_BENCH=1 vitest run src/bench.test.tsx`) | 549 | 14.9 | 70 | 30 |
|
|
288
|
+
|
|
289
|
+
A step = the scroll, the window's render flushed and the layout read back
|
|
290
|
+
(main-thread work, no frame wait); the jump includes the first re-aim.
|
|
291
|
+
|
|
292
|
+
## Round 2 (contract `docs/round-2-contract.md`)
|
|
293
|
+
|
|
294
|
+
| item | web |
|
|
295
|
+
|---|---|
|
|
296
|
+
| Resizable | flex panels (sizes = grow factors over a 0 basis = `panelExtents`), hairline handles with an 8 px hit area, pointer drag from the START sizes, `keyboardResize`, bound `sizes` + `change {sizes}` at drag end / key, `separator` a11y named `$string.resize` |
|
|
297
|
+
| templates (breaking) | `<ExponentialSurface>` takes `templates` beside `root` (the reducer's lifted ones; `useSurface` and `HostSurface` pass them). The in-tree fallback and the `templateNodeFrom` export are gone: a `root`-only caller renders no template items, and dev builds warn once per missing template |
|
|
298
|
+
| `position: sticky` | CSS sticky |
|
|
299
|
+
| `backdropBlur` | `backdrop-filter: blur(var(--xui-blur-*))` (+ `-webkit-`) |
|
|
300
|
+
| `animation` | `@keyframes xui-<name>` in the base sheet, duration `calc($motion × factor)`, `animationDuration` keeps the factor, shimmer = an `::after` band on `--xui-band`; reduced motion = 0 ms (the rest frame) |
|
|
301
|
+
| `direction` | on any node: its `dir`, and its subtree's natives (Radix `dir`, arrows, chart mirror) read it; `rtlMirroredIcons` flip by `:dir(rtl)` |
|
|
302
|
+
| Formatter | `intlFormatter(locale, timeZone)` per surface (`timeZone` prop, default the platform's): the six format functions, Table `number`/`currency`/`percent`/`date`/`relativeTime` cells, NumberField, Slider value, chart ticks and summary, picker triggers, calendar names; a `formatRelativeTime` without `now` or a Table `relativeTime` column re-binds every minute |
|
|
303
|
+
| text props | a bound number shows `412`, a boolean `true`, an object nothing (`displayString`) |
|
|
304
|
+
| strings | `invalidValue`, `message`, `codeBlock`, `dialog`, `table`, `carousel`, `slide`, `resize` |
|
|
305
|
+
| parts | every laid-out part carries `data-xui-id="<id>.<part>[.<i or row key>]"` + `data-xui-part`; inactive carousel pages are `inert` + `data-xui-inactive` |
|
|
306
|
+
| §7 sizes | overlays = layout-transparent wrappers (`data-xui-overlay-root`: the parent's alignment, a Button trigger at its own size at the start, a Link trigger fills), menu trigger without chevron, Chart `height` = the whole box, AudioPlayer track + controls row, Video / Image 16:9, Table and CodeBlock from recipes only (per-side border widths no longer pick up the UA's 3 px), Browse in the drop zone, TreeGuides columns (round 3: 14 px), Switch label first, Checkbox gap, Radio items at the root gap, Accordion count part |
|
|
307
|
+
|
|
308
|
+
## Round 3 (VAPP-102: Row, Section, Chip, Segmented, Menu)
|
|
309
|
+
|
|
310
|
+
| item | web |
|
|
311
|
+
|---|---|
|
|
312
|
+
| macros | `Row`, `Section`, `Chip` need no painter: they expand to Box/Text/Icon/List/TreeGuides/Badge/Button/Image and the two new natives. A `Section` body is a divided `List`; with `tree` it is `role=tree` of `treeitem`s |
|
|
313
|
+
| Segmented | ONE row of segments (Radix ToggleGroup: radiogroup single / toolbar multiple, roving arrows), variants `segmented` (default) / `toggles` / `outline`; `bar` = a full-width `<nav>` of column buttons (icon above a caption label, `$control.tabBar` tall), `aria-current="page"`, the same roving keys; parts root, item, icon, label |
|
|
314
|
+
| Menu | ONE painter for press and context menus (`openOn`); builtin glyphs `Menu.check`, `Menu.submenuIndicator` |
|
|
315
|
+
| TreeGuides | a Row's `guides` part the CORE fills (`elbowAt`, `tee`, `passThrough`); `TREE_GUIDE_COLUMN` 14 px columns, the line at i·14 + 7, the elbow = one element (left + bottom borders) to the column's right edge with a `TREE_GUIDE_RADIUS` 3 px corner, every vertical starting `TREE_GUIDE_BRIDGE` 1 px above the part's top (overflow visible) |
|
|
316
|
+
|
|
317
|
+
## Round 5 (VAPP-162, `docs/round-5-contract.md`)
|
|
318
|
+
|
|
319
|
+
| item | web |
|
|
320
|
+
|---|---|
|
|
321
|
+
| macros | `MessageBubble`, `CallSurface`, `Plan` need no painter (they expand to Box/Text/Markdown/Icon/VoiceWave/Avatar/Tooltip/Button/Card/Trace/Progress) |
|
|
322
|
+
| QrCode | ONE SVG path of the core's `qrMatrix` in module units (quiet zone 4, `shape-rendering: crispEdges`) over a `size` px plate; the lighter of the plate/module recipe colours fills the plate (`qrPaint`), so a dark theme still scans; `label` = caption + img name |
|
|
323
|
+
| QrScanner | the viewfinder chrome (feed, four corner brackets, the scan line sweeping by CSS, still under reduced motion or `disabled`); THE CAMERA IS THE HOST'S: `host.qrScanner.start(feed, formats, onValue)` renders its feed into the `feed` element and returns the stop; every decoded string goes through `qrScanReporter` (once per distinct value) to `scanned {value}` and the polite live region; no `qrScanner` (or `start` returning `false`) = the `ui-scan` icon + `Camera unavailable` |
|
|
324
|
+
| VoiceWave `bars` | the core's `voiceWaveBars` as 20 rounded rects in the `bar` colour, rAF writes `y`/`height` while it moves; a child stays unpainted |
|
|
325
|
+
| Segmented | `orientation: vertical` stacks every variant (ArrowUp/ArrowDown); a vertical `bar` = the rail (72 × 56 items); `option.count` = the `badge` at a `bar` icon's top-end corner (`99+`, none at 0), read after the label |
|
|
326
|
+
| Button `hold` | `ButtonHold` (the shared machine): pointer capture on down, 24 px slop, Space/Enter key down → up, Escape/blur/pointercancel cancel; `holdingLabel` while held; never `press` |
|
|
327
|
+
| serif | `ui-serif` stays a CSS keyword: `font-family: ui-serif, Georgia, serif` |
|
|
328
|
+
|
|
329
|
+
## The primitive set
|
|
330
|
+
|
|
331
|
+
`src/primitives/` holds the generic shadcn/Radix components the app and
|
|
332
|
+
extension authors share (button, input, select, dialog, sheet, tabs, meter,
|
|
333
|
+
calendar, table, command, toast, … plus round 1's alert-dialog, breadcrumb,
|
|
334
|
+
kbd, scroll-area), themed only through the surface's variables; the catalog's
|
|
335
|
+
natives own Radix directly. The Exponential app's own components (the glass
|
|
336
|
+
rows, typeahead, composer, trace, pill, segmented bar) live in `@exp/ui`, not
|
|
337
|
+
here. Their class strings and pure helpers live in
|
|
338
|
+
`@exponential_app/eui/web/primitives` (FEED-131), shared with the Solid twin
|
|
339
|
+
(`@exponential_app/eui-solid/primitives`, the same module set). `@exp/ui` re-exports every one
|
|
340
|
+
(its old paths are one-line shims), and its `styles.css` scans this directory
|
|
341
|
+
so the app's Tailwind build still emits their classes. An embedder without
|
|
342
|
+
Tailwind takes the compiled `dist/exponential-ui-react.css`
|
|
343
|
+
(`bun run --filter @exponential_app/eui-react build:css`).
|
|
344
|
+
|
|
345
|
+
## Commands
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
bun run --filter @exponential_app/eui-react test # jsdom: fixtures → DOM, bind-time, round 1, inputs, data, theme css, primitives (LANG=en_US.UTF-8)
|
|
349
|
+
bun run --filter @exponential_app/eui-react test:browser # headless Chromium: geometry, overlays, typing, rounds 1–2, conformance
|
|
350
|
+
bun run --filter @exponential_app/eui-react bench:list # the 100,000-row list bench in Chromium
|
|
351
|
+
bun run --filter @exponential_app/eui-react typecheck
|
|
352
|
+
bun run --filter @exponential_app/eui-react dev:harness # http://localhost:4181/?view=kitchen-sink&theme=playful
|
|
353
|
+
bun run --filter @exponential_app/eui-react shots # shots/exponential-ui-kitchen-sink/{web,web-mobile}.webp
|
|
354
|
+
bun run --filter @exponential_app/eui-react build:css
|
|
355
|
+
bun run --filter @exponential_app/eui-react smoke:pack # packs both packages, builds a plain Vite app on them, renders the sink
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The app's `/exponential-ui-kitchen-sink` route (view `exponential-ui-kitchen-sink`)
|
|
359
|
+
renders the same fixture with the app's icon registry; the theme builder
|
|
360
|
+
(`packages/exponential-ui/builder`) previews its recipe sheet and the sink
|
|
361
|
+
through this renderer.
|