Adds "Element Embedding" demo package. (#1596)

pull/1598/head
David Iglesias 2 years ago committed by GitHub
parent 294ea4ff8f
commit 8f1b3c3f9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,48 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# Keeping the repo
.metadata
pubspec.lock
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

@ -0,0 +1,25 @@
# element_embedding_demo
This package contains the application used to demonstrate the
upcoming Flutter web feature: "Element Embedding".
This was first shown on the Flutter Forward event in Nairobi (Kenya), by Tim
Sneath. [See the replay here](https://www.youtube.com/watch?v=zKQYGKAe5W8&t=5799s).
## Running the demo
The demo is a Flutter web app, so it can be run as:
```terminal
$ flutter run -d chrome
```
## Points of Interest
* Check the new JS Interop:
* Look at `lib/main.dart`, find the `@js.JSExport()` annotation.
* Find the JS code that interacts with Dart in `web/js/demo-js-interop.js`.
* See how the Flutter web application is embedded into the page now:
* Find `hostElement` in `web/index.html`.
_(Built by @ditman, @kevmoo and @malloc-error)_

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

@ -0,0 +1,335 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:js/js.dart' as js;
import 'package:js/js_util.dart' as js_util;
void main() {
runApp(const MyApp());
}
enum DemoScreen { counter, textField, custom }
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
@js.JSExport()
class _MyAppState extends State<MyApp> {
final _streamController = StreamController<void>.broadcast();
DemoScreen _currentDemoScreen = DemoScreen.counter;
int _counterScreenCount = 0;
@override
void initState() {
super.initState();
final export = js_util.createDartExport(this);
js_util.setProperty(js_util.globalThis, '_appState', export);
js_util.callMethod<void>(js_util.globalThis, '_stateSet', []);
}
@override
void dispose() {
_streamController.close();
super.dispose();
}
@js.JSExport()
void increment() {
if (_currentDemoScreen == DemoScreen.counter) {
setState(() {
_counterScreenCount++;
_streamController.add(null);
});
}
}
@js.JSExport()
void addHandler(void Function() handler) {
_streamController.stream.listen((event) {
handler();
});
}
@js.JSExport()
int get count => _counterScreenCount;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Element embedding',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: demoScreenRouter(_currentDemoScreen),
);
}
Widget demoScreenRouter(DemoScreen which) {
switch (which) {
case DemoScreen.counter:
return CounterDemo(
title: 'Counter',
numToDisplay: _counterScreenCount,
incrementHandler: increment,
);
case DemoScreen.textField:
return const TextFieldDemo(title: 'Note to Self');
case DemoScreen.custom:
return const CustomDemo(title: 'Character Counter');
}
}
@js.JSExport()
void changeDemoScreenTo(String screenString) {
setState(() {
switch (screenString) {
case 'counter':
_currentDemoScreen = DemoScreen.counter;
break;
case 'textField':
_currentDemoScreen = DemoScreen.textField;
break;
case 'custom':
_currentDemoScreen = DemoScreen.custom;
break;
default:
_currentDemoScreen = DemoScreen.counter;
break;
}
});
}
}
class CounterDemo extends StatefulWidget {
final String title;
final int numToDisplay;
final VoidCallback incrementHandler;
const CounterDemo({
super.key,
required this.title,
required this.numToDisplay,
required this.incrementHandler,
});
@override
State<CounterDemo> createState() => _CounterDemoState();
}
class _CounterDemoState extends State<CounterDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${widget.numToDisplay}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: widget.incrementHandler,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class TextFieldDemo extends StatelessWidget {
const TextFieldDemo({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: const Center(
child: Padding(
padding: EdgeInsets.all(14.0),
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: OutlineInputBorder(),
// hintText: 'Text goes here!',
),
),
),
),
);
}
}
class CustomDemo extends StatefulWidget {
final String title;
const CustomDemo({super.key, required this.title});
@override
State<CustomDemo> createState() => _CustomDemoState();
}
class _CustomDemoState extends State<CustomDemo> {
final double textFieldHeight = 80;
final Color colorPrimary = const Color(0xff027dfd);
// const Color(0xffd43324);
// const Color(0xff6200ee);
// const Color.fromARGB(255, 255, 82, 44);
final TextEditingController _textController = TextEditingController();
late FocusNode textFocusNode;
int totalCharCount = 0;
@override
void initState() {
super.initState();
textFocusNode = FocusNode();
textFocusNode.requestFocus();
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: MediaQuery.of(context).size.height - textFieldHeight,
flexibleSpace: Container(
color: colorPrimary,
height: MediaQuery.of(context).size.height - textFieldHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'COUNT WITH DASH!',
style: TextStyle(color: Colors.white, fontSize: 18),
),
const SizedBox(
height: 26,
),
Container(
width: 98,
height: 98,
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.white),
shape: BoxShape.circle,
),
child: Center(
child: Container(
width: 90,
height: 90,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/dash.png'),
fit: BoxFit.cover,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
const SizedBox(height: 20),
Text(
totalCharCount.toString(),
style: const TextStyle(color: Colors.white, fontSize: 52),
),
// const Text(
// 'characters typed',
// style: TextStyle(color: Colors.white, fontSize: 14),
// ),
],
),
),
),
body: Column(
children: [
SizedBox(
height: textFieldHeight,
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 18, right: 18),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
focusNode: textFocusNode,
onSubmitted: (value) {
textFocusNode.requestFocus();
},
onChanged: (value) {
handleChange();
},
maxLines: 1,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
),
const SizedBox(
width: 12,
),
Center(
child: Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: colorPrimary,
shape: BoxShape.circle,
),
child: IconButton(
icon: const Icon(Icons.refresh),
color: Colors.white,
onPressed: () {
handleClear();
},
),
),
),
],
),
),
),
),
],
),
);
}
void handleChange() {
setState(() {
totalCharCount = _textController.value.text.toString().length;
});
}
void handleClear() {
setState(() {
_textController.clear();
totalCharCount = 0;
});
textFocusNode.requestFocus();
}
}

@ -0,0 +1,23 @@
name: element_embedding_demo
description: A small app to be embedded into a HTML element (see web/index.html)
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0-0 <4.0.0'
dependencies:
cupertino_icons: ^1.0.2
flutter:
sdk: flutter
js: ^0.6.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
assets:
- assets/dash.png

@ -0,0 +1,260 @@
@font-face {
font-family: "DM Sans";
src: url(../fonts/DMSans-Regular.ttf);
font-weight: normal;
}
@font-face {
font-family: "DM Sans";
src: url(../fonts/DMSans-Bold.ttf);
font-weight: 700;
}
/** Reset */
* {
box-sizing: border-box;
font-family: "DM Sans", sans-serif;
}
html, body {
margin: 0;
padding: 0;
min-height: 100vh;
}
body {
background-color: #fff;
background-image: radial-gradient(
ellipse at bottom,
#fafafa 5%,
transparent 60%
),
linear-gradient(136deg, transparent, #eee 290%),
linear-gradient(115deg, #fafafa, transparent 40%),
linear-gradient(180deg, transparent 0, #ddd 70%),
radial-gradient(ellipse at -70% -180%, transparent 80%, #eee 0),
radial-gradient(ellipse at bottom, #71c7ee 40%, transparent 80%),
radial-gradient(ellipse at 5% 340%, transparent 80%, #ddd 0);
background-repeat: no-repeat;
color: #555;
}
/** Layout **/
body { display: flex; flex-direction: column; }
section.contents {
flex: 1 1 auto;
flex-direction: row;
display: flex;
}
section.contents aside {
flex: 0;
display: flex;
flex-direction: column;
order: -1;
}
section.contents aside fieldset {
display: flex;
flex-flow: wrap;
justify-content: space-between;
align-items: flex-end;
}
section.contents aside .align-top {
align-self: flex-start;
}
section.contents article {
flex: 1;
margin-top: 50px;
display: flex;
justify-content: center;
}
/** Title */
h1 {
font-weight: 700;
font-size: 48px;
padding: 0;
line-height: .9em;
letter-spacing: -2px;
margin: 0 0 30px 0;
}
/** Controls for the demo (left column) */
#demo_controls {
background: linear-gradient(90deg, rgba(255,255,255,1) 10%, rgba(255,255,255,0) 100%);
padding: 40px 20px 0px 20px;
z-index: 10;
}
#demo_controls fieldset {
padding: 0;
border: none;
width: 210px;
}
#demo_controls legend {
text-align: center;
font-size: 20px;
line-height: 40px;
margin-bottom: 3px;
}
#demo_controls select.screen {
display: block;
width: 120px;
padding: 4px;
text-align: center;
margin-bottom: 10px;
}
#demo_controls input {
display: block;
width: 100px;
margin: 0 0 10px 0;
text-align: center;
}
/** Keep controls that */
#demo_controls .tight input {
margin: 0px;
}
#demo_controls input[type="button"] {
line-height: 10px;
font-size: 14px;
border-radius: 15px;
border: 1px solid #aaa;
border-style: outset;
background-color: #fff;
height: 30px;
color: #555;
transition: all 100ms ease-in-out;
cursor: pointer;
}
#demo_controls input[type="button"]:hover {
/* .active:hover background-color: #96B6E3;*/
border-color: #1c68d4;
background-color: #1c68d4;
color: white;
}
#demo_controls input[type="button"].active {
border-color: #1c68d4;
background-color: #1c68d4;
color: white;
}
#demo_controls input#value {
font-size: 32px;
line-height: 1em;
min-height: 30px;
color: #888;
}
#demo_controls input#increment {
/* Center vertically next to taller input#value */
position: relative;
top: -6px;
}
#demo_controls .disabled {
pointer-events: none;
opacity: .5;
}
/** The style for the DIV where flutter will be rendered, and the CSS fx */
#flutter_target {
border: 1px solid #aaa;
width: 320px;
height: 480px;
border-radius: 0px;
transition: all 150ms ease-in;
}
#flutter_target.resize {
width: 480px;
height: 320px;
}
#flutter_target.spin { animation: spin 6400ms ease-in-out infinite; }
#flutter_target.shadow { position: relative; }
#flutter_target.shadow::before {
content: "";
position: absolute;
display: block;
width: 100%;
top: calc(100% - 1px);
left: 0;
height: 1px;
background-color: black;
border-radius: 50%;
z-index: -1;
transform: rotateX(80deg);
box-shadow: 0px 0px 60px 38px rgb(0 0 0 / 25%);
}
#flutter_target.mirror {
-webkit-box-reflect: below 0px linear-gradient(to bottom, rgba(0,0,0,0.0), rgba(0,0,0,0.4));
}
@keyframes spin {
0% {
transform: perspective(1000px) rotateY(0deg);
animation-timing-function: ease-in;
}
15% {
transform: perspective(1000px) rotateY(165deg);
animation-timing-function: linear;
}
75% {
transform: perspective(1000px) rotateY(195deg);
animation-timing-function: linear;
}
90% {
transform: perspective(1000px) rotateY(359deg);
animation-timing-function: ease-out;
}
100% {
transform: perspective(1000px) rotateY(359deg);
animation-timing-function: linear;
}
}
/** "Handheld"/Device mode container */
#handheld::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: url(../icons/unsplash-x9WGMWwp1NM.png) no-repeat;
background-size: 1000px;
background-position: top right;
opacity: 1;
transition: opacity 200ms ease-out;
}
#handheld::after {
content: "";
position: absolute;
display: block;
width: 77px;
height: 67px;
top: 534px;
right: 573px;
background: url(../icons/nail.png) no-repeat;
background-size: 77px;
opacity: 1;
transition: opacity 200ms ease-out;
}
#handheld.hidden::before,
#handheld.hidden::after {
opacity: 0;
}
#flutter_target.handheld {
position: absolute;
right: 0px;
transform-origin: 0px 0px 0px;
transform: rotate(-14.1deg) scale(0.80) translate(-539px, -45px);
width: 316px;
height: 678px;
border-radius: 34px;
border: 1px solid #000;
overflow: hidden;
}
.imageAttribution {
position: absolute;
bottom: 6px;
right: 6px;
font-size: 10px;
}
.imageAttribution, .imageAttribution a { color: #fff; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,93 @@
Copyright 2014-2017 Indian Type Foundry (info@indiantypefoundry.com). Copyright 2019 Google LLC.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<base href="/" />
<meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A Flutter Web Element embedding demo." />
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Flutter Element embedding" />
<link rel="apple-touch-icon" href="icons/Icon-192.png" />
<link rel="preload" as="image" href="icons/unsplash-x9WGMWwp1NM.png" />
<!-- Favicon -->
<link rel="icon" type="image/png" href="icons/favicon.png" />
<title>Element embedding</title>
<link rel="manifest" href="manifest.json" />
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<link rel="stylesheet" type="text/css" href="css/style.css" />
</head>
<body>
<section class="contents">
<article>
<div id="flutter_target"></div>
</article>
<aside id="demo_controls">
<h1>Element embedding</h1>
<fieldset id="fx">
<legend>Effects</legend>
<input value="Shadow" data-fx="shadow" type="button" class="fx" />
<input value="Mirror 🧪" data-fx="mirror" type="button" class="fx" />
<input value="Resize" data-fx="resize" type="button" class="fx align-top" />
<div class="tight">
<input value="Spin" data-fx="spin" type="button" class="fx" />
<input type="range" value="0" min="-180" max="180" list="markers" id="rotation" class="tight" />
<datalist id="markers">
<option value="-180"></option>
<option value="-135"></option>
<option value="-45"></option>
<option value="0"></option>
<option value="45"></option>
<option value="135"></option>
<option value="180"></option>
</datalist>
</div>
<input value="Device" data-fx="handheld" type="button" class="fx" />
</fieldset>
<fieldset id="interop">
<legend>JS Interop</legend>
<label for="screen-selector">
Screen
<select name="screen-select" id="screen-selector" class="screen">
<option value="counter">Counter</option>
<option value="textField">TextField</option>
<option value="custom">Custom App</option>
</select>
</label>
<label for="value">
Value
<input id="value" value="" type="text" readonly />
</label>
<input
id="increment"
value="Increment"
type="button"
/>
</fieldset>
</aside>
</section>
<script>
window.addEventListener("load", function (ev) {
// Embed flutter into div#flutter_target
let target = document.querySelector("#flutter_target");
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function (engineInitializer) {
let appRunner = await engineInitializer.initializeEngine({
hostElement: target,
});
await appRunner.runApp();
},
});
});
</script>
<script src="js/demo-js-interop.js" defer></script>
<script src="js/demo-css-fx.js" defer></script>
</body>
</html>

@ -0,0 +1,82 @@
// Manages toggling the VFX of the buttons
(function () {
"use strict";
let handheld;
let fxButtons = document.querySelector("#fx");
let flutterTarget = document.querySelector("#flutter_target");
let attribution = document.createElement("span");
attribution.className = "imageAttribution";
attribution.innerHTML = "Photo by <a href='https://unsplash.com/photos/x9WGMWwp1NM' rel='noopener noreferrer' target='_blank'>Nathana Rebouças</a> on Unsplash";
// (Re)moves the flutterTarget inside a div#handheld.
function handleHandHeld(fx) {
resetRotation();
if (!handheld) {
handheld = document.createElement("div");
handheld.id = "handheld";
handheld.classList.add("hidden");
// Inject before the flutterTarget
flutterTarget.parentNode.insertBefore(handheld, flutterTarget);
handheld.append(flutterTarget);
handheld.append(attribution);
window.setTimeout(function () {
handheld.classList.remove("hidden");
}, 100);
// Disable all effects on the flutter container
flutterTarget.className = "";
setOtherFxEnabled(false);
} else {
handheld.classList.add("hidden");
window.setTimeout(function () {
handheld.parentNode.insertBefore(flutterTarget, handheld);
handheld.remove();
handheld = null;
}, 210);
setOtherFxEnabled(true);
}
window.requestAnimationFrame(function () {
// Let the browser flush the DOM...
flutterTarget.classList.toggle(fx);
});
}
// Sets a rotation style on the flutterTarget (in degrees).
function handleRotation(degrees) {
flutterTarget.style.transform = `perspective(1000px) rotateY(${degrees}deg)`;
}
// Removes the inline style from the flutterTarget.
function resetRotation() {
flutterTarget.style = null;
}
// Enables/disables the buttons that are not compatible with the "handheld" mode.
function setOtherFxEnabled(enabled) {
fxButtons.querySelectorAll('input').forEach((btn) => {
if (btn.dataset.fx != 'handheld') {
btn.classList.toggle('disabled', !enabled);
}
});
}
// Handles clicks on the buttons inside #fx.
fxButtons.addEventListener("click", (event) => {
let fx = event.target.dataset.fx;
if (fx === "handheld") {
handleHandHeld(fx);
return;
}
flutterTarget.classList.toggle(fx);
});
fxButtons.addEventListener("input", (event) => {
if (event.target.id === "rotation") {
flutterTarget.classList.toggle("spin", false);
handleRotation(event.target.value);
}
})
})();

@ -0,0 +1,43 @@
// Sets up a channel to JS-interop with Flutter
(function() {
"use strict";
// This function will be called from Flutter when it prepares the JS-interop.
window._stateSet = function () {
window._stateSet = function () {
console.log("Call _stateSet only once!");
};
// The state of the flutter app, see `class _MyAppState` in lib/main.dart.
let appState = window._appState;
let valueField = document.querySelector("#value");
let updateState = function () {
valueField.value = appState.count;
};
// Register a callback to update the HTML field from Flutter.
appState.addHandler(updateState);
// Render the first value (0).
updateState();
let incrementButton = document.querySelector("#increment");
incrementButton.addEventListener("click", (event) => {
appState.increment();
});
let screenSelector = document.querySelector("#screen-selector");
screenSelector.addEventListener("change", (event) => {
appState.changeDemoScreenTo(event.target.value);
setJsInteropControlsEnabled(event.target.value === 'counter');
});
// Enables/disables the Value/Increment controls.
function setJsInteropControlsEnabled(enabled) {
let elements = document.querySelectorAll("#increment, label[for='value']");
elements.forEach((el) => {
el.classList.toggle('disabled', !enabled);
})
}
};
}());

@ -0,0 +1,35 @@
{
"name": "element_embedding_demo",
"short_name": "element_embedding",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "An example of how to embed a Flutter Web app into any HTML Element of a page.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

@ -22,6 +22,7 @@ declare -ar PROJECT_NAMES=(
"desktop_photo_search/fluent_ui"
"desktop_photo_search/material"
"experimental/context_menus"
"experimental/element_embedding_demo"
"experimental/federated_plugin/federated_plugin"
"experimental/federated_plugin/federated_plugin/example"
"experimental/federated_plugin/federated_plugin_macos"

Loading…
Cancel
Save