Loading...
Loading...
Develop custom native UI libraries based on Flutter widgets for WebF. Create reusable component libraries that wrap Flutter widgets as web-accessible custom elements. Use when building UI libraries, wrapping Flutter packages, or creating native component systems.
npx skill4agent add openwebf/webf webf-native-ui-devwebf-native-ui┌─────────────────────────────────────────┐
│ JavaScript/TypeScript (React/Vue) │ ← Generated by CLI
│ @openwebf/my-component-lib │
├─────────────────────────────────────────┤
│ TypeScript Definitions (.d.ts) │ ← You write this
│ Component interfaces and events │
├─────────────────────────────────────────┤
│ Dart (Flutter) │ ← You write this
│ Flutter widget wrappers │
│ my_component_lib package │
└─────────────────────────────────────────┘# 1. Create Flutter package with Dart wrappers
# 2. Write TypeScript definition files
# 3. Generate React/Vue components with WebF CLI
# 4. Test and publish
webf codegen my-ui-lib --flutter-package-src=./flutter_package# Create Flutter package
flutter create --template=package my_component_lib
cd my_component_libmy_component_lib/
├── lib/
│ ├── my_component_lib.dart # Main export file
│ └── src/
│ ├── button.dart # Dart widget wrapper
│ ├── button.d.ts # TypeScript definitions
│ ├── input.dart
│ └── input.d.ts
├── pubspec.yaml
└── README.mddependencies:
flutter:
sdk: flutter
webf: ^0.24.0 # Latest WebF versionimport 'package:flutter/widgets.dart';
import 'package:webf/webf.dart';
import 'button_bindings_generated.dart'; // Will be generated by CLI
/// Custom button component wrapping Flutter widgets
class MyCustomButton extends MyCustomButtonBindings {
MyCustomButton(super.context);
// Internal state
String _variant = 'filled';
bool _disabled = false;
// Property getters/setters (implement interface from bindings)
String get variant => _variant;
set variant(String value) {
_variant = value;
// Trigger rebuild when property changes
setState(() {});
}
bool get disabled => _disabled;
set disabled(bool value) {
_disabled = value;
setState(() {});
}
WebFWidgetElementState createState() {
return MyCustomButtonState(this);
}
}
class MyCustomButtonState extends WebFWidgetElementState {
MyCustomButtonState(super.widgetElement);
MyCustomButton get widgetElement => super.widgetElement as MyCustomButton;
Widget build(BuildContext context) {
return GestureDetector(
onTap: widgetElement.disabled ? null : () {
// Dispatch click event to JavaScript
widgetElement.dispatchEvent(Event('click'));
},
child: Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: _getBackgroundColor(),
borderRadius: BorderRadius.circular(8),
),
child: Text(
// Get text from child nodes
widgetElement.getTextContent() ?? 'Button',
style: TextStyle(
color: widgetElement.disabled ? Colors.grey : Colors.white,
),
),
),
);
}
Color _getBackgroundColor() {
if (widgetElement.disabled) return Colors.grey[400]!;
switch (widgetElement.variant) {
case 'filled':
return Colors.blue;
case 'outlined':
return Colors.transparent;
default:
return Colors.blue;
}
}
}.d.ts/**
* Custom button component with multiple variants.
*/
/**
* Properties for <my-custom-button>.
*/
interface MyCustomButtonProperties {
/**
* Button variant style.
* Supported values: 'filled' | 'outlined' | 'text'
* @default 'filled'
*/
variant?: string;
/**
* Whether the button is disabled.
* @default false
*/
disabled?: boolean;
}
/**
* Events for <my-custom-button>.
*/
interface MyCustomButtonEvents {
/**
* Fired when the button is clicked.
*/
click: Event;
}PropertiesEvents?CustomEvent<T>library my_component_lib;
import 'package:webf/webf.dart';
import 'src/button.dart';
export 'src/button.dart';
/// Install all components in this library
void installMyComponentLib() {
// Register custom elements
WebFController.defineCustomElement(
'my-custom-button',
(context) => MyCustomButton(context),
);
// Add more components here
// WebFController.defineCustomElement('my-custom-input', ...);
}# Install WebF CLI globally (if not already installed)
npm install -g @openwebf/webf-cli
# Generate TypeScript bindings and React/Vue components
webf codegen my-ui-lib-react \
--flutter-package-src=./my_component_lib \
--framework=react
webf codegen my-ui-lib-vue \
--flutter-package-src=./my_component_lib \
--framework=vue.d.ts*_bindings_generated.dart.d.tspackage.jsonnpm run buildmy-ui-lib-react/
├── src/
│ ├── MyCustomButton.tsx # React component
│ └── index.ts # Main export
├── dist/ # Built files (after npm run build)
├── package.json
├── tsconfig.json
└── README.mdimport 'package:my_component_lib/my_component_lib.dart';
void main() {
WebFControllerManager.instance.initialize(WebFControllerManagerConfig(
maxAliveInstances: 2,
maxAttachedInstances: 1,
));
// Install your component library
installMyComponentLib();
runApp(MyApp());
}import { MyCustomButton } from '@openwebf/my-ui-lib-react';
function App() {
return (
<div>
<MyCustomButton
variant="filled"
onClick={() => console.log('Clicked!')}
>
Click Me
</MyCustomButton>
</div>
);
}<template>
<div>
<MyCustomButton
variant="filled"
@click="handleClick"
>
Click Me
</MyCustomButton>
</div>
</template>
<script setup>
import { MyCustomButton } from '@openwebf/my-ui-lib-vue';
const handleClick = () => {
console.log('Clicked!');
};
</script># In Flutter package directory
flutter pub publish
# Or for private packages
flutter pub publish --server=https://your-private-registry.com# Automatic publishing with CLI
webf codegen my-ui-lib-react \
--flutter-package-src=./my_component_lib \
--framework=react \
--publish-to-npm
# Or manual publishing
cd my-ui-lib-react
npm publishwebf codegen my-ui-lib-react \
--flutter-package-src=./my_component_lib \
--framework=react \
--publish-to-npm \
--npm-registry=https://registry.your-company.com/interface MyComplexWidgetProperties {
// JSON string properties for complex data
items?: string; // Will be JSON.parse() in Dart
// Enum-like values
alignment?: 'left' | 'center' | 'right';
// Numeric properties
maxLength?: number;
opacity?: number;
}
set items(String? value) {
if (value != null) {
try {
final List<dynamic> parsed = jsonDecode(value);
_items = parsed.cast<Map<String, dynamic>>();
setState(() {});
} catch (e) {
print('Error parsing items: $e');
}
}
}void _handleValueChange(String newValue) {
// Dispatch CustomEvent with data
widgetElement.dispatchEvent(CustomEvent(
'change',
detail: {'value': newValue},
));
}interface MyInputEvents {
change: CustomEvent<{value: string}>;
}interface MyInputProperties {
// Regular properties
value?: string;
// Methods
focus(): void;
clear(): void;
}class MyInput extends MyInputBindings {
final FocusNode _focusNode = FocusNode();
void focus() {
_focusNode.requestFocus();
}
void clear() {
// Clear the input
value = '';
// Dispatch event
dispatchEvent(Event('input'));
}
}
Widget build(BuildContext context) {
// Read CSS properties
final renderStyle = widgetElement.renderStyle;
final backgroundColor = renderStyle.backgroundColor?.value;
final borderRadius = renderStyle.borderRadius;
return Container(
decoration: BoxDecoration(
color: backgroundColor ?? Colors.blue,
borderRadius: BorderRadius.circular(
borderRadius?.topLeft?.x ?? 8.0
),
),
child: buildChild(),
);
}
Widget build(BuildContext context) {
// Get text content from child nodes
final text = widgetElement.getTextContent() ?? '';
// Build child widgets
final children = widgetElement.children.map((child) {
return child.renderObject?.widget ?? SizedBox();
}).toList();
return Column(
children: children,
);
}
set variant(String value) {
const validVariants = ['filled', 'outlined', 'text'];
if (validVariants.contains(value)) {
_variant = value;
} else {
print('Warning: Invalid variant "$value"');
_variant = 'filled';
}
setState(() {});
}Timer? _debounceTimer;
set searchQuery(String value) {
_searchQuery = value;
// Debounce search
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 300), () {
_performSearch();
});
}
void didMount() {
super.didMount();
// Called when element is inserted into DOM
_initializeWidget();
}
void dispose() {
// Clean up resources
_debounceTimer?.cancel();
_focusNode.dispose();
super.dispose();
}
set jsonData(String? value) {
if (value == null || value.isEmpty) {
_data = null;
return;
}
try {
_data = jsonDecode(value);
setState(() {});
} catch (e) {
print('Error parsing JSON: $e');
// Dispatch error event
dispatchEvent(CustomEvent('error', detail: {'message': e.toString()}));
}
}# Generate React components
webf codegen output-dir --flutter-package-src=./my_package --framework=react
# Generate Vue components
webf codegen output-dir --flutter-package-src=./my_package --framework=vue
# Specify package name
webf codegen output-dir \
--flutter-package-src=./my_package \
--framework=react \
--package-name=@mycompany/my-ui-lib# Publish to npm after generation
webf codegen output-dir \
--flutter-package-src=./my_package \
--framework=react \
--publish-to-npm
# Publish to custom registry
webf codegen output-dir \
--flutter-package-src=./my_package \
--framework=react \
--publish-to-npm \
--npm-registry=https://registry.company.com/package.jsontsconfig.jsonglobal.d.tsError: Could not find 'button_bindings_generated.dart'.d.ts.dartPropertiesEventssetState()
set myProperty(String value) {
_myProperty = value;
setState(() {}); // ← Don't forget this!
}// Make sure event names match your TypeScript definitions
widgetElement.dispatchEvent(Event('click')); // matches 'click' in TypeScriptpackage.json{
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
}.md