Loading...
Loading...
Discover the power of Web Components and how to build them with both JavaScript and Dart for reusable, framework-agnostic UI elements.
npx skill4agent add rodydavis/skills how-to-create-html-web-components-with-dartclass HelloWorld extends HTMLElement {
static observedAttributes = ["name"];
constructor() {
super();
}
update() {
this.innerHTML = `Hello: ${this.getAttribute('name')}`;
}
connectedCallback() {
console.log("Custom element added to page.");
this. update();
}
disconnectedCallback() {
console.log("Custom element removed from page.");
}
adoptedCallback() {
console.log("Custom element moved to new page.");
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`Attribute ${name} has changed.`);
if (name === 'name') {
this. update();
}
}
}
customElements.define("hello-world", HelloWorld);<html>
<body>
<hello-world name="Rody"></hello-world>
<script src="./index.js"></script>
</body>
</html>Reflect.construct()import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:web/web.dart';
class WebComponent<T extends HTMLElement> {
late T element;
final String extendsType = 'HTMLElement';
void connectedCallback() {}
void disconnectedCallback() {}
void adoptedCallback() {}
void attributeChangedCallback(
String name,
String? oldValue,
String? newValue,
) {}
Iterable<String> get observedAttributes => [];
bool get formAssociated => false;
ElementInternals? get internals => element['_internals'] as ElementInternals?;
set internals(ElementInternals? value) {
element['_internals'] = value;
}
R getRoot<R extends JSObject>() {
final hasShadow = element.shadowRoot != null;
return (hasShadow ? element.shadowRoot! : element) as R;
}
static void define(String tag, WebComponent Function() create) {
final obj = _factory(create);
window.customElements.define(tag, obj);
}
}
@JS('Reflect.construct')
external JSAny _reflectConstruct(
JSObject target,
JSAny args,
JSFunction constructor,
);
final _instances = <HTMLElement, WebComponent>{};
JSFunction _factory(WebComponent Function() create) {
final base = create();
final elemProto = globalContext[base.extendsType] as JSObject;
late JSAny obj;
JSAny constructor() {
final args = <String>[].jsify()!;
final self = _reflectConstruct(elemProto, args, obj as JSFunction);
final el = self as HTMLElement;
_instances.putIfAbsent(el, () => create()..element = el);
return self;
}
obj = constructor.toJS;
obj = obj as JSObject;
final observedAttributes = base.observedAttributes;
final formAssociated = base.formAssociated;
obj['prototype'] = elemProto['prototype'];
obj['observedAttributes'] = observedAttributes.toList().jsify()!;
obj['formAssociated'] = formAssociated.jsify()!;
final prototype = obj['prototype'] as JSObject;
prototype['connectedCallback'] = (HTMLElement instance) {
_instances[instance]?.connectedCallback();
}.toJSCaptureThis;
prototype['disconnectedCallback'] = (HTMLElement instance) {
_instances[instance]?.disconnectedCallback();
_instances.remove(instance);
}.toJSCaptureThis;
prototype['adoptedCallback'] = (HTMLElement instance) {
_instances[instance]?.adoptedCallback();
}.toJSCaptureThis;
prototype['attributeChangedCallback'] = (
HTMLElement instance,
String name,
String? oldName,
String? newName,
) {
_instances[instance]?.attributeChangedCallback(name, oldName, newName);
}.toJSCaptureThis;
return obj as JSFunction;
}If you want a package that does this for you, html_web_components is on pub.dev.
import 'package:html_web_components/html_web_components.dart';
class HelloWorld extends WebComponent {
@override
List<String> observedAttributes = ['name'];
void update() {
element.innerText = "Hello: ${element.getAttribute('name')}!";
}
@override
void connectedCallback() {
super.connectedCallback();
update();
}
@override
void attributeChangedCallback(
String name,
String? oldValue,
String? newValue,
) {
super.attributeChangedCallback(name, oldValue, newValue);
if (observedAttributes.contains(name)) {
update();
}
}
}
void main() {
WebComponent.define('hello-world', HelloWorld.new);
}