diff --git a/CODEOWNERS b/CODEOWNERS index 68fbe22b6a..4d7df82a28 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,5 @@ packages/rspeedy/** @colinaaa @upupming @luhc228 packages/rspeedy/plugin-react/** @upupming packages/react/** @hzy @HuJean @Yradex packages/react/transform/** @gaoachao +packages/genui/a2ui/** @pupiltong @Sherry-hue @HuJean @gaoachao @fzx2666-fz benchmark/react/** @hzy @HuJean diff --git a/codecov.yml b/codecov.yml index 0a3fb576a6..ff1bbb7990 100644 --- a/codecov.yml +++ b/codecov.yml @@ -24,6 +24,9 @@ coverage: default: threshold: "1%" +ignore: + - "packages/genui/**" + fixes: - "/home/runner/_work/lynx-stack::" diff --git a/packages/genui/a2ui/LICENSE b/packages/genui/a2ui/LICENSE new file mode 100644 index 0000000000..f4f87bd4ed --- /dev/null +++ b/packages/genui/a2ui/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + \ No newline at end of file diff --git a/packages/genui/a2ui/README.md b/packages/genui/a2ui/README.md new file mode 100644 index 0000000000..14314c5739 --- /dev/null +++ b/packages/genui/a2ui/README.md @@ -0,0 +1,3 @@ +# WIP: This package is inspired by Google A2UI. + +Currently We're still working on it. diff --git a/packages/genui/a2ui/package.json b/packages/genui/a2ui/package.json new file mode 100644 index 0000000000..24c46d6b25 --- /dev/null +++ b/packages/genui/a2ui/package.json @@ -0,0 +1,111 @@ +{ + "name": "@lynx-js/a2ui-reactlynx", + "version": "0.0.0", + "private": true, + "license": "Apache-2.0", + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.js" + }, + "./core": { + "types": "./src/core/index.ts", + "default": "./src/core/index.js" + }, + "./chat": { + "types": "./src/chat/index.ts", + "default": "./src/chat/index.js" + }, + "./catalog/all": { + "types": "./src/catalog/all.ts", + "default": "./src/catalog/all.js" + }, + "./catalog/Text": { + "types": "./src/catalog/Text.ts", + "default": "./src/catalog/Text.js" + }, + "./catalog/Image": { + "types": "./src/catalog/Image.ts", + "default": "./src/catalog/Image.js" + }, + "./catalog/Button": { + "types": "./src/catalog/Button.ts", + "default": "./src/catalog/Button.js" + }, + "./catalog/Row": { + "types": "./src/catalog/Row.ts", + "default": "./src/catalog/Row.js" + }, + "./catalog/Column": { + "types": "./src/catalog/Column.ts", + "default": "./src/catalog/Column.js" + }, + "./catalog/List": { + "types": "./src/catalog/List.ts", + "default": "./src/catalog/List.js" + }, + "./catalog/Card": { + "types": "./src/catalog/Card.ts", + "default": "./src/catalog/Card.js" + }, + "./catalog/Divider": { + "types": "./src/catalog/Divider.ts", + "default": "./src/catalog/Divider.js" + }, + "./catalog/TextField": { + "types": "./src/catalog/TextField.ts", + "default": "./src/catalog/TextField.js" + }, + "./catalog/Icon": { + "types": "./src/catalog/Icon.ts", + "default": "./src/catalog/Icon.js" + }, + "./catalog/Tabs": { + "types": "./src/catalog/Tabs.ts", + "default": "./src/catalog/Tabs.js" + }, + "./catalog/Modal": { + "types": "./src/catalog/Modal.ts", + "default": "./src/catalog/Modal.js" + }, + "./catalog/AudioPlayer": { + "types": "./src/catalog/AudioPlayer.ts", + "default": "./src/catalog/AudioPlayer.js" + }, + "./catalog/Slider": { + "types": "./src/catalog/Slider.ts", + "default": "./src/catalog/Slider.js" + }, + "./catalog/DateTimeInput": { + "types": "./src/catalog/DateTimeInput.ts", + "default": "./src/catalog/DateTimeInput.js" + }, + "./catalog/ChoicePicker": { + "types": "./src/catalog/ChoicePicker.ts", + "default": "./src/catalog/ChoicePicker.js" + }, + "./catalog/CheckBox": { + "types": "./src/catalog/CheckBox.ts", + "default": "./src/catalog/CheckBox.js" + } + }, + "main": "./src/index.js", + "types": "./src/index.ts", + "dependencies": { + "@a2ui/web_core": "0.9.1", + "@preact/signals": "^2.5.1" + }, + "devDependencies": { + "@lynx-js/lynx-ui": "^3.130.0", + "@lynx-js/lynx-ui-input": "^3.130.0", + "@lynx-js/react": "workspace:*", + "@lynx-js/types": "3.7.0", + "@types/react": "^18.3.28" + }, + "peerDependencies": { + "@lynx-js/lynx-ui": "^3.130.0", + "@lynx-js/lynx-ui-input": "^3.130.0", + "@lynx-js/react": "workspace:^" + } +} diff --git a/packages/genui/a2ui/src/catalog/Button.ts b/packages/genui/a2ui/src/catalog/Button.ts new file mode 100755 index 0000000000..655fcf6f70 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Button.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Button } from './Button/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Button', Button as unknown as ComponentRenderer); + +export { Button }; diff --git a/packages/genui/a2ui/src/catalog/Button/catalog.json b/packages/genui/a2ui/src/catalog/Button/catalog.json new file mode 100644 index 0000000000..fb277cb797 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Button/catalog.json @@ -0,0 +1,71 @@ +{ + "Button": { + "properties": { + "child": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "borderless" + ] + }, + "action": { + "type": "object", + "description": "v0.9 actions should use the `event` wrapper for server-dispatched clicks.", + "properties": { + "event": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "context": { + "type": "object", + "description": "Context is a JSON object map in v0.9.", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "event" + ], + "additionalProperties": false + } + }, + "required": [ + "child", + "action" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Button/index.tsx b/packages/genui/a2ui/src/catalog/Button/index.tsx new file mode 100755 index 0000000000..9a402a8cb1 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Button/index.tsx @@ -0,0 +1,31 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { A2UIRender } from '../../core/A2UIRender.jsx'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export function Button( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const { action, child, surface, sendAction } = props; + + const handleClick = () => { + if (action) { + void sendAction?.(action as Record); + } + }; + + const childResource = child + ? surface.resources.get(child as string) + : undefined; + + return ( + + {childResource + ? + : Button} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/Button/style.css b/packages/genui/a2ui/src/catalog/Button/style.css new file mode 100644 index 0000000000..250b24023a --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Button/style.css @@ -0,0 +1,24 @@ +@import "../luna-styles/index.css"; + +.button { + display: flex; + justify-content: center; + align-items: center; + min-width: 0; + padding-left: 12px; + padding-right: 12px; + padding-top: 8px; + padding-bottom: 8px; + margin: 4px; + border-radius: 9999px; + background-color: var(--primary); +} + +.button.ui-active { + background-color: var(--primary-2); +} + +.button-text { + font-size: 12px; + color: var(--primary-content); +} diff --git a/packages/genui/a2ui/src/catalog/Card.ts b/packages/genui/a2ui/src/catalog/Card.ts new file mode 100755 index 0000000000..8046e98e46 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Card.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Card } from './Card/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Card', Card as unknown as ComponentRenderer); + +export { Card }; diff --git a/packages/genui/a2ui/src/catalog/Card/catalog.json b/packages/genui/a2ui/src/catalog/Card/catalog.json new file mode 100644 index 0000000000..9fa220c8fc --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Card/catalog.json @@ -0,0 +1,12 @@ +{ + "Card": { + "properties": { + "child": { + "type": "string" + } + }, + "required": [ + "child" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Card/index.tsx b/packages/genui/a2ui/src/catalog/Card/index.tsx new file mode 100755 index 0000000000..32b3099ef9 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Card/index.tsx @@ -0,0 +1,35 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { NodeRenderer } from '../../core/A2UIRender.jsx'; +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export interface CardProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function Card( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const { child: childId, surface, dataContextPath } = props; + const childComponent = surface.components.get(childId as string); + const childWithContext = childComponent && dataContextPath + ? { ...childComponent, dataContextPath: dataContextPath } + : childComponent; + + return ( + + {childWithContext && ( + + )} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/Card/style.css b/packages/genui/a2ui/src/catalog/Card/style.css new file mode 100644 index 0000000000..7599a6527b --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Card/style.css @@ -0,0 +1,40 @@ +@import "../luna-styles/index.css"; + +.card { + display: flex; + flex-direction: column; + min-height: 0; + width: 100%; + box-sizing: border-box; + flex-shrink: 0; + height: fit-content; +} + +/* Elevated (Default) */ +.card-elevated { + background-color: var(--paper); + border-radius: 12px; + box-shadow: 0 2px 8px var(--backdrop); + padding: 16px; +} + +/* Outlined */ +.card-outlined { + background-color: var(--canvas); + border: 1px solid var(--line); + border-radius: 12px; + padding: 16px; +} + +/* Filled */ +.card-filled { + background-color: var(--neutral-film); + border-radius: 12px; + padding: 16px; +} + +/* Ghost */ +.card-ghost { + background-color: transparent; + padding: 0; +} diff --git a/packages/genui/a2ui/src/catalog/CheckBox.ts b/packages/genui/a2ui/src/catalog/CheckBox.ts new file mode 100755 index 0000000000..03c4ac6567 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/CheckBox.ts @@ -0,0 +1,13 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { CheckBox } from './CheckBox/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register( + 'CheckBox', + CheckBox as unknown as ComponentRenderer, +); + +export { CheckBox }; diff --git a/packages/genui/a2ui/src/catalog/CheckBox/catalog.json b/packages/genui/a2ui/src/catalog/CheckBox/catalog.json new file mode 100644 index 0000000000..1a69897d86 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/CheckBox/catalog.json @@ -0,0 +1,48 @@ +{ + "CheckBox": { + "properties": { + "label": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + }, + "value": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "label", + "value" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/CheckBox/index.tsx b/packages/genui/a2ui/src/catalog/CheckBox/index.tsx new file mode 100755 index 0000000000..e2f6d22e1f --- /dev/null +++ b/packages/genui/a2ui/src/catalog/CheckBox/index.tsx @@ -0,0 +1,32 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export interface CheckBoxProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function CheckBox( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const { id, label = 'CheckBox', value, setValue } = props; + + const handleChange = () => { + setValue?.('value', !value); + }; + + return ( + + + {!!value && } + + {label as string} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/CheckBox/style.css b/packages/genui/a2ui/src/catalog/CheckBox/style.css new file mode 100644 index 0000000000..a04e41cb49 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/CheckBox/style.css @@ -0,0 +1,24 @@ +@import "../luna-styles/index.css"; + +.checkbox-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + gap: 10px; + width: 100%; +} + +.checkbox-input { + width: 20px; + height: 20px; + border-radius: 4px; + border-width: 1.5px; + border-color: var(--line); + background-color: var(--canvas); +} + +.checkbox-label { + font-size: 16px; + color: var(--content-2); +} diff --git a/packages/genui/a2ui/src/catalog/Column.ts b/packages/genui/a2ui/src/catalog/Column.ts new file mode 100755 index 0000000000..04a7c48ff5 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Column.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Column } from './Column/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Column', Column as unknown as ComponentRenderer); + +export { Column }; diff --git a/packages/genui/a2ui/src/catalog/Column/catalog.json b/packages/genui/a2ui/src/catalog/Column/catalog.json new file mode 100644 index 0000000000..c08ad2bae5 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Column/catalog.json @@ -0,0 +1,57 @@ +{ + "Column": { + "properties": { + "children": { + "description": "Static child IDs array or template object.", + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "componentId": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "componentId", + "path" + ], + "additionalProperties": false + } + ] + }, + "justify": { + "type": "string", + "enum": [ + "start", + "center", + "end", + "spaceBetween", + "spaceAround", + "spaceEvenly", + "stretch" + ] + }, + "align": { + "type": "string", + "enum": [ + "center", + "end", + "start", + "stretch" + ] + } + }, + "required": [ + "children" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Column/index.tsx b/packages/genui/a2ui/src/catalog/Column/index.tsx new file mode 100644 index 0000000000..1f1ed50f73 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Column/index.tsx @@ -0,0 +1,44 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { NodeRenderer } from '../../core/A2UIRender.jsx'; +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export interface ColumnProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function Column( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const children = props['children']; + const surface = props.surface; + const dataContextPath = props.dataContextPath; + const justify = props['justify'] as string | undefined ?? 'start'; + const align = props['align'] as string | undefined ?? 'stretch'; + const explicitChildren = Array.isArray(children) ? children : []; + + return ( + + {explicitChildren.map((childId: string) => { + const child = surface.components.get(childId); + if (!child) return null; + const childWithContext = dataContextPath + ? { ...child, dataContextPath: dataContextPath } + : child; + return ( + + ); + })} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/Column/style.css b/packages/genui/a2ui/src/catalog/Column/style.css new file mode 100644 index 0000000000..3b22380d11 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Column/style.css @@ -0,0 +1,82 @@ +@import "../luna-styles/index.css"; + +.column { + display: flex; + flex-direction: column; +} + +/* Alignment */ +.alignment-start { + align-items: flex-start; +} + +.alignment-center { + align-items: center; +} + +.alignment-end { + align-items: flex-end; +} + +.alignment-stretch { + align-items: stretch; +} + +/* Distribution */ +.distribution-start { + justify-content: flex-start; +} + +.distribution-center { + justify-content: center; +} + +.distribution-end { + justify-content: flex-end; +} + +.distribution-spaceBetween { + justify-content: space-between; +} + +.distribution-spaceAround { + justify-content: space-around; +} + +.distribution-spaceEvenly { + justify-content: space-evenly; +} + +/* Usage Hints */ + +/* Section: Large sections of content */ +.col-hint-section { + padding: 24px; + gap: 24px; +} + +/* Group: Standard logical grouping */ +.col-hint-group { + gap: 16px; +} + +/* Compact: Tighter grouping */ +.col-hint-compact { + gap: 8px; +} + +/* Screen: Full screen container */ +.col-hint-screen { + width: 100%; + height: 100%; + background-color: var(--canvas); +} + +/* Root Column: Default root container */ +.col-hint-root-column { + width: 100%; + min-height: 100%; + padding: 16px; + gap: 16px; + background-color: var(--canvas-ambient); +} diff --git a/packages/genui/a2ui/src/catalog/Divider.ts b/packages/genui/a2ui/src/catalog/Divider.ts new file mode 100755 index 0000000000..44daa6fcf8 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Divider.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Divider } from './Divider/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Divider', Divider as unknown as ComponentRenderer); + +export { Divider }; diff --git a/packages/genui/a2ui/src/catalog/Divider/catalog.json b/packages/genui/a2ui/src/catalog/Divider/catalog.json new file mode 100644 index 0000000000..4dc765578f --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Divider/catalog.json @@ -0,0 +1,14 @@ +{ + "Divider": { + "properties": { + "axis": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ] + } + }, + "required": [] + } +} diff --git a/packages/genui/a2ui/src/catalog/Divider/index.tsx b/packages/genui/a2ui/src/catalog/Divider/index.tsx new file mode 100755 index 0000000000..9388ed96a3 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Divider/index.tsx @@ -0,0 +1,21 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export interface DividerProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function Divider( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const id = props.id; + const axis = props['axis'] as string | undefined ?? 'horizontal'; + return ; +} diff --git a/packages/genui/a2ui/src/catalog/Divider/style.css b/packages/genui/a2ui/src/catalog/Divider/style.css new file mode 100644 index 0000000000..436e03ee12 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Divider/style.css @@ -0,0 +1,19 @@ +@import "../luna-styles/index.css"; + +.divider { + display: block; + min-height: 0; + overflow: auto; + background-color: var(--rule); + border: none; +} + +.divider-horizontal { + width: 100%; + height: 1px; +} + +.divider-vertical { + height: 100%; + width: 1px; +} diff --git a/packages/genui/a2ui/src/catalog/Image.ts b/packages/genui/a2ui/src/catalog/Image.ts new file mode 100755 index 0000000000..96fcf68142 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Image.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Image } from './Image/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Image', Image as unknown as ComponentRenderer); + +export { Image }; diff --git a/packages/genui/a2ui/src/catalog/Image/catalog.json b/packages/genui/a2ui/src/catalog/Image/catalog.json new file mode 100644 index 0000000000..ea8f1489ca --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Image/catalog.json @@ -0,0 +1,50 @@ +{ + "Image": { + "properties": { + "url": { + "description": "Image URL or path binding.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + }, + "fit": { + "type": "string", + "enum": [ + "contain", + "cover", + "fill", + "none", + "scale-down" + ] + }, + "variant": { + "type": "string", + "enum": [ + "icon", + "avatar", + "smallFeature", + "mediumFeature", + "largeFeature", + "header" + ] + } + }, + "required": [ + "url" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Image/index.tsx b/packages/genui/a2ui/src/catalog/Image/index.tsx new file mode 100755 index 0000000000..9651cde658 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Image/index.tsx @@ -0,0 +1,33 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { useEffect, useState } from '@lynx-js/react'; + +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export function Image( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const { id, url } = props; + + const [hasError, setHasError] = useState(false); + + useEffect(() => { + setHasError(false); + }, [url]); + + const finalSrc = hasError + ? 'https://lf3-static.bytednsdoc.com/obj/eden-cn/zalzzh-ukj-lapzild-shpjpmmv-eufs/ljhwZthlaukjlkulzlp/built-in-images/logo.png' + : url; + + return ( + setHasError(true)} + /> + ); +} diff --git a/packages/genui/a2ui/src/catalog/Image/style.css b/packages/genui/a2ui/src/catalog/Image/style.css new file mode 100644 index 0000000000..7a5c8a4285 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Image/style.css @@ -0,0 +1,38 @@ +.icon { + width: 24px; + height: 24px; +} + +.avatar { + width: 40px; + height: 40px; + border-radius: 50%; +} + +.smallFeature { + width: 120px; + height: 80px; + border-radius: 8px; +} + +.mediumFeature { + width: 240px; + height: 160px; + border-radius: 8px; +} + +.largeFeature { + width: 100%; + height: 180px; + border-radius: 12px; +} + +.header { + width: 100%; + height: 200px; +} + +.a2ui-image { + flex-shrink: 0; + padding: 8px; +} diff --git a/packages/genui/a2ui/src/catalog/List.ts b/packages/genui/a2ui/src/catalog/List.ts new file mode 100755 index 0000000000..4b0460fa0a --- /dev/null +++ b/packages/genui/a2ui/src/catalog/List.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { List } from './List/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('List', List as unknown as ComponentRenderer); + +export { List }; diff --git a/packages/genui/a2ui/src/catalog/List/catalog.json b/packages/genui/a2ui/src/catalog/List/catalog.json new file mode 100644 index 0000000000..6c749b3d31 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/List/catalog.json @@ -0,0 +1,52 @@ +{ + "List": { + "properties": { + "children": { + "description": "Static child IDs array or template object.", + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "componentId": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "componentId", + "path" + ], + "additionalProperties": false + } + ] + }, + "direction": { + "type": "string", + "enum": [ + "vertical", + "horizontal" + ] + }, + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end", + "stretch" + ] + } + }, + "required": [ + "children" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/List/index.tsx b/packages/genui/a2ui/src/catalog/List/index.tsx new file mode 100755 index 0000000000..4a5bf28292 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/List/index.tsx @@ -0,0 +1,96 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { NodeRenderer } from '../../core/A2UIRender.jsx'; +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; +import { useDataBinding } from '../../core/useDataBinding.js'; + +import './style.css'; + +export interface ListProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function List( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const { children, surface, dataContextPath, direction = 'vertical' } = props; + + interface ListItem { + key: string; + component: v0_9.AnyComponent & { dataContextPath?: string }; + } + + const isDynamic = children && !Array.isArray(children) + && typeof children === 'object'; + const template = isDynamic + ? (children as { path: string; componentId: string }) + : undefined; + + const [listData, , fullPath] = useDataBinding[]>( + template ? { path: template.path } : undefined, + surface, + dataContextPath, + [], + ); + + let content: (ListItem | null)[] = []; + if (Array.isArray(children)) { + content = children.map((childId: string) => { + const child = surface.components.get(childId); + if (!child) return null; + // Propagate dataContextPath + const childWithContext = dataContextPath + ? { ...child, dataContextPath: dataContextPath } + : child; + return { + key: childId, + component: childWithContext, + }; + }); + const componentId = template?.componentId ?? ''; + + const items = Array.isArray(listData) ? listData : []; + + content = items.map((item, index) => { + const child = surface.components.get(componentId); + if (!child) return null; + + const key = item && typeof item === 'object' && 'key' in item + ? String(item['key']) + : `${index}`; + + const itemPath = `${fullPath}/${index}`; + const childWithContext = { ...child, dataContextPath: itemPath }; + + return { + key: key, + component: childWithContext, + }; + }); + } + + return ( + + {content.map((item) => { + if (!item) return null; + return ( + + + + ); + })} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/List/style.css b/packages/genui/a2ui/src/catalog/List/style.css new file mode 100644 index 0000000000..492fa3e05b --- /dev/null +++ b/packages/genui/a2ui/src/catalog/List/style.css @@ -0,0 +1,4 @@ +.list { + width: 100%; + height: 400px; +} diff --git a/packages/genui/a2ui/src/catalog/RadioGroup.ts b/packages/genui/a2ui/src/catalog/RadioGroup.ts new file mode 100644 index 0000000000..1f2e2f2a14 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/RadioGroup.ts @@ -0,0 +1,13 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { RadioGroup } from './RadioGroup/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register( + 'RadioGroup', + RadioGroup as unknown as ComponentRenderer, +); + +export { RadioGroup }; diff --git a/packages/genui/a2ui/src/catalog/RadioGroup/catalog.json b/packages/genui/a2ui/src/catalog/RadioGroup/catalog.json new file mode 100644 index 0000000000..c6f1a6aea3 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/RadioGroup/catalog.json @@ -0,0 +1,54 @@ +{ + "RadioGroup": { + "properties": { + "items": { + "description": "The list of string options to display.", + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": ["path"], + "additionalProperties": false + } + ] + }, + "value": { + "description": "The currently selected value.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": ["path"], + "additionalProperties": false + } + ] + }, + "usageHint": { + "type": "string", + "enum": ["default", "card", "row"], + "description": "A hint for the visual style of the radio group." + } + }, + "required": [ + "items", + "value" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/RadioGroup/index.tsx b/packages/genui/a2ui/src/catalog/RadioGroup/index.tsx new file mode 100644 index 0000000000..e24a2d9c18 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/RadioGroup/index.tsx @@ -0,0 +1,64 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Radio, RadioGroupRoot, RadioIndicator } from '@lynx-js/lynx-ui'; + +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { + ComponentInstance, + GenericComponentProps, +} from '../../core/types.js'; + +import './style.css'; + +const HitSlop = { + 'hit-slop': { + top: '8px' as `${number}px`, + left: '8px' as `${number}px`, + right: '8px' as `${number}px`, + bottom: '8px' as `${number}px`, + }, +}; + +export interface RadioGroupProps extends ComponentProps { + component: ComponentInstance & { dataContextPath?: string }; +} + +export function RadioGroup( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const value = props['value']; + const items = props['items']; + const usageHint = (props['usageHint'] as string | undefined) ?? 'default'; + const setValue = props.setValue as + | ((key: string, value: unknown) => void) + | undefined; + const explicitItems = Array.isArray(items) ? items : []; + + const handleValueChange = (newValue: string) => { + setValue?.('value', newValue); + }; + + return ( + + + + {explicitItems.map((itemValue: string) => ( + + + + + + + {itemValue} + + ))} + + + + ); +} diff --git a/packages/genui/a2ui/src/catalog/RadioGroup/style.css b/packages/genui/a2ui/src/catalog/RadioGroup/style.css new file mode 100644 index 0000000000..ac4ca5b48a --- /dev/null +++ b/packages/genui/a2ui/src/catalog/RadioGroup/style.css @@ -0,0 +1,110 @@ +@import "../luna-styles/index.css"; + +/* ========================= + * Radio Group + * ========================= */ + +.radio-group { + display: flex; + flex-direction: column; + width: 100%; +} + +.radio-group-container { + display: flex; + flex-direction: column; + gap: 16px; +} + +/* Usage Hints */ + +/* Default: Standard vertical list */ +.radio-group-default .radio-group-container { + flex-direction: column; + gap: 16px; +} + +/* Row: Horizontal layout */ +.radio-group-row .radio-group-container { + flex-direction: row; + flex-wrap: wrap; + gap: 24px; + align-items: center; +} + +/* Card: Options look like cards */ +.radio-group-card .radio-option { + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background-color: var(--paper); +} + +.radio-group-card .radio-option:active { + background-color: var(--neutral-film); +} + +/* ========================= + * Radio Option + * ========================= */ + +.radio-option { + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; + cursor: pointer; +} + +.label { + font-size: 16px; + color: var(--content); + line-height: 1.5; +} + +/* ========================= + * Radio Item + * ========================= */ + +.radio-item { + width: 20px; + height: 20px; + border-radius: 50%; + border-width: 1.5px; + border-style: solid; + border-color: var(--line); + display: flex; + align-items: center; + justify-content: center; + background-color: transparent; + flex-shrink: 0; +} + +.radio-item.ui-disabled { + opacity: 0.5; +} + +.radio-item.ui-checked { + border-color: var(--primary); + background-color: var(--primary); +} + +.radio-item.ui-active { + border-color: var(--primary-2); +} + +/* Indicator Dot */ +.radio-indicator { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.radio-indicator-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--primary-content); +} diff --git a/packages/genui/a2ui/src/catalog/Row.ts b/packages/genui/a2ui/src/catalog/Row.ts new file mode 100755 index 0000000000..6a025a3221 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Row.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Row } from './Row/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Row', Row as unknown as ComponentRenderer); + +export { Row }; diff --git a/packages/genui/a2ui/src/catalog/Row/catalog.json b/packages/genui/a2ui/src/catalog/Row/catalog.json new file mode 100644 index 0000000000..b607d7c8b5 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Row/catalog.json @@ -0,0 +1,57 @@ +{ + "Row": { + "properties": { + "children": { + "description": "Static child IDs array or template object.", + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "componentId": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "componentId", + "path" + ], + "additionalProperties": false + } + ] + }, + "justify": { + "type": "string", + "enum": [ + "center", + "end", + "spaceAround", + "spaceBetween", + "spaceEvenly", + "start", + "stretch" + ] + }, + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end", + "stretch" + ] + } + }, + "required": [ + "children" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Row/index.tsx b/packages/genui/a2ui/src/catalog/Row/index.tsx new file mode 100755 index 0000000000..712f4e99bc --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Row/index.tsx @@ -0,0 +1,44 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { NodeRenderer } from '../../core/A2UIRender.jsx'; +import type { ComponentProps } from '../../core/ComponentRegistry.js'; +import type { GenericComponentProps } from '../../core/types.js'; + +import './style.css'; + +export interface RowProps extends ComponentProps { + component: v0_9.AnyComponent & { dataContextPath?: string }; +} + +export function Row( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const children = props['children']; + const surface = props.surface; + const dataContextPath = props.dataContextPath; + const justify = props['justify'] as string | undefined ?? 'start'; + const align = props['align'] as string | undefined ?? 'stretch'; + const explicitChildren = Array.isArray(children) ? children : []; + + return ( + + {explicitChildren.map((childId: string) => { + const child = surface.components.get(childId); + if (!child) return null; + const childWithContext = dataContextPath + ? { ...child, dataContextPath: dataContextPath } + : child; + return ( + + ); + })} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/Row/style.css b/packages/genui/a2ui/src/catalog/Row/style.css new file mode 100644 index 0000000000..2c6c4ffb64 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Row/style.css @@ -0,0 +1,76 @@ +@import "../luna-styles/index.css"; + +.row { + display: flex; + flex-direction: row; + flex-wrap: wrap; /* Allow wrapping */ +} + +/* Alignment */ +.alignment-start { + align-items: flex-start; +} + +.alignment-center { + align-items: center; +} + +.alignment-end { + align-items: flex-end; +} + +.alignment-stretch { + align-items: stretch; +} + +/* Distribution */ +.distribution-start { + justify-content: flex-start; +} + +.distribution-center { + justify-content: center; +} + +.distribution-end { + justify-content: flex-end; +} + +.distribution-spaceBetween { + justify-content: space-between; +} + +.distribution-spaceAround { + justify-content: space-around; +} + +.distribution-spaceEvenly { + justify-content: space-evenly; +} + +/* Usage Hints */ + +/* Group: Standard horizontal grouping */ +.row-hint-group { + gap: 16px; +} + +/* Compact: Tight grouping (e.g., icon + text) */ +.row-hint-compact { + gap: 8px; +} + +/* Balanced: Good for space-between layouts */ +.row-hint-balanced { + width: 100%; +} + +/* Toolbar: Specific for action bars */ +.row-hint-toolbar { + width: 100%; + height: 56px; + padding: 0 16px; + align-items: center; + background-color: var(--paper); + border-bottom: 1px solid var(--line); +} diff --git a/packages/genui/a2ui/src/catalog/Text.ts b/packages/genui/a2ui/src/catalog/Text.ts new file mode 100755 index 0000000000..5e61540015 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Text.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { Text } from './Text/index.jsx'; +import { componentRegistry } from '../core/ComponentRegistry.js'; +import type { ComponentRenderer } from '../core/ComponentRegistry.js'; + +componentRegistry.register('Text', Text as unknown as ComponentRenderer); + +export { Text }; diff --git a/packages/genui/a2ui/src/catalog/Text/catalog.json b/packages/genui/a2ui/src/catalog/Text/catalog.json new file mode 100644 index 0000000000..d3a48c6a79 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Text/catalog.json @@ -0,0 +1,41 @@ +{ + "Text": { + "properties": { + "text": { + "description": "Literal text or path binding.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + }, + "variant": { + "type": "string", + "enum": [ + "h1", + "h2", + "h3", + "h4", + "h5", + "caption", + "body" + ] + } + }, + "required": [ + "text" + ] + } +} diff --git a/packages/genui/a2ui/src/catalog/Text/index.tsx b/packages/genui/a2ui/src/catalog/Text/index.tsx new file mode 100644 index 0000000000..8706acc272 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Text/index.tsx @@ -0,0 +1,19 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type { GenericComponentProps } from '../../core/types.js'; +import './style.css'; + +export function Text( + props: GenericComponentProps, +): import('@lynx-js/react').ReactNode { + const id = props.id; + const text = props['text']; + const variant = props['variant'] as string | undefined ?? 'body'; + + return ( + + {text as string} + + ); +} diff --git a/packages/genui/a2ui/src/catalog/Text/style.css b/packages/genui/a2ui/src/catalog/Text/style.css new file mode 100644 index 0000000000..55d98af8fb --- /dev/null +++ b/packages/genui/a2ui/src/catalog/Text/style.css @@ -0,0 +1,97 @@ +@import "../luna-styles/index.css"; + +.text-h1 { + font-size: 24px; + font-weight: bold; + color: var(--content); + line-height: 1.2; + flex-shrink: 0; +} + +.text-h2 { + font-size: 20px; + font-weight: bold; + color: var(--content); + line-height: 1.3; + flex-shrink: 0; +} + +.text-h3 { + font-size: 18px; + font-weight: bold; + color: var(--content); + line-height: 1.3; + flex-shrink: 0; +} + +.text-h4 { + font-size: 16px; + font-weight: bold; + color: var(--content); + line-height: 1.4; + flex-shrink: 0; +} + +.text-h5 { + font-size: 14px; + font-weight: bold; + color: var(--content); + line-height: 1.4; + flex-shrink: 0; +} + +.text-caption { + font-size: 12px; + color: var(--content-muted); + line-height: 1.4; + flex-shrink: 0; +} + +.text-body { + flex-shrink: 0; + font-size: 14px; + font-weight: normal; + color: var(--content); + line-height: 1.5; +} + +/* New Usage Hints */ + +.text-price { + flex-shrink: 0; + font-size: 16px; + font-weight: bold; + color: #ff4d4f; /* Typical price color, or use variable if available */ + line-height: 1.4; +} + +.text-link { + flex-shrink: 0; + font-size: 14px; + color: #1890ff; /* Link blue */ + text-decoration: underline; + line-height: 1.5; +} + +.text-label { + flex-shrink: 0; + font-size: 10px; + font-weight: bold; + color: var(--content-muted-2); + text-transform: uppercase; + letter-spacing: 0.5px; + line-height: 1.4; +} + +.button .text-body, +.button .text-caption, +.button .text-h1, +.button .text-h2, +.button .text-h3, +.button .text-h4, +.button .text-h5, +.button .text-price, +.button .text-link, +.button .text-label { + color: var(--primary-content); +} diff --git a/packages/genui/a2ui/src/catalog/all.ts b/packages/genui/a2ui/src/catalog/all.ts new file mode 100755 index 0000000000..50a81bafb8 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/all.ts @@ -0,0 +1,13 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export * from './Text/index.jsx'; +export * from './Image/index.jsx'; +export * from './Row/index.jsx'; +export * from './Column/index.jsx'; +export * from './List/index.jsx'; +export * from './Card/index.jsx'; +export * from './Button/index.jsx'; +export * from './Divider/index.jsx'; +export * from './CheckBox/index.jsx'; +export * from './RadioGroup/index.jsx'; diff --git a/packages/genui/a2ui/src/catalog/index.ts b/packages/genui/a2ui/src/catalog/index.ts new file mode 100755 index 0000000000..85f876d108 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/index.ts @@ -0,0 +1,4 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export * from './all.js'; diff --git a/packages/genui/a2ui/src/catalog/luna-styles/index.css b/packages/genui/a2ui/src/catalog/luna-styles/index.css new file mode 100644 index 0000000000..0d5f51f4ce --- /dev/null +++ b/packages/genui/a2ui/src/catalog/luna-styles/index.css @@ -0,0 +1,4 @@ +@import "./luna-dark.css"; +@import "./luna-light.css"; +@import "./lunaris-dark.css"; +@import "./lunaris-light.css"; diff --git a/packages/genui/a2ui/src/catalog/luna-styles/luna-dark.css b/packages/genui/a2ui/src/catalog/luna-styles/luna-dark.css new file mode 100644 index 0000000000..7376a27e56 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/luna-styles/luna-dark.css @@ -0,0 +1,37 @@ +.luna-dark { + --canvas: #0d0d0d; + --canvas-ambient: #000000; + --backdrop: rgba(0, 0, 0, 0.7); + --content: #f8f8f8; + --content-2: #d9d9d9; + --content-muted: #b3b3b3; + --content-muted-2: #959595; + --content-subtle: #6e6e6e; + --content-faint: #4f4f4f; + --paper: #1a1a1a; + --paper-clear: #232323; + --primary: #e0e0e0; + --primary-2: #a4a4a4; + --primary-muted: #7a7a7a; + --primary-content: #010101; + --primary-content-faded: rgba(0, 0, 0, 0.3); + --secondary: #5b5b5b; + --secondary-2: #333333; + --secondary-content: #e0e0e0; + --neutral: #f8f8f8; + --neutral-2: #d0d0d0; + --neutral-subtle: #656565; + --neutral-faint: #505050; + --neutral-ambient: #2c2c2c; + --neutral-content: #010101; + --neutral-veil: rgba(248, 248, 248, 0.5); + --neutral-film: rgba(248, 248, 248, 0.08); + --line: rgba(248, 248, 248, 0.32); + --rule: #333333; + --gradient-a: #a7a7a7; + --gradient-b: #a5a5a5; + --gradient-c: #a1a1a1; + --gradient-d: #bababa; + --gradient-content: #ffffff; + --gradient-content-faded: rgba(255, 255, 255, 0.68); +} diff --git a/packages/genui/a2ui/src/catalog/luna-styles/luna-light.css b/packages/genui/a2ui/src/catalog/luna-styles/luna-light.css new file mode 100644 index 0000000000..31f3e63486 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/luna-styles/luna-light.css @@ -0,0 +1,37 @@ +.luna-light { + --canvas: #ffffff; + --canvas-ambient: #f4f4f4; + --backdrop: rgba(0, 0, 0, 0.1); + --content: #010101; + --content-2: #333333; + --content-muted: #5d5d5d; + --content-muted-2: #707070; + --content-subtle: #9e9e9e; + --content-faint: #d0d0d0; + --paper: #f8f8f8; + --paper-clear: #ffffff; + --primary: #1a1a1a; + --primary-2: #545454; + --primary-muted: #9b9b9b; + --primary-content: #fafafa; + --primary-content-faded: rgba(255, 255, 255, 0.4); + --secondary: #c0c0c0; + --secondary-2: #d9d9d9; + --secondary-content: #3c3c3c; + --neutral: #010101; + --neutral-2: #333333; + --neutral-subtle: #b3b3b3; + --neutral-faint: #d9d9d9; + --neutral-ambient: #eeeeee; + --neutral-content: #f8f8f8; + --neutral-veil: rgba(0, 0, 0, 0.24); + --neutral-film: rgba(0, 0, 0, 0.05); + --line: rgba(0, 0, 0, 0.17); + --rule: #e5e5e5; + --gradient-a: #939393; + --gradient-b: #a0a0a0; + --gradient-c: #a1a1a1; + --gradient-d: #bfbfbf; + --gradient-content: #ffffff; + --gradient-content-faded: rgba(255, 255, 255, 0.68); +} diff --git a/packages/genui/a2ui/src/catalog/luna-styles/lunaris-dark.css b/packages/genui/a2ui/src/catalog/luna-styles/lunaris-dark.css new file mode 100644 index 0000000000..667ff311d9 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/luna-styles/lunaris-dark.css @@ -0,0 +1,37 @@ +.lunaris-dark { + --canvas: #0d0d0d; + --canvas-ambient: #000000; + --backdrop: rgba(0, 0, 0, 0.7); + --content: #f8f8f8; + --content-2: #d9d9d9; + --content-muted: #b3b3b3; + --content-muted-2: #959595; + --content-subtle: #6e6e6e; + --content-faint: #4f4f4f; + --paper: #1a1a1a; + --paper-clear: #232323; + --primary: #ff8ab5; + --primary-2: #ff4f8f; + --primary-muted: #d04377; + --primary-content: #010101; + --primary-content-faded: rgba(0, 0, 0, 0.3); + --secondary: #534363; + --secondary-2: #362e46; + --secondary-content: #eee5f6; + --neutral: #f8f8f8; + --neutral-2: #d0d0d0; + --neutral-subtle: #656565; + --neutral-faint: #505050; + --neutral-ambient: #2c2c2c; + --neutral-content: #010101; + --neutral-veil: rgba(248, 248, 248, 0.5); + --neutral-film: rgba(248, 248, 248, 0.08); + --line: rgba(248, 248, 248, 0.32); + --rule: #333333; + --gradient-a: #ff7385; + --gradient-b: #fe69a1; + --gradient-c: #a998bf; + --gradient-d: #00d0f1; + --gradient-content: #ffffff; + --gradient-content-faded: rgba(255, 255, 255, 0.68); +} diff --git a/packages/genui/a2ui/src/catalog/luna-styles/lunaris-light.css b/packages/genui/a2ui/src/catalog/luna-styles/lunaris-light.css new file mode 100644 index 0000000000..73b3dd34c5 --- /dev/null +++ b/packages/genui/a2ui/src/catalog/luna-styles/lunaris-light.css @@ -0,0 +1,37 @@ +.lunaris-light { + --canvas: #ffffff; + --canvas-ambient: #f4f4f4; + --backdrop: rgba(0, 0, 0, 0.1); + --content: #010101; + --content-2: #333333; + --content-muted: #5d5d5d; + --content-muted-2: #707070; + --content-subtle: #9e9e9e; + --content-faint: #d0d0d0; + --paper: #f8f8f8; + --paper-clear: #ffffff; + --primary: #ff1a6e; + --primary-2: #ff558e; + --primary-muted: #f992b1; + --primary-content: #ffffff; + --primary-content-faded: rgba(255, 255, 255, 0.4); + --secondary: #fbcfdc; + --secondary-2: #fce5ed; + --secondary-content: #a92d5a; + --neutral: #010101; + --neutral-2: #333333; + --neutral-subtle: #b3b3b3; + --neutral-faint: #d9d9d9; + --neutral-ambient: #eeeeee; + --neutral-content: #f8f8f8; + --neutral-veil: rgba(0, 0, 0, 0.24); + --neutral-film: rgba(0, 0, 0, 0.05); + --line: rgba(0, 0, 0, 0.17); + --rule: #e5e5e5; + --gradient-a: #ff3d63; + --gradient-b: #ff5d99; + --gradient-c: #a998bf; + --gradient-d: #3dd5e9; + --gradient-content: #ffffff; + --gradient-content-faded: rgba(255, 255, 255, 0.68); +} diff --git a/packages/genui/a2ui/src/chat/Conversation.tsx b/packages/genui/a2ui/src/chat/Conversation.tsx new file mode 100755 index 0000000000..aa07e4ab42 --- /dev/null +++ b/packages/genui/a2ui/src/chat/Conversation.tsx @@ -0,0 +1,134 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { + Input, + KeyboardAwareResponder, + KeyboardAwareRoot, + KeyboardAwareTrigger, +} from '@lynx-js/lynx-ui-input'; +import type { InputRef } from '@lynx-js/lynx-ui-input'; +import { useCallback, useRef, useState } from '@lynx-js/react'; + +import { useLynxClient } from './useLynxClient.js'; +import { A2UIRender } from '../core/A2UIRender.jsx'; +import type { Resource } from '../core/types.js'; + +export interface Message { + id: string; + role: 'user' | 'agent'; + content: string; + resource?: Resource; +} + +export interface ConversationProps { + initialInput?: string; + messages?: Message[]; + sendMessage?: (content: string) => Promise; + url?: string; +} + +export function Conversation( + props: ConversationProps, +): import('@lynx-js/react').ReactNode { + const { initialInput, url } = props; + + // Logic to handle self-managed state if url is provided + // We pass a dummy string if url is missing to satisfy the hook type, but we won't use the result if controlled + const hookResult = useLynxClient(url ?? ''); + + // If controlled props are provided, use them; otherwise use hook result if url is present + const messages = props.messages ?? (url ? hookResult.messages : []); + const sendMessage = props.sendMessage + ?? (url ? hookResult.sendMessage : undefined); + const inputRef = useRef(null); + + if (!sendMessage && !props.messages) { + // Fallback or error if neither controlled nor uncontrolled props are valid + console.warn( + 'Conversation requires either `messages` and `sendMessage` OR `url`.', + ); + } + + const [inputValue, setInputValue] = useState(initialInput); + const [isLoading, setIsLoading] = useState(false); + + const handleInput = useCallback((e: string) => { + setInputValue(e); + }, []); + + const handleSend = useCallback(() => { + setIsLoading(true); + const content = inputValue ?? 'Introduce yourself.'; + setInputValue(''); + void inputRef.current?.blur(); + void inputRef.current?.setValue(''); + try { + if (sendMessage) { + void sendMessage(content); + } + } catch (e) { + console.error('sendMessage error:', e); + } finally { + setIsLoading(false); + } + }, [inputValue, sendMessage]); + + return ( + + + + {messages?.map((item: Message) => + item.role === 'user' + ? ( + + {item.content} + + ) + : ( + + ( + Thinking... + )} + /> + + ) + )} + {isLoading && ( + + Thinking... + + )} + + + + + { + void handleSend(); + }} + > + + + + + + + ); +} diff --git a/packages/genui/a2ui/src/chat/index.ts b/packages/genui/a2ui/src/chat/index.ts new file mode 100644 index 0000000000..107469a136 --- /dev/null +++ b/packages/genui/a2ui/src/chat/index.ts @@ -0,0 +1,5 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export { Conversation, type Message } from './Conversation.jsx'; +export { useLynxClient } from './useLynxClient.js'; diff --git a/packages/genui/a2ui/src/chat/useLynxClient.ts b/packages/genui/a2ui/src/chat/useLynxClient.ts new file mode 100644 index 0000000000..b18375f069 --- /dev/null +++ b/packages/genui/a2ui/src/chat/useLynxClient.ts @@ -0,0 +1,112 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { useCallback, useRef, useState } from '@lynx-js/react'; + +import type { Message } from './Conversation.jsx'; +import { BaseClient } from '../core/BaseClient.js'; +import type { Resource } from '../core/types.js'; + +export interface UseLynxClientOptions { + keepHistory?: boolean; +} + +export interface UseLynxClientResult { + messages: Message[]; + sendMessage: ( + content: string, + ) => Promise<{ messageId: string; resource: Resource }>; + setMessages: import('@lynx-js/react').Dispatch< + import('@lynx-js/react').SetStateAction + >; + clientRef: import('@lynx-js/react').MutableRefObject; +} + +export function useLynxClient( + url: string, + options: UseLynxClientOptions = {}, +): UseLynxClientResult { + const { keepHistory = true } = options; + const clientRef = useRef(null); + const [messages, setMessages] = useState([]); + + const getClient = useCallback(() => { + if (!clientRef.current) { + const client = new BaseClient(url); + + client.onResourceCreated = (resource, id) => { + if (!keepHistory) return; + setMessages((prev) => [ + ...prev, + { + id: `${id}-agent-new`, + role: 'agent', + content: '', + resource, + }, + ]); + }; + + client.onResponseComplete = (messageId, { hasBeginRendering }) => { + if (!keepHistory) return; + if (!hasBeginRendering) { + setMessages((prev) => + prev.filter( + (m) => + m.id !== `${messageId}-agent` + && m.id !== `${messageId}-agent-new`, + ) + ); + } + }; + + clientRef.current = client; + } + return clientRef.current; + }, [url, keepHistory]); + + const sendMessage = useCallback( + async (content: string) => { + const client = getClient(); + + if (keepHistory) { + const userMsgId = 'user_' + + Date.now().toString(36) + + Math.random().toString(36).slice(2, 10); + setMessages((prev) => [ + ...prev, + { + id: userMsgId, + role: 'user', + content, + }, + ]); + } + + const result = await client.makeRequest(content); + const { messageId, resource } = result; + + if (keepHistory) { + setMessages((prev) => [ + ...prev, + { + id: `${messageId}-agent`, + role: 'agent', + content: '', + resource, + }, + ]); + } + + return result; + }, + [getClient, keepHistory], + ); + + return { + messages, + sendMessage, + setMessages, + clientRef, + }; +} diff --git a/packages/genui/a2ui/src/core/A2UIRender.tsx b/packages/genui/a2ui/src/core/A2UIRender.tsx new file mode 100644 index 0000000000..8981189f02 --- /dev/null +++ b/packages/genui/a2ui/src/core/A2UIRender.tsx @@ -0,0 +1,211 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { memo, useEffect, useState } from '@lynx-js/react'; +import type { ReactNode } from '@lynx-js/react'; + +import { componentRegistry } from './ComponentRegistry.js'; +import type { ComponentInstance, Resource, Surface } from './types.js'; +import { useAction } from './useAction.js'; +import { useResolvedProps } from './useDataBinding.js'; + +function Loading(props: { id: string }) { + const content = `loading ${props.id}...`; + return ( + + {content} + + ); +} + +function buildNodeRecursive( + component: ComponentInstance, + surface: Surface, + resolvedProps?: Record, + setValue?: (key: string, value: unknown) => void, + sendAction?: (action: Record) => void, +): ReactNode { + const tag = component.component; + + if (componentRegistry.has(tag)) { + const Component = componentRegistry.get(tag)! as unknown as ( + props: Record, + ) => import('@lynx-js/react').ReactNode; + return ( + ) => { + void sendAction?.(a); + }} + dataContextPath={component.dataContextPath} + {...resolvedProps} + /> + ); + } + + console.warn(`Component ${tag} not registered in v0.9 registry.`); + return null; +} + +function A2UIRenderImpl( + props: { + resource: Resource; + renderFallback?: () => import('@lynx-js/react').ReactNode; + }, +): import('@lynx-js/react').ReactNode { + const { resource } = props; + const [data, setData] = useState(() => { + if (resource.completed) { + try { + return resource.read(); + } catch { + return null; + } + } + return null; + }); + const [loading, setLoading] = useState(!resource.completed); + const [error, setError] = useState(null); + + useEffect(() => { + let active = true; + + if (resource.completed) { + try { + const res = resource.read(); + if (active) { + setData(res); + setLoading(false); + } + } catch (err) { + if (active) { + setError(err as Error); + setLoading(false); + } + } + } else { + resource.promise + .then((res) => { + if (active) { + setData(res); + setLoading(false); + } + }) + .catch((err) => { + if (active) { + setError(err as Error); + setLoading(false); + } + }); + } + + const unsubscribe = resource.onUpdate((newData) => { + if (active) { + setData(newData); + } + }); + + return () => { + active = false; + unsubscribe(); + }; + }, [resource]); + + if (loading) { + return props.renderFallback?.() ?? ; + } + + if (error) { + return Error: {String(error)}; + } + + if (!data) return null; + + const dataObj = data as Record; + const type = dataObj['type'] as string; + const surfaceId = dataObj['surfaceId'] as string; + const surface = dataObj['surface'] as Surface; + const component = dataObj['component'] as ComponentInstance | undefined; + if (type === 'beginRendering') { + const id = surface.rootComponentId!; + const childResource = surface.resources.get(id); + if (!childResource) return null; + return ( + + + + ); + } + + if (type === 'surfaceUpdate' && component) { + return ; + } + + if (type === 'deleteSurface') { + return null; + } + + return null; +} + +export const A2UIRender = memo(A2UIRenderImpl); + +export function NodeRenderer( + props: { component: ComponentInstance; surface: Surface }, +): import('@lynx-js/react').ReactNode { + const { component: initialComponent, surface } = props; + const [component, setComponent] = useState(initialComponent); + + useEffect(() => { + setComponent(initialComponent); + }, [initialComponent]); + + useEffect(() => { + const resource = surface.resources.get(component.id!); + if (!resource) return; + + return resource.onUpdate((data) => { + const dataMap = data as unknown as Readonly>; + if (dataMap['type'] === 'surfaceUpdate' && dataMap['component']) { + setComponent({ ...(dataMap['component'] as ComponentInstance) }); + } + }); + }, [component.id, surface]); + + const [resolvedProps, setValue] = useResolvedProps( + component, + surface, + component.dataContextPath, + ); + + const { sendAction } = useAction({ + id: component.id!, + surfaceId: surface.surfaceId, + dataContext: component.dataContextPath, + }); + + return ( + <> + {buildNodeRecursive( + component, + surface, + resolvedProps, + setValue, + (a: Record) => { + void sendAction(a as unknown as Parameters[0]); + }, + )} + + ); +} diff --git a/packages/genui/a2ui/src/core/BaseClient.ts b/packages/genui/a2ui/src/core/BaseClient.ts new file mode 100755 index 0000000000..dcbdf52248 --- /dev/null +++ b/packages/genui/a2ui/src/core/BaseClient.ts @@ -0,0 +1,590 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { processor } from './processor.js'; +import type { MessageProcessor } from './processor.js'; +import type { + A2UIClientEventMessage, + Resource, + ServerToClientMessage, + UserActionPayload, +} from './types.js'; +import { createResource } from '../utils/createResource.js'; + +const MESSAGE_PROCESS_DELAY = 300; + +function randomId(prefix = '') { + return prefix + Date.now().toString(36) + + Math.random().toString(36).slice(2, 10); +} + +function buildSseParams( + message: A2UIClientEventMessage, + messageId: string, +): Record { + const params: Record = { messageId }; + const anyMessage: Record = message as Record< + string, + unknown + >; + + if (typeof message === 'string') { + params['text'] = message; + } else if (anyMessage) { + if (typeof anyMessage['text'] === 'string') { + params['text'] = anyMessage['text']; + } else if (anyMessage['text']) { + params['text'] = JSON.stringify(anyMessage['text']); + } else if (anyMessage['userAction']) { + const userAction = anyMessage['userAction'] as { + name: string; + context?: Record; + }; + const actionName = userAction.name || 'unknownAction'; + const context = userAction.context ?? {}; + params['text'] = `USER_ACTION: ${actionName}, Context: ${ + JSON.stringify(context) + }`; + } else { + params['text'] = JSON.stringify(message); + } + + if (typeof anyMessage['sessionId'] === 'string') { + params['sessionId'] = anyMessage['sessionId']; + } else if (anyMessage['sessionId']) { + params['sessionId'] = JSON.stringify(anyMessage['sessionId']); + } + } + + return params; +} + +function createFallbackMessagesFromPlainText(text: string) { + const surfaceId = 'default'; + const rootId = 'root-text'; + + return [ + { + createSurface: { + surfaceId, + catalogId: 'inline-text', + }, + }, + { + updateComponents: { + surfaceId, + components: [ + { + id: rootId, + component: 'Text', + text, + }, + ], + }, + }, + ]; +} + +function createTextCardMessages(text: string) { + const surfaceId = randomId('text_surface_'); + const rootId = 'root'; + const textId = 'text'; + + return [ + { + createSurface: { + surfaceId, + catalogId: 'inline-text', + }, + }, + { + updateComponents: { + surfaceId, + components: [ + { + id: rootId, + component: 'Card', + child: textId, + }, + { + id: textId, + component: 'Text', + text, + variant: 'body', + }, + ], + }, + }, + ]; +} + +function normalizePayloadToMessages(payload: unknown): ServerToClientMessage[] { + const messages: ServerToClientMessage[] = []; + + const add = (value: unknown) => { + if (!value) return; + if (Array.isArray(value)) { + for (const item of value) { + add(item); + } + } else { + messages.push(value as ServerToClientMessage); + } + }; + + const handle = (value: unknown): void => { + if (value === null || value === undefined) return; + + if (Array.isArray(value)) { + for (const item of value) { + handle(item); + } + return; + } + + if (typeof value === 'string') { + add(createFallbackMessagesFromPlainText(value)); + return; + } + + if (typeof value === 'object') { + const v = value as Record; + + if ( + v['createSurface'] || v['updateComponents'] || v['updateDataModel'] + || v['deleteSurface'] + ) { + add(v); + return; + } + + if ('kind' in v && 'data' in v) { + if (v['kind'] === 'data') { + handle(v['data']); + } else if (v['kind'] === 'text') { + add( + createTextCardMessages( + typeof v['data'] === 'string' ? v['data'] : String(v['data']), + ), + ); + } + return; + } + + if (Array.isArray(v['messages'])) { + handle(v['messages']); + return; + } + + add(createFallbackMessagesFromPlainText(JSON.stringify(v))); + return; + } + }; + + handle(payload); + return messages; +} + +function prepareMessagesForProcessing( + rawMessages: ServerToClientMessage[], + messageId: string, + activeSurfaceIds: Set, +) { + let hasComponentUpdate = false; + const messages = rawMessages.filter((msg: ServerToClientMessage) => { + const deletedSurfaceId = (msg as { deleteSurface?: { surfaceId?: string } }) + .deleteSurface?.surfaceId; + if (typeof deletedSurfaceId === 'string') { + activeSurfaceIds.delete(deletedSurfaceId); + } + + const createdSurfaceId = (msg as { createSurface?: { surfaceId?: string } }) + .createSurface?.surfaceId; + if (typeof createdSurfaceId === 'string') { + if (activeSurfaceIds.has(createdSurfaceId)) { + return false; + } + activeSurfaceIds.add(createdSurfaceId); + } + + if ( + ((msg as { updateComponents?: { components?: unknown[] } }) + .updateComponents + && Array.isArray( + (msg as { updateComponents?: { components?: unknown[] } }) + .updateComponents?.components, + ) + && (((msg as { updateComponents?: { components: unknown[] } }) + .updateComponents?.components ?? []).length > 0)) + ) { + hasComponentUpdate = true; + } + + msg.messageId ??= messageId; + + return true; + }); + + return { messages, hasComponentUpdate }; +} + +export class BaseClient { + protected processor: MessageProcessor; + protected resources: Map; + protected resolves: Map void>; + protected baseUrl: string; + public onResponseComplete?: ( + messageId: string, + info: { hasBeginRendering: boolean }, + ) => void; + public onResourceCreated?: (resource: Resource, messageId: string) => void; + + constructor(baseUrl: string) { + this.processor = processor as unknown as MessageProcessor; + this.resources = new Map(); + this.resolves = new Map(); + this.baseUrl = baseUrl; + + this.processor.onUpdate((data) => { + const { type, surfaceId, messageId, updates = [] } = data as { + surfaceId: string; + type: string; + messageId: string; + updates?: v0_9.AnyComponent[]; + targetId?: string; + }; + + const surface = this.processor.getOrCreateSurface(surfaceId); + + if (type === 'beginRendering') { + const resource = this.resources.get(messageId); + resource?.complete({ type: 'beginRendering', surfaceId, surface }); + } else if (type === 'surfaceUpdate') { + (updates || []).forEach((update) => { + if (!update.id) return; + const resource = surface.resources.get(update.id); + resource?.complete({ + type: 'surfaceUpdate', + surfaceId, + surface, + component: update as import('./types.js').ComponentInstance, + }); + }); + } else if (type === 'deleteSurface') { + const { targetId } = data as { targetId?: string }; + const target = targetId ?? surface.rootComponentId; + if (target && surface.resources.has(target)) { + const resource = surface.resources.get(target)!; + resource.complete({ type: 'deleteSurface', surfaceId, surface }); + } + } + + const resolve = this.resolves.get(messageId); + if (resolve) { + resolve({ type, surfaceId, surface }); + this.resolves.delete(messageId); + } + }); + + this.processor.onEvent( + ({ message, resolve }: import('./processor.js').A2UIEvent) => { + void (async () => { + if ( + typeof message === 'object' && message !== null + && 'userAction' in message + && (message as { userAction: unknown }).userAction + ) { + try { + const response = await this.processUserAction( + (message as { userAction: unknown }) + .userAction as UserActionPayload, + ); + resolve(response); + } catch (e) { + console.error('Error processing userAction', e); + resolve([]); + } + } else { + resolve([]); + } + })(); + }, + ); + } + + async processUserAction(userAction: UserActionPayload): Promise { + const response = await this.send({ userAction } as A2UIClientEventMessage); + const { messageId, resource, startStreaming, promise } = response; + this.resources.set(messageId, resource); + if (this.onResourceCreated) { + this.onResourceCreated(resource, messageId); + } + startStreaming(); + return promise; + } + + async makeRequest( + request: string, + ): Promise< + { messageId: string; resource: Resource; promise: Promise } + > { + const response = await this.send( + request as unknown as A2UIClientEventMessage, + ); + const { messageId, resource, startStreaming, promise } = response; + this.resources.set(messageId, resource); + startStreaming(); + return { messageId, resource, promise }; + } + + async send( + message: A2UIClientEventMessage, + id?: string, + ): Promise<{ + messageId: string; + resource: Resource; + startStreaming: () => void; + promise: Promise; + }> { + const messageId = id ?? randomId('task_'); + const promise = new Promise((resolve) => { + this.resolves.set(messageId, resolve); + }); + + const resource = createResource(messageId) as unknown as Resource; + + const startStreaming = () => { + void (async () => { + const params = new URLSearchParams(buildSseParams(message, messageId)); + + interface TypedEventSource { + addEventListener( + type: string, + listener: ( + event: { data?: unknown; target?: unknown; type?: string }, + ) => void, + ): void; + close(): void; + readyState: number; + } + const g = globalThis as Record; + const tsKey = 'Event' + 'Source'; + const NativeES = g[tsKey] as + | (new(url: string) => TypedEventSource) + | undefined; + const EventSourceImpl = NativeES + ?? (lynx.EventSource as unknown as new( + url: string, + ) => TypedEventSource); + + const url = `${this.baseUrl}?${params.toString()}`; + + console.info('[BaseClient v0.9] streaming answer message', message); + + if (url.includes('localhost') && typeof lynx !== 'undefined') { + console.warn( + '[BaseClient v0.9] You are using \'localhost\' in Lynx environment. This may not work on a physical device. Please use your computer\'s IP address.', + ); + } + + if (url.length > 2048) { + console.warn( + `[BaseClient v0.9] URL is too long (${url.length} chars), request might fail. Consider using POST or shortening the payload.`, + ); + } + + console.info( + '[BaseClient v0.9] Using EventSource implementation:', + EventSourceImpl === NativeES + ? 'Native EventSource' + : 'Custom/Lynx EventSource', + ); + + const eventSource = new EventSourceImpl(url); + + console.info( + '[BaseClient v0.9] EventSource created, readyState:', + (eventSource as unknown as { readyState: number }).readyState, + ); + + let isCompleted = false; + let hasBeginRendering = false; + let hasReceivedProcessedPayload = false; + const activeSurfaceIds = new Set(this.processor.getSurfaces().keys()); + + const messageQueue: ServerToClientMessage[][] = []; + let isProcessingQueue = false; + + const processQueue = async () => { + if (isProcessingQueue) return; + isProcessingQueue = true; + while (messageQueue.length > 0) { + const msgs = messageQueue.shift(); + if (msgs && msgs.length > 0) { + this.processor.processMessages(msgs); + } + await new Promise((resolve) => + setTimeout(resolve, MESSAGE_PROCESS_DELAY) + ); + } + isProcessingQueue = false; + }; + + eventSource.addEventListener( + 'open', + (event: { data?: unknown; target?: unknown; type?: string }) => { + console.info('[BaseClient v0.9] SSE connection opened', event); + }, + ); + + eventSource.addEventListener( + 'update', + (event: { data?: unknown; target?: unknown; type?: string }) => { + console.info( + '[BaseClient v0.9] SSE update event', + event.data, + event, + ); + }, + ); + + eventSource.addEventListener( + 'delta', + (event: { data?: unknown; target?: unknown; type?: string }) => { + console.info( + '[BaseClient v0.9] SSE delta event', + event.data, + event, + ); + try { + let payload = event.data; + if (typeof payload === 'string') { + try { + payload = JSON.parse(payload); + } catch { + // ignore + } + } + + if (typeof payload === 'string') { + try { + payload = JSON.parse(payload); + } catch { + // ignore + } + } + + const messages = normalizePayloadToMessages(payload); + console.info( + '[BaseClient v0.9] Normalized delta messages', + messages, + ); + const prepared = prepareMessagesForProcessing( + messages, + messageId, + activeSurfaceIds, + ); + if (prepared.hasComponentUpdate) { + hasBeginRendering = true; + } + + if (prepared.messages.length > 0) { + hasReceivedProcessedPayload = true; + messageQueue.push(prepared.messages); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + processQueue(); + } + } catch (e) { + console.error('Error processing delta', e); + } + }, + ); + + eventSource.addEventListener( + 'complete', + (event: { data?: unknown; target?: unknown; type?: string }) => { + console.info( + '[BaseClient v0.9] SSE complete event', + event.data, + event, + ); + if (isCompleted) return; + isCompleted = true; + + try { + let payload = event.data; + if (typeof payload === 'string') { + try { + payload = JSON.parse(payload); + } catch { + // ignore + } + } + + const messages = normalizePayloadToMessages(payload); + console.info( + '[BaseClient v0.9] Normalized complete messages', + messages, + ); + + if (!hasReceivedProcessedPayload && messages.length > 0) { + const prepared = prepareMessagesForProcessing( + messages, + messageId, + activeSurfaceIds, + ); + if (prepared.hasComponentUpdate) { + hasBeginRendering = true; + } + + if (prepared.messages.length > 0) { + messageQueue.push(prepared.messages); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + processQueue(); + } + } + } catch (e) { + console.error( + '[BaseClient v0.9] Error processing complete payload', + e, + ); + } + + eventSource.close(); + + if (this.onResponseComplete) { + this.onResponseComplete(messageId, { hasBeginRendering }); + } + }, + ); + + eventSource.addEventListener( + 'error', + (event: { data?: unknown; target?: unknown; type?: string }) => { + console.error('[BaseClient v0.9] SSE error details:', event); + if ( + event && typeof event === 'object' && 'target' in event + && event.target && typeof event.target === 'object' + && 'readyState' in event.target + && typeof (event.target as Record)['readyState'] + !== 'undefined' + ) { + const target = event.target as Record; + console.error( + '[BaseClient v0.9] SSE readyState:', + target['readyState'], + ); + } + eventSource.close(); + }, + ); + })(); + }; + + return { messageId, resource, startStreaming, promise }; + } +} diff --git a/packages/genui/a2ui/src/core/ComponentRegistry.ts b/packages/genui/a2ui/src/core/ComponentRegistry.ts new file mode 100644 index 0000000000..10daf8f8fd --- /dev/null +++ b/packages/genui/a2ui/src/core/ComponentRegistry.ts @@ -0,0 +1,37 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type { ComponentType } from '@lynx-js/react'; + +import type { ComponentInstance, Surface } from './types.js'; + +export class BaseComponentRegistry { + private registry = new Map(); + register(name: string, component: T): void { + this.registry.set(name, component); + } + has(name: string): boolean { + return this.registry.has(name); + } + get(name: string): T | undefined { + return this.registry.get(name); + } +} + +export interface ComponentProps { + id: string; + surfaceId: string; + surface: Surface; + /** + * The full v0.9 component instance as defined by the protocol. + */ + component: ComponentInstance; +} + +export type ComponentRenderer = ComponentType; + +export class ComponentRegistry + extends BaseComponentRegistry +{} + +export const componentRegistry = new ComponentRegistry(); diff --git a/packages/genui/a2ui/src/core/index.ts b/packages/genui/a2ui/src/core/index.ts new file mode 100644 index 0000000000..ddcd260280 --- /dev/null +++ b/packages/genui/a2ui/src/core/index.ts @@ -0,0 +1,10 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export * from './types.js'; +export { A2UIRender } from './A2UIRender.jsx'; +export { BaseClient } from './BaseClient.js'; +export { componentRegistry, type ComponentProps } from './ComponentRegistry.js'; +export { processor, type MessageProcessor } from './processor.js'; +export { useAction } from './useAction.js'; +export { useDataBinding, useResolvedProps } from './useDataBinding.js'; diff --git a/packages/genui/a2ui/src/core/processor.ts b/packages/genui/a2ui/src/core/processor.ts new file mode 100644 index 0000000000..883d9860fe --- /dev/null +++ b/packages/genui/a2ui/src/core/processor.ts @@ -0,0 +1,456 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import type { + ComponentInstance, + ServerToClientMessage, + Surface, +} from './types.js'; +import { createResource } from '../utils/createResource.js'; +import { SignalStore } from '../utils/SignalStore.js'; + +export interface A2UIEvent { + message: Record; + resolve: (response: unknown) => void; +} + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export class MessageProcessor { + surfaces: Map; + private listener: ((event: A2UIEvent) => void) | null = null; + private updateListener: ((data: Record) => void) | null = + null; + + constructor() { + this.surfaces = new Map(); + } + + onUpdate(callback: (data: Record) => void): void { + this.updateListener = callback; + } + + dispatch(message: Record): Promise { + return new Promise((resolve) => { + if (this.listener) { + this.listener({ message, resolve }); + } else { + console.warn('No host listener attached!'); + resolve([]); + } + }); + } + + onEvent(callback: (event: A2UIEvent) => void): () => void { + this.listener = callback; + return () => { + this.listener = null; + }; + } + + getSurfaces(): ReadonlyMap { + return this.surfaces; + } + + clearSurfaces(): void { + this.surfaces.clear(); + } + + getOrCreateSurface(surfaceId: string): Surface { + let surface = this.surfaces.get(surfaceId); + if (!surface) { + surface = { + surfaceId, + rootComponentId: null, + components: new Map(), + resources: new Map(), + store: new SignalStore(), + }; + this.surfaces.set(surfaceId, surface); + } else if (!surface.store) { + surface.store = new SignalStore(); + } + return surface; + } + + /** + * Resolve a JSON Pointer taking into account the current data context. + * + * - Absolute paths (starting with '/') are returned as-is. + * - Relative paths are resolved against the provided dataContextPath. + */ + resolvePath(path: string, dataContextPath?: string): string { + if (!path) return path; + if (path.startsWith('/')) { + return path; + } + const context = dataContextPath ?? ''; + const cleanContext = context.endsWith('/') ? context.slice(0, -1) : context; + + if (path.startsWith('./')) { + const rest = path.substring(2); + if (!cleanContext) { + return `/${rest}`; + } + return `${cleanContext}/${rest}`; + } + + if (!cleanContext) { + return `/${path}`; + } + return `${cleanContext}/${path}`; + } + + private cloneComponentTree( + originalId: string, + newIdSuffix: string, + dataContextPath: string, + surface: Surface, + updates: ComponentInstance[], + ): string | null { + const original = surface.components.get(originalId); + if (!original) return null; + + const newId = `${originalId}${newIdSuffix}`; + const cloned = JSON.parse(JSON.stringify(original)) as ComponentInstance; + (cloned as unknown as Record)['id'] = newId; + cloned.dataContextPath = dataContextPath; + + surface.components.set(newId, cloned); + updates.push(cloned); + + if (!surface.resources.has(newId)) { + surface.resources.set(newId, createResource(newId)); + } + + // Recursively clone the subtree below this component. + const childIds: string[] = []; + const anyCloned = cloned as unknown as Record; + + if (Array.isArray(anyCloned['children'])) { + for (const childId of anyCloned['children']) { + if (typeof childId === 'string') { + childIds.push(childId); + } + } + } + + if (typeof anyCloned['child'] === 'string') { + childIds.push(anyCloned['child']); + } + + if (Array.isArray(anyCloned['tabs'])) { + for (const tab of anyCloned['tabs'] as unknown[]) { + if ( + tab && typeof tab === 'object' && 'child' in tab + && typeof (tab as Record)['child'] === 'string' + ) { + childIds.push((tab as Record)['child'] as string); + } + } + } + + if (typeof anyCloned['trigger'] === 'string') { + childIds.push(anyCloned['trigger']); + } + + if (typeof anyCloned['content'] === 'string') { + childIds.push(anyCloned['content']); + } + + const newChildren: string[] = []; + for (const childId of childIds) { + const newChildId = this.cloneComponentTree( + childId, + newIdSuffix, + dataContextPath, + surface, + updates, + ); + if (newChildId) { + newChildren.push(newChildId); + } + } + + if (Array.isArray(anyCloned['children'])) { + anyCloned['children'] = newChildren; + } else if (newChildren.length > 0) { + anyCloned['children'] = newChildren; + } + + return newId; + } + + private flattenValue( + value: unknown, + basePath: string, + updates: { path: string; value: unknown }[], + ) { + const normalizedBase = basePath === '' ? '/' : basePath; + + const push = (path: string, v: unknown) => { + updates.push({ path, value: v }); + }; + + if (Array.isArray(value)) { + push(normalizedBase, value); + value.forEach((item, index) => { + const childPath = normalizedBase === '/' + ? `/${index}` + : `${normalizedBase}/${index}`; + if (isObject(item) || Array.isArray(item)) { + push(childPath, item); + this.flattenValue(item, childPath, updates); + } else { + updates.push({ path: childPath, value: String(item) }); + } + }); + return; + } + + if (isObject(value)) { + push(normalizedBase, value); + for (const [key, v] of Object.entries(value)) { + const childPath = normalizedBase === '/' + ? `/${key}` + : `${normalizedBase}/${key}`; + if (isObject(v) || Array.isArray(v)) { + push(childPath, v); + this.flattenValue(v, childPath, updates); + } else { + updates.push({ path: childPath, value: String(v) }); + } + } + return; + } + + // Primitive at the base path. + updates.push({ path: normalizedBase, value: String(value) }); + } + + processMessages(messages: ServerToClientMessage[]): void { + for (const message of messages) { + if ('createSurface' in message && message.createSurface) { + const createSurface = (message as unknown as Record)[ + 'createSurface' + ] as Record; + const surfaceId = createSurface['surfaceId'] as string; + const surface = this.getOrCreateSurface(surfaceId); + const catId = createSurface['catalogId']; + if (catId !== undefined) surface.catalogId = catId as string; + const t = createSurface['theme']; + if (t !== undefined) { + surface.theme = t as Readonly>; + } + const sData = createSurface['sendDataModel']; + if (sData !== undefined) surface.sendDataModel = sData as boolean; + } + + if ('updateComponents' in message && message.updateComponents) { + const { surfaceId, components } = message.updateComponents; + const surface = this.getOrCreateSurface(surfaceId); + + const updatesMap = new Map(); + + for (const item of components as ComponentInstance[]) { + if (!item.id) continue; + const existing = surface.components.get(item.id); + const dataContextPath = existing?.dataContextPath; + const instance = { ...item } as ComponentInstance; + if (dataContextPath !== undefined) { + instance.dataContextPath = dataContextPath; + } + + const anyInstance = instance as unknown as Record; + if ( + anyInstance['children'] + && !Array.isArray(anyInstance['children']) + && typeof anyInstance['children'] === 'object' + && anyInstance['children'] !== null + && typeof (anyInstance['children'] as Record)[ + 'componentId' + ] === 'string' + && typeof (anyInstance['children'] as Record)[ + 'path' + ] === 'string' + ) { + const templatePath = this.resolvePath( + (anyInstance['children'] as Record)[ + 'path' + ] as string, + dataContextPath, + ); + instance.__template = { + componentId: (anyInstance['children'] as Record)[ + 'componentId' + ] as string, + path: templatePath, + }; + } + + // this.resolveComponentPaths(instance, dataContextPath); + + surface.components.set(instance.id!, instance); + updatesMap.set(instance.id!, instance); + + if (!surface.resources.has(instance.id!)) { + surface.resources.set(instance.id!, createResource(instance.id!)); + } + } + + // Determine the root component if not already set. + if (!surface.rootComponentId) { + if (surface.components.has('root')) { + surface.rootComponentId = 'root'; + } else if (updatesMap.size > 0) { + // Fallback: use the first updated component as root if not specified + surface.rootComponentId = updatesMap.keys().next().value ?? null; + } + + if (surface.rootComponentId) { + if (!surface.resources.has(surface.rootComponentId)) { + surface.resources.set( + surface.rootComponentId, + createResource(surface.rootComponentId), + ); + } + // Signal that rendering can begin for this surface. + if (this.updateListener) { + this.updateListener({ + type: 'beginRendering', + surfaceId, + messageId: (message as { messageId?: string }).messageId, + }); + } + } + } + + const updates = Array.from(updatesMap.values()); + if (updates.length > 0 && this.updateListener) { + this.updateListener({ + type: 'surfaceUpdate', + updates, + surfaceId, + }); + } + } + + if ('updateDataModel' in message && message.updateDataModel) { + const { surfaceId, path, value } = message.updateDataModel as { + surfaceId: string; + path?: string; + value?: unknown; + }; + const surface = this.getOrCreateSurface(surfaceId); + + const updates: { path: string; value: unknown }[] = []; + + if (value !== undefined) { + const basePath = path && path !== '' ? path : '/'; + this.flattenValue(value, basePath, updates); + } else if (path) { + // Deletion semantics: we simply clear the value at the path. + updates.push({ path, value: '' }); + } + + if (updates.length > 0) { + surface.store.updateBatch(updates); + } + + // Re-expand any templated containers based on the updated data model. + const componentUpdates: ComponentInstance[] = []; + + for (const component of surface.components.values()) { + const anyComponent = component as unknown as Record; + const templateInfo = anyComponent['__template'] as + | { componentId: v0_9.ComponentId; path: string } + | undefined; + + if (!templateInfo) continue; + + const dataSignal = surface.store.getSignal(templateInfo.path); + let data: unknown; + try { + data = dataSignal.value + ? JSON.parse(dataSignal.value as string) + : undefined; + } catch { + data = undefined; + } + + const explicitChildren: string[] = []; + const generatedUpdates: ComponentInstance[] = []; + + if (Array.isArray(data)) { + data.forEach((_, index) => { + const suffix = `:${index}`; + const ctx = `${templateInfo.path}/${index}`; + const newId = this.cloneComponentTree( + templateInfo.componentId, + suffix, + ctx, + surface, + generatedUpdates, + ); + if (newId) { + explicitChildren.push(newId); + } + }); + } else if (isObject(data)) { + for (const key of Object.keys(data)) { + const suffix = `:${key}`; + const ctx = `${templateInfo.path}/${key}`; + const newId = this.cloneComponentTree( + templateInfo.componentId, + suffix, + ctx, + surface, + generatedUpdates, + ); + if (newId) { + explicitChildren.push(newId); + } + } + } + + if (explicitChildren.length > 0) { + anyComponent['children'] = explicitChildren; + componentUpdates.push(component); + componentUpdates.push(...generatedUpdates); + } + } + + if (componentUpdates.length > 0 && this.updateListener) { + this.updateListener({ + type: 'surfaceUpdate', + updates: componentUpdates, + surfaceId, + }); + } + } + + if ('deleteSurface' in message && message.deleteSurface) { + const { surfaceId } = message.deleteSurface; + const surface = this.surfaces.get(surfaceId); + + if (this.updateListener) { + this.updateListener({ + type: 'deleteSurface', + surfaceId, + targetId: surface?.rootComponentId ?? surfaceId, + messageId: (message as { messageId?: string }).messageId, + }); + } + + // Optionally clear local state for this surface. + this.surfaces.delete(surfaceId); + } + } + } +} + +export const processor: MessageProcessor = new MessageProcessor(); diff --git a/packages/genui/a2ui/src/core/types.ts b/packages/genui/a2ui/src/core/types.ts new file mode 100644 index 0000000000..2a7bfd4db9 --- /dev/null +++ b/packages/genui/a2ui/src/core/types.ts @@ -0,0 +1,84 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import type { Resource as GenericResource } from '../utils/createResource.js'; +import type { SignalStore } from '../utils/SignalStore.js'; + +export type SurfaceId = string; + +export type ComponentInstance = v0_9.AnyComponent & { + /** + * Absolute data context path for this component when created via a template. + * Used for resolving relative bindings inside the component tree. + */ + dataContextPath?: string; + /** + * Internal metadata for templated containers so we can re-expand on + * data model updates. + */ + __template?: { + componentId: v0_9.ComponentId; + path: string; + }; +}; + +export interface Surface { + surfaceId: SurfaceId; + catalogId?: string; + theme?: Readonly>; + sendDataModel?: boolean | undefined; + /** id of the root component for this surface (must be 'root'). */ + rootComponentId?: string | null; + components: Map; + resources: Map; + store: SignalStore; +} + +export interface ResourceInfo { + /** + * Internal event type emitted by the processor. + */ + type: 'beginRendering' | 'surfaceUpdate' | 'deleteSurface'; + surfaceId: string; + surface: Surface; + component?: ComponentInstance; +} + +export type Resource = GenericResource; + +export type ServerToClientMessage = v0_9.A2uiMessage & { + /** + * Message id injected by the client (SSE messageId / task id). + */ + messageId?: string; +}; + +export interface UserActionPayload { + name: string; + surfaceId: string; + sourceComponentId: string; + timestamp: string; // ISO 8601 + context: Record; +} + +export type A2UIClientEventMessage = + | string + | { + text?: string; + sessionId?: string; + } + | { + userAction: UserActionPayload; + sessionId?: string; + }; + +export interface GenericComponentProps { + id?: string; + surface: Surface; + setValue?: (key: string, value: unknown) => void; + sendAction?: (action: Record) => void; + dataContextPath?: string; + [key: string]: unknown; +} diff --git a/packages/genui/a2ui/src/core/useAction.ts b/packages/genui/a2ui/src/core/useAction.ts new file mode 100644 index 0000000000..cd318e301b --- /dev/null +++ b/packages/genui/a2ui/src/core/useAction.ts @@ -0,0 +1,153 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type * as v0_9 from '@a2ui/web_core/v0_9'; + +import { processor } from './processor.js'; +import type { UserActionPayload } from './types.js'; + +export interface ActionProps { + id: string; + surfaceId: string; + dataContext?: string | undefined; +} + +function isDataBinding(value: unknown): value is v0_9.DataBinding { + return !!value && typeof value === 'object' + && 'path' in (value as Record); +} + +function isFunctionCall(value: unknown): value is v0_9.FunctionCall { + return !!value && typeof value === 'object' + && 'call' in (value as Record); +} + +function resolveFromStore( + path: string, + surfaceId: string, + dataContextPath?: string, +): unknown { + const surface = processor.getOrCreateSurface(surfaceId); + const store = surface.store; + const resolvedPath = processor.resolvePath(path, dataContextPath); + const signal = store.getSignal(resolvedPath); + const raw = signal.value; + if (!raw) return raw; + try { + return JSON.parse(raw as string); + } catch { + return raw; + } +} + +function resolveDynamicValue( + value: v0_9.DynamicValue, + surfaceId: string, + dataContextPath?: string, +): unknown { + if ( + typeof value === 'string' || typeof value === 'number' + || typeof value === 'boolean' + ) { + return value; + } + + if (Array.isArray(value)) { + return value.map((v: unknown) => v); + } + + if (isDataBinding(value)) { + return resolveFromStore(value.path, surfaceId, dataContextPath); + } + + if (isFunctionCall(value)) { + return resolveFunctionCall(value, surfaceId, dataContextPath); + } + + return value; +} + +function resolveFunctionArguments( + args: Record | undefined, + surfaceId: string, + dataContextPath?: string, +): Record | undefined { + if (!args) return undefined; + const resolved: Record = {}; + for (const [key, val] of Object.entries(args)) { + if (isObject(val) && !isDataBinding(val) && !isFunctionCall(val)) { + // Literal object configuration; do a shallow copy. + resolved[key] = { ...val }; + } else { + resolved[key] = resolveDynamicValue( + val as v0_9.DynamicValue, + surfaceId, + dataContextPath, + ); + } + } + return resolved; +} + +function resolveFunctionCall( + fn: v0_9.FunctionCall, + surfaceId: string, + dataContextPath?: string, +): Record { + return { + call: fn.call, + args: resolveFunctionArguments(fn.args, surfaceId, dataContextPath), + returnType: fn.returnType, + }; +} + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function useAction( + props: ActionProps, +): { sendAction: (action: v0_9.Action) => Promise } { + const { id, surfaceId, dataContext } = props; + + const sendAction = (action: v0_9.Action) => { + let name = 'unknownAction'; + let context: Record = {}; + + if ('event' in action && action.event) { + name = action.event.name; + const ctx = action.event.context as Record | undefined; + if (ctx) { + const resolvedContext: Record = {}; + for (const [key, value] of Object.entries(ctx)) { + resolvedContext[key] = resolveDynamicValue( + value as v0_9.DynamicValue, + surfaceId, + dataContext, + ); + } + context = resolvedContext; + } + } else if ('functionCall' in action && action.functionCall) { + const fn = action.functionCall; + name = fn.call; + context = { + functionCall: resolveFunctionCall(fn, surfaceId, dataContext), + }; + } + + const userAction: UserActionPayload = { + name, + surfaceId, + sourceComponentId: id, + timestamp: new Date().toISOString(), + context, + }; + + return processor.dispatch({ userAction }); + }; + + return { + sendAction, + }; +} diff --git a/packages/genui/a2ui/src/core/useDataBinding.ts b/packages/genui/a2ui/src/core/useDataBinding.ts new file mode 100644 index 0000000000..52d96c4415 --- /dev/null +++ b/packages/genui/a2ui/src/core/useDataBinding.ts @@ -0,0 +1,155 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { effect } from '@preact/signals'; + +import { useEffect, useState } from '@lynx-js/react'; + +import type { Surface } from './types.js'; + +export function useDataBinding( + dynamicValue: unknown, + surface: Surface | undefined, + dataContextPath?: string, + fallbackValue?: T, +): [T | undefined, (newValue: T) => void, string | undefined] { + let path: string | undefined; + let initialValue: string | undefined; + + if (typeof dynamicValue === 'string') { + initialValue = dynamicValue; + } else if ( + dynamicValue + && typeof dynamicValue === 'object' + && 'path' in dynamicValue + ) { + path = (dynamicValue as Record)['path'] as + | string + | undefined; + } else if ( + typeof dynamicValue === 'number' || typeof dynamicValue === 'boolean' + ) { + initialValue = String(dynamicValue); + } + + if (path && !path.startsWith('/')) { + if (dataContextPath) { + path = `${dataContextPath}/${path}`; + } else { + path = `/${path}`; + } + } + + const signal = surface?.store && path + ? surface.store.getSignal(path, initialValue) + : undefined; + + const [signalValue, setSignalValue] = useState(() => + signal?.value as T | undefined + ); + + useEffect(() => { + if (!signal) return; + const dispose = effect(() => { + setSignalValue(signal.value as T | undefined); + }); + return dispose; + }, [signal]); + + const currentValue = path + ? (signalValue ?? (initialValue as T | undefined)) + : (initialValue as T | undefined ?? fallbackValue); + + const setValue = (newValue: T) => { + if (path && surface?.store) { + surface.store.update(path, newValue); + } + }; + + return [currentValue, setValue, path]; +} + +function isDataBinding(prop: unknown): boolean { + // In 0.9, a binding is typically { path: string } + // Exclude template configurations like { path: string, componentId: string } + return Boolean( + prop + && typeof prop === 'object' + && 'path' in prop + && !('componentId' in prop), + ); +} + +function resolveProperties( + properties: Record, + surface: Surface | undefined, + dataContextPath?: string, +) { + if (!properties) return properties; + const result: Record = {}; + for (const key in properties) { + const prop = properties[key]; + if (isDataBinding(prop)) { + let path = (prop as Record)['path'] as + | string + | undefined; + if (path && typeof path === 'string' && !path.startsWith('/')) { + path = dataContextPath ? `${dataContextPath}/${path}` : `/${path}`; + } + + if (path && surface?.store) { + const signal = surface.store.getSignal(path); + result[key] = signal.value; + } else { + result[key] = undefined; + } + } else if ( + typeof prop === 'string' + || typeof prop === 'number' + || typeof prop === 'boolean' + ) { + result[key] = prop; + } else { + result[key] = prop; + } + } + return result; +} + +export function useResolvedProps( + properties: Record, + surface: Surface | undefined, + dataContextPath?: string, +): readonly [Record, (key: string, value: unknown) => void] { + const [resolved, setResolved] = useState(() => + resolveProperties(properties, surface, dataContextPath) + ); + + useEffect(() => { + if (!surface?.store) return; + const dispose = effect(() => { + setResolved(resolveProperties(properties, surface, dataContextPath)); + }); + return dispose; + }, [properties, surface, dataContextPath]); + + const setValue = (key: string, value: unknown) => { + const prop = properties?.[key]; + if (isDataBinding(prop)) { + let path = (prop as Record)['path'] as + | string + | undefined; + if (path && surface?.store) { + if (!path.startsWith('/')) { + path = dataContextPath ? `${dataContextPath}/${path}` : `/${path}`; + } + surface.store.update(path, value); + } + } + }; + + return [resolved, setValue] as readonly [ + Record, + (key: string, value: unknown) => void, + ]; +} diff --git a/packages/genui/a2ui/src/utils/ComponentRegistry.ts b/packages/genui/a2ui/src/utils/ComponentRegistry.ts new file mode 100644 index 0000000000..b16640d543 --- /dev/null +++ b/packages/genui/a2ui/src/utils/ComponentRegistry.ts @@ -0,0 +1,18 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export class ComponentRegistry { + private components = new Map(); + + register(tag: string, component: T) { + this.components.set(tag, component); + } + + get(tag: string): T | undefined { + return this.components.get(tag); + } + + has(tag: string): boolean { + return this.components.has(tag); + } +} diff --git a/packages/genui/a2ui/src/utils/SignalStore.ts b/packages/genui/a2ui/src/utils/SignalStore.ts new file mode 100644 index 0000000000..ffd270271d --- /dev/null +++ b/packages/genui/a2ui/src/utils/SignalStore.ts @@ -0,0 +1,33 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { batch, signal } from '@preact/signals'; +import type { Signal } from '@preact/signals'; + +export class SignalStore { + private signals = new Map>(); + + getSignal(path: string, initialValue?: unknown): Signal { + let s = this.signals.get(path); + if (!s) { + s = signal(initialValue); + this.signals.set(path, s); + } + return s; + } + + update(path: string, value: unknown): void { + const s = this.getSignal(path); + if (s.value !== value) { + s.value = value; + } + } + + updateBatch(updates: { path: string; value: unknown }[]): void { + batch(() => { + for (const { path, value } of updates) { + this.update(path, value); + } + }); + } +} diff --git a/packages/genui/a2ui/src/utils/createResource.ts b/packages/genui/a2ui/src/utils/createResource.ts new file mode 100644 index 0000000000..c0f8d0eec3 --- /dev/null +++ b/packages/genui/a2ui/src/utils/createResource.ts @@ -0,0 +1,68 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export interface Resource { + id: string; + readonly completed: boolean; + read: () => T; + complete: (result: T) => void; + onUpdate: (callback: (result: T) => void) => () => void; + promise: Promise; +} + +export function createResource(id: string): Resource { + let resolve: (value: T) => void; + const mockFn = new Promise((_resolve) => { + resolve = _resolve; + }); + let status: 'pending' | 'success' | 'error' = 'pending'; + let result: T; + let error: unknown; + let listeners: ((data: T) => void)[] = []; + + const promise = mockFn + .then((res) => { + status = 'success'; + result = res; + return res; + }) + .catch((err) => { + status = 'error'; + error = err; + throw err; + }); + + return { + id, + promise, + get completed() { + return status === 'success'; + }, + read: () => { + switch (status) { + case 'pending': + throw promise; + case 'error': + throw error; + case 'success': + return result; + default: + throw new Error('Unknown status'); + } + }, + complete: (res: T) => { + if (status === 'pending') { + resolve(res); + } else { + result = res; + listeners.forEach((fn) => fn(res)); + } + }, + onUpdate: (fn: (data: T) => void) => { + listeners.push(fn); + return () => { + listeners = listeners.filter((l) => l !== fn); + }; + }, + }; +} diff --git a/packages/genui/a2ui/src/utils/index.ts b/packages/genui/a2ui/src/utils/index.ts new file mode 100644 index 0000000000..18876cfe32 --- /dev/null +++ b/packages/genui/a2ui/src/utils/index.ts @@ -0,0 +1,6 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export * from './ComponentRegistry.js'; +export * from './createResource.js'; +export * from './SignalStore.js'; diff --git a/packages/genui/a2ui/tsconfig.json b/packages/genui/a2ui/tsconfig.json new file mode 100644 index 0000000000..ade6f5c77e --- /dev/null +++ b/packages/genui/a2ui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "isolatedDeclarations": false, + "composite": true, + "outDir": "./dist", + "target": "esnext", + "jsx": "preserve", + "jsxImportSource": "@lynx-js/react", + "rootDir": "./src", + }, + "include": ["src"], + "references": [], +} diff --git a/packages/genui/tsconfig.json b/packages/genui/tsconfig.json new file mode 100644 index 0000000000..f0cfbb28a8 --- /dev/null +++ b/packages/genui/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "target": "ESNext", + "module": "ESNext", + "lib": ["DOM", "ESNext"], + "moduleResolution": "Bundler", + "esModuleInterop": false, + }, + "references": [ + /** packages-start */ + { "path": "./a2ui/tsconfig.json" }, + /** packages-end */ + ], + "include": [], +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20ec284727..ae6ccabd4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -472,6 +472,31 @@ importers: packages/background-only: {} + packages/genui/a2ui: + dependencies: + '@a2ui/web_core': + specifier: 0.9.1 + version: 0.9.1 + '@preact/signals': + specifier: ^2.5.1 + version: 2.5.1(preact@10.29.1) + devDependencies: + '@lynx-js/lynx-ui': + specifier: ^3.130.0 + version: 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-input': + specifier: ^3.130.0 + version: 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': + specifier: workspace:* + version: link:../../react + '@lynx-js/types': + specifier: 3.7.0 + version: 3.7.0 + '@types/react': + specifier: ^18.3.28 + version: 18.3.28 + packages/lynx/benchx_cli: dependencies: zx: @@ -1501,7 +1526,7 @@ importers: dependencies: dompurify: specifier: ^3.3.1 - version: 3.4.0 + version: 3.3.1 markdown-it: specifier: ^14.1.0 version: 14.1.0 @@ -2032,6 +2057,9 @@ importers: packages: + '@a2ui/web_core@0.9.1': + resolution: {integrity: sha512-qKFKOgdtPqe0TdiEreTlyqw3TuGjxb7NzVuYejWFUVqy1NQNwe7Hcd4zz8EP+MzWXHSo2sjWsGjuvWBWlVcuOQ==} + '@acemir/cssom@0.9.30': resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} @@ -3238,15 +3266,182 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + '@lynx-js/gesture-runtime@2.1.1': + resolution: {integrity: sha512-zBDOHakuePjw5wPCButExNgcllyFPGqYzqP2CWCWcQaX4gqJmCmYeRKguihyxH6ma6ExxlH/J2x5CHD91ed3bQ==} + peerDependencies: + '@lynx-js/react': '*' + '@lynx-js/types': '*' + '@lynx-js/internal-preact@10.28.4-2d1d67a': resolution: {integrity: sha512-CA8rFURvLvksg155ONHZHEa0ZIxWnS4H/UP8vp5PF8aNOFd3D3Z+Lq+0IQP5GyE/9x+jMIvbBXaY3JVVknSphQ==} '@lynx-js/lynx-core@0.1.3': resolution: {integrity: sha512-uWzKKYJUK4Q09ZRZxWSAFINnmZb9piWPbvWF9SkLn+3snBl9u/BJa4ekPRcKWAhBmpbtxWH1x27fxe3Q3p5l3Q==} + '@lynx-js/lynx-ui-button@3.130.0': + resolution: {integrity: sha512-0f13aZROKhTxMEY69rtWlkfvd6NM3A7JwloFQsh2nxcOiABp6dddvzzQJSayt2xAUxRjFl4uh5Rj/ZrQjjCrNw==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-checkbox@3.130.0': + resolution: {integrity: sha512-2xgq7oyGjq7CKYiHd/9nO+gzT2T3KSi4gb3EPxqrENDiiDv5im3ePTwzazUuHaWWK+7cHuJgxZIhwQU0Rk6l8w==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-common@3.130.0': + resolution: {integrity: sha512-6odzHU1z1PomtmskJUxIpj+TkHq9daOhUb7N0a3LquKxz9wp41qM1r2oDLRHdeUlylfId1F87Bto6F7Ql9+37w==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-dialog@3.130.0': + resolution: {integrity: sha512-AFkA7V+xI5EdaA2JShIcK3UAMG1RuaZcWl16f66KNRD1/ZgIQNp7sYOW/bnQmH+BXts7p4ut7NWBNNe5WcSVvQ==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-draggable@3.130.0': + resolution: {integrity: sha512-c9Sw0xahJqttWq/M9SmCIc3X0HIS/d47OBk5oNQRHjhJEaGKP23u/22SfCANXboxLhCxCXXv1OVmW/x8O9Wm2A==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-feed-list@3.130.0': + resolution: {integrity: sha512-5nUNYzRjSiR94N0LAyZ+b8aj5s6WYJGShohL2piKxw5lDI7PafaYt/i30aj0Q52b7ViB8ZZuD/ZtmbD4pKeRLA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-form@3.130.0': + resolution: {integrity: sha512-hiDetKqgAGSGcuxIaRG/ljNfZrQjh31BtWZDw0aXJZL/ifHqwv2NQHErAPp9mgO8i/5NYYNYQ5Sgs/ophKhUMA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-input@3.130.0': + resolution: {integrity: sha512-CPBbnDARhs5wfX//78+ATXYhGhU/R4SjFTL/Yzvur3Wh+lLFQz1mEvv0BOfUFnd/lEilc9Y9q00h6g7w7WHd3A==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-lazy-component@3.130.0': + resolution: {integrity: sha512-Xr4jQBM8mB3VtSxKnxxy0VdAT1M0dE5YWx5mEkpTezGVIX5rU+Ub23VFTtSZeGYWX1EFaleTOR17fsD4+C3CfA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-list@3.130.0': + resolution: {integrity: sha512-MBj7S19iJ3Tnu4qTVFFXMjceQ6pS+Yj84xRqoXdgVO/kvcjTzxJxQJkUt4RvCuvmQ+fOLz3Lt0ePGxVxkOYY+g==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-overlay@3.130.0': + resolution: {integrity: sha512-rc11LA4svUdfX7ZG7pjS+7JzoXllmvv6Ng91QwkYbPIuRlcHu/d0VHtd8cPTgkoBevSWCACBIDoIEgONTfyuTA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-popover@3.130.0': + resolution: {integrity: sha512-oP37HtY/KPiDruepLnpaJQQD3WOFINIz3g432xo+wZ+ScAKtZxfQm2pDpeb9eyPejKv9KjNIFexYtoFVMvSFSg==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-presence@3.130.0': + resolution: {integrity: sha512-h5a1SNFZdmEcsEJ6NrFcWC/LqEmINDh+LE9+givCvA4IwkYBWvgBQHoe/0LESJV75TC1TlB3L0sULsBVxMvqXg==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-radio-group@3.130.0': + resolution: {integrity: sha512-vRylYRYH41BVFCEt2lsOhnZuTcLu5ACcJvOXpCgTItCDsQ/+g1GSzDl37IAwwI4MBUDPG30uwD0+s7JjYjgn0g==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-scroll-view@3.130.0': + resolution: {integrity: sha512-Q5iwceQKFZ4TTLI/h5ZGWhBLP880g4Kn1h4jOeSGWUaKFyEUdbEd1DQKZA673q6pftyLoU6OQ66ZUzyX7tlaqA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-sheet@3.130.0': + resolution: {integrity: sha512-OSTgaX5xEJX0cjo9uwwrKcJS8wFZjFr7+yDew7gHK3bv7/FbJUVkJjhyb/bTGMsU+oyUZMUcqTgs1kgiokbPiQ==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-sortable@3.130.0': + resolution: {integrity: sha512-gdFKT6WJOaXSyaej28jGHaLqMu3E/FGNOcqgTXdEJmFbOgpvCABRdZ1flfQMd5tmCV64HXcN6KYJ7X3BBG6uTw==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-swipe-action@3.130.0': + resolution: {integrity: sha512-usfmTKBxVRsHAQQ3XvJOqqR0XRKc9+Gu+QjQdq7sa85m1xeFUocin0E8rBPuUNh82RAKC8NVgSw/olKWN/hi1Q==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-swiper@3.130.0': + resolution: {integrity: sha512-g/kKFEO4hj+pshuJPpugiTqAjiLEr1kaVjTh8GbD40NFo8X+V9tZw92BO1pfVhoRqffqC6gI4mqk7qXqDcqvbA==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui-switch@3.130.0': + resolution: {integrity: sha512-AjbqyYAmFI902GrBhsaOsWQdUZjL5RdN8kA3zQfj6c5HzmYqfjWf6Q0HLJP5EoVKl97Inyl8hGlZjECGNEAqVw==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/lynx-ui@3.130.0': + resolution: {integrity: sha512-SgzadpXCECf0dYKmrFytU7C+ihxO//z7pOirNMZppe0MOhWrv4Ld0jBt/yWfSv2rBjBRFIyo2xCxn/PM7yq0dw==} + peerDependencies: + '@lynx-js/react': '>=0.100.0' + '@lynx-js/types': '*' + '@types/react': ^18 + + '@lynx-js/motion@0.0.2': + resolution: {integrity: sha512-/oA8yBnWlGsIRxI3FGfdjxFOuKr5j3oLLmn5viW9JeuLRfIg2u+FM+UGiaAl1mZ9xfns1jamK+uax+cPwvW2dA==} + peerDependencies: + '@lynx-js/react': '*' + '@lynx-js/types': '*' + peerDependenciesMeta: + '@lynx-js/types': + optional: true + '@lynx-js/preact-devtools@5.0.1': resolution: {integrity: sha512-ayUU8PJ0TVWABbd5/d/04KI18W9RY4kkaRekBXXbRE/eBkGMZ3lVJ0zOB+B+85B9RUpvEVK2FleqJkS+qBKG6Q==} + '@lynx-js/react-use@0.0.7': + resolution: {integrity: sha512-L+snt3B4ti3sCPiMg1+0FQxr/KV1CadV0kTUD/Lj8VBhJ5gK5TiI0tRLSCGUrMZyNlzI9enXlk6C3GIquEo8Fw==} + peerDependencies: + '@lynx-js/react': '>=0.114.4' + '@lynx-js/tasm@0.0.33': resolution: {integrity: sha512-pquTgvCwrcP1VV8Fw/mRrvQDFyeA2Wu2KK3yI24wZYSua14g8AhRAeFpSs9NT6lDaktCnzc1mg+a2GzWoGrVWA==} hasBin: true @@ -3471,6 +3666,14 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@preact/signals-core@1.14.1': + resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==} + + '@preact/signals@2.5.1': + resolution: {integrity: sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==} + peerDependencies: + preact: '>= 10.25.0 || >=11.0.0-0' + '@prefresh/core@1.5.9': resolution: {integrity: sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==} peerDependencies: @@ -4605,6 +4808,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/js-cookie@2.2.7': + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + '@types/jsdom@21.1.7': resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} @@ -5054,6 +5260,9 @@ packages: engines: {node: '>=10.0.0'} deprecated: this version has critical issues, please update to the latest version + '@xobotyi/scrollbar-width@1.9.5': + resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -5709,6 +5918,9 @@ packages: peerDependencies: postcss: ^8.0.9 + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + css-loader@7.1.4: resolution: {integrity: sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==} engines: {node: '>= 18.12.0'} @@ -5749,6 +5961,10 @@ packages: css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -5832,6 +6048,9 @@ packages: dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -6013,8 +6232,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.4.0: - resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -6466,9 +6685,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-shallow-equal@1.0.0: + resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastest-stable-stringify@2.0.2: + resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -6602,6 +6827,20 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + framer-motion@12.23.12: + resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + framer-motion@12.34.1: resolution: {integrity: sha512-kcZyNaYQfvE2LlH6+AyOaJAQV4rGp5XbzfhsZpiSZcwDMfZUHhuxLWeyRzf5I7jip3qKRpuimPA9pXXfr111kQ==} peerDependencies: @@ -7025,6 +7264,9 @@ packages: inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + inline-style-prefixer@7.0.1: + resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} + inquirer@8.2.6: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} @@ -7367,6 +7609,9 @@ packages: jpeg-js@0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -7725,6 +7970,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -7983,6 +8231,12 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-css@5.6.2: + resolution: {integrity: sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==} + peerDependencies: + react: '*' + react-dom: '*' + nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -8752,6 +9006,18 @@ packages: '@types/react': optional: true + react-universal-interface@0.6.2: + resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} + peerDependencies: + react: '*' + tslib: '*' + + react-use@17.6.0: + resolution: {integrity: sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==} + peerDependencies: + react: '*' + react-dom: '*' + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -8879,6 +9145,9 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -8989,6 +9258,9 @@ packages: rslog@1.3.2: resolution: {integrity: sha512-1YyYXBvN0a2b1MSIDLwDTqqgjDzRKxUg/S/+KO6EAgbtZW1B3fdLHAMhEEtvk1patJYMqcRvlp3HQwnxj7AdGQ==} + rtl-css-js@1.16.1: + resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -9186,6 +9458,10 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} @@ -9255,6 +9531,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-harmonic-interval@1.0.1: + resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} + engines: {node: '>=6.9'} + set-proto@1.0.0: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} @@ -9364,6 +9644,10 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -9400,6 +9684,9 @@ packages: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} + stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -9410,6 +9697,12 @@ packages: stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + + stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -9527,6 +9820,9 @@ packages: peerDependencies: postcss: ^8.4.32 + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -9639,6 +9935,10 @@ packages: peerDependencies: tslib: ^2 + throttle-debounce@3.0.1: + resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} + engines: {node: '>=10'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -9772,6 +10072,9 @@ packages: peerDependencies: typescript: '>=4.0.0' + ts-easing@0.2.0: + resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -10354,6 +10657,13 @@ packages: snapshots: + '@a2ui/web_core@0.9.1': + dependencies: + '@preact/signals-core': 1.14.1 + date-fns: 4.1.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + '@acemir/cssom@0.9.30': {} '@adobe/css-tools@4.4.2': {} @@ -11572,15 +11882,303 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} + '@lynx-js/gesture-runtime@2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)': + dependencies: + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@lynx-js/internal-preact@10.28.4-2d1d67a': {} '@lynx-js/lynx-core@0.1.3': {} + '@lynx-js/lynx-ui-button@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-checkbox@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-common@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/gesture-runtime': 2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0) + '@lynx-js/react': link:packages/react + '@lynx-js/react-use': 0.0.7(@lynx-js/react@packages+react)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-dialog@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-overlay': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-presence': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-draggable@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/react-use': 0.0.7(@lynx-js/react@packages+react)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-feed-list@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/gesture-runtime': 2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-list': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-form@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-checkbox': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-input': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-radio-group': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-switch': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-input@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-scroll-view': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-lazy-component@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-list@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/gesture-runtime': 2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-overlay@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-popover@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-overlay': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-presence': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-presence@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-radio-group@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-scroll-view@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/gesture-runtime': 2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-lazy-component': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-sheet@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-dialog': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-overlay': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-presence': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/motion': 0.0.2(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react + - react-dom + + '@lynx-js/lynx-ui-sortable@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-draggable': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-swipe-action@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/gesture-runtime': 2.1.1(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-swiper@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/react': link:packages/react + '@lynx-js/react-use': 0.0.7(@lynx-js/react@packages+react)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - react + - react-dom + + '@lynx-js/lynx-ui-switch@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)': + dependencies: + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + clsx: 2.1.1 + + '@lynx-js/lynx-ui@3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/lynx-ui-button': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-checkbox': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-common': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-dialog': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-draggable': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-feed-list': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-form': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-input': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-lazy-component': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-list': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-popover': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-presence': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-radio-group': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-scroll-view': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-sheet': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-sortable': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-swipe-action': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-swiper': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lynx-js/lynx-ui-switch': 3.130.0(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(@types/react@18.3.28) + '@lynx-js/react': link:packages/react + '@lynx-js/types': 3.7.0 + '@types/react': 18.3.28 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react + - react-dom + + '@lynx-js/motion@0.0.2(@lynx-js/react@packages+react)(@lynx-js/types@3.7.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/react': link:packages/react + framer-motion: 12.23.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + motion-dom: 12.23.12 + motion-utils: 12.23.6 + optionalDependencies: + '@lynx-js/types': 3.7.0 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react + - react-dom + '@lynx-js/preact-devtools@5.0.1': dependencies: errorstacks: 2.4.1 htm: 3.1.1 + '@lynx-js/react-use@0.0.7(@lynx-js/react@packages+react)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lynx-js/react': link:packages/react + react-use: 17.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + '@lynx-js/tasm@0.0.33': {} '@lynx-js/trace-processor@0.0.1': @@ -11873,6 +12471,13 @@ snapshots: '@polka/url@1.0.0-next.25': {} + '@preact/signals-core@1.14.1': {} + + '@preact/signals@2.5.1(preact@10.29.1)': + dependencies: + '@preact/signals-core': 1.14.1 + preact: 10.29.1 + '@prefresh/core@1.5.9(preact@10.29.1)': dependencies: preact: 10.29.1 @@ -13181,6 +13786,8 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/js-cookie@2.2.7': {} + '@types/jsdom@21.1.7': dependencies: '@types/node': 24.10.13 @@ -13683,6 +14290,8 @@ snapshots: '@xmldom/xmldom@0.8.11': {} + '@xobotyi/scrollbar-width@1.9.5': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -14351,6 +14960,10 @@ snapshots: dependencies: postcss: 8.5.6 + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.1.0 + css-loader@7.1.4(@rspack/core@1.7.9(@swc/helpers@0.5.21))(webpack@5.105.2(@swc/core@1.15.24(@swc/helpers@0.5.21))): dependencies: icss-utils: 5.1.0(postcss@8.5.6) @@ -14399,6 +15012,11 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 @@ -14510,6 +15128,8 @@ snapshots: dataloader@1.4.0: {} + date-fns@4.1.0: {} + debounce@1.2.1: {} debug@2.6.9: @@ -14640,7 +15260,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.4.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -15330,8 +15950,12 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-shallow-equal@1.0.0: {} + fast-uri@3.1.0: {} + fastest-stable-stringify@2.0.2: {} + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -15467,6 +16091,15 @@ snapshots: forwarded@0.2.0: {} + framer-motion@12.23.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + motion-dom: 12.23.12 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + framer-motion@12.34.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: motion-dom: 12.34.1 @@ -15964,6 +16597,10 @@ snapshots: inline-style-parser@0.2.4: {} + inline-style-prefixer@7.0.1: + dependencies: + css-in-js-utils: 3.1.0 + inquirer@8.2.6: dependencies: ansi-escapes: 4.3.2 @@ -16361,6 +16998,8 @@ snapshots: jpeg-js@0.4.4: {} + js-cookie@2.2.1: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -16841,6 +17480,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.0.14: {} + mdn-data@2.0.28: {} mdn-data@2.12.2: {} @@ -17244,6 +17885,19 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nano-css@5.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + css-tree: 1.1.3 + csstype: 3.2.3 + fastest-stable-stringify: 2.0.2 + inline-style-prefixer: 7.0.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + rtl-css-js: 1.16.1 + stacktrace-js: 2.0.2 + stylis: 4.3.6 + nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -17961,6 +18615,30 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-universal-interface@0.6.2(react@19.2.4)(tslib@2.8.1): + dependencies: + react: 19.2.4 + tslib: 2.8.1 + + react-use@17.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@types/js-cookie': 2.2.7 + '@xobotyi/scrollbar-width': 1.9.5 + copy-to-clipboard: 3.3.3 + fast-deep-equal: 3.1.3 + fast-shallow-equal: 1.0.0 + js-cookie: 2.2.1 + nano-css: 5.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-universal-interface: 0.6.2(react@19.2.4)(tslib@2.8.1) + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + set-harmonic-interval: 1.0.1 + throttle-debounce: 3.0.1 + ts-easing: 0.2.0 + tslib: 2.8.1 + react@19.2.4: {} read-cache@1.0.0: @@ -18158,6 +18836,8 @@ snapshots: requires-port@1.0.0: {} + resize-observer-polyfill@1.5.1: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -18281,6 +18961,10 @@ snapshots: rslog@1.3.2: {} + rtl-css-js@1.16.1: + dependencies: + '@babel/runtime': 7.25.4 + run-applescript@7.0.0: {} run-async@2.4.1: {} @@ -18442,6 +19126,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + screenfull@5.2.0: {} + scroll-into-view-if-needed@3.1.0: dependencies: compute-scroll-into-view: 3.1.1 @@ -18560,6 +19246,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-harmonic-interval@1.0.1: {} + set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 @@ -18709,6 +19397,8 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map@0.5.6: {} + source-map@0.6.1: {} source-map@0.7.6: {} @@ -18756,6 +19446,10 @@ snapshots: stable-hash-x@0.2.0: {} + stack-generator@2.0.10: + dependencies: + stackframe: 1.3.4 + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -18764,6 +19458,17 @@ snapshots: stackframe@1.3.4: {} + stacktrace-gps@3.1.2: + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + + stacktrace-js@2.0.2: + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + statuses@1.5.0: {} statuses@2.0.2: {} @@ -18888,6 +19593,8 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 7.1.1 + stylis@4.3.6: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.12 @@ -19039,6 +19746,8 @@ snapshots: dependencies: tslib: 2.8.1 + throttle-debounce@3.0.1: {} + through@2.3.8: {} thunky@1.1.0: {} @@ -19150,6 +19859,8 @@ snapshots: picomatch: 4.0.3 typescript: 5.9.3 + ts-easing@0.2.0: {} + ts-interface-checker@0.1.13: {} ts-patch@3.3.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 03b9dd5ef4..b8dbf3c626 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,7 @@ packages: - benchmark/react - examples/* - packages/background-only + - packages/genui/* - packages/lynx/* - packages/mcp-servers/* - packages/repl diff --git a/tsconfig.json b/tsconfig.json index b9b1f9373d..8a12cf8132 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -130,7 +130,10 @@ }, { "path": "./packages/tailwind-preset/tsconfig.build.json" - } + }, + { + "path": "./packages/genui" + }, ], "include": [] }