custom element registry 'whenDefine' function

This commit is contained in:
Karl Seguin
2025-12-02 22:19:58 +08:00
parent 4823e0b188
commit 568a4428ba
3 changed files with 66 additions and 0 deletions

View File

@@ -61,8 +61,11 @@ pub const Loader = struct {
.{ "litNonce", {} },
.{ "litHtmlVersions", {} },
.{ "litElementVersions", {} },
.{ "litHtmlPolyfillSupport", {} },
.{ "litElementHydrateSupport", {} },
.{ "litElementPolyfillSupport", {} },
.{ "reactiveElementVersions", {} },
.{ "recaptcha", {} },
.{ "grecaptcha", {} },

View File

@@ -81,3 +81,41 @@
testing.expectEqual('NO-HYPHEN-INVALID', el.tagName);
}
</script>
<script id="whenDefined_already_defined">
{
class AlreadyDefined extends HTMLElement {}
customElements.define('already-defined', AlreadyDefined);
const promise = customElements.whenDefined('already-defined');
testing.expectEqual('object', typeof promise);
testing.expectEqual(true, promise instanceof Promise);
}
</script>
<script id="whenDefined_not_yet_defined">
{
const promise = customElements.whenDefined('future-element');
testing.expectEqual('object', typeof promise);
testing.expectEqual(true, promise instanceof Promise);
// Now define it
class FutureElement extends HTMLElement {}
customElements.define('future-element', FutureElement);
}
</script>
<script id="whenDefined_same_promise">
{
const promise1 = customElements.whenDefined('pending-element');
const promise2 = customElements.whenDefined('pending-element');
// Should return the same promise for the same name
testing.expectEqual(true, promise1 === promise2);
// Define it so cleanup happens
class PendingElement extends HTMLElement {}
customElements.define('pending-element', PendingElement);
}
</script>

View File

@@ -30,6 +30,7 @@ const CustomElementDefinition = @import("CustomElementDefinition.zig");
const CustomElementRegistry = @This();
_definitions: std.StringHashMapUnmanaged(*CustomElementDefinition) = .{},
_when_defined: std.StringHashMapUnmanaged(js.PersistentPromiseResolver) = .{},
const DefineOptions = struct {
extends: ?[]const u8 = null,
@@ -103,6 +104,10 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
_ = page._undefined_custom_elements.swapRemove(idx);
}
if (self._when_defined.fetchRemove(name)) |entry| {
try entry.value.resolve(constructor);
}
}
pub fn get(self: *CustomElementRegistry, name: []const u8) ?js.Function {
@@ -114,6 +119,25 @@ pub fn upgrade(self: *CustomElementRegistry, root: *Node, page: *Page) !void {
try upgradeNode(self, root, page);
}
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
if (self._definitions.get(name)) |definition| {
return page.js.resolvePromise(definition.constructor);
}
const gop = try self._when_defined.getOrPut(page.arena, name);
if (gop.found_existing) {
return gop.value_ptr.promise();
}
errdefer _ = self._when_defined.remove(name);
const owned_name = try page.dupeString(name);
const resolver = try page.js.createPromiseResolver(.page);
gop.key_ptr.* = owned_name;
gop.value_ptr.* = resolver;
return resolver.promise();
}
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
if (node.is(Element)) |element| {
try upgradeElement(self, element, page);
@@ -222,6 +246,7 @@ pub const JsApi = struct {
pub const define = bridge.function(CustomElementRegistry.define, .{ .dom_exception = true });
pub const get = bridge.function(CustomElementRegistry.get, .{ .null_as_undefined = true });
pub const upgrade = bridge.function(CustomElementRegistry.upgrade, .{});
pub const whenDefined = bridge.function(CustomElementRegistry.whenDefined, .{});
};
const testing = @import("../../testing.zig");