mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-19 02:18:10 +00:00
Element.slot, Element.assignedSlot and slotchange event
This commit is contained in:
@@ -53,7 +53,6 @@ const DocumentFragment = @import("webapi/DocumentFragment.zig");
|
|||||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||||
const Performance = @import("webapi/Performance.zig");
|
const Performance = @import("webapi/Performance.zig");
|
||||||
const Screen = @import("webapi/Screen.zig");
|
const Screen = @import("webapi/Screen.zig");
|
||||||
const HtmlScript = @import("webapi/Element.zig").Html.Script;
|
|
||||||
const MutationObserver = @import("webapi/MutationObserver.zig");
|
const MutationObserver = @import("webapi/MutationObserver.zig");
|
||||||
const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
|
const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
|
||||||
const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig");
|
const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig");
|
||||||
@@ -95,6 +94,7 @@ _element_styles: Element.StyleLookup = .{},
|
|||||||
_element_datasets: Element.DatasetLookup = .{},
|
_element_datasets: Element.DatasetLookup = .{},
|
||||||
_element_class_lists: Element.ClassListLookup = .{},
|
_element_class_lists: Element.ClassListLookup = .{},
|
||||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||||
|
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||||
|
|
||||||
_script_manager: ScriptManager,
|
_script_manager: ScriptManager,
|
||||||
|
|
||||||
@@ -108,6 +108,10 @@ _intersection_observers: std.ArrayList(*IntersectionObserver) = .{},
|
|||||||
_intersection_check_scheduled: bool = false,
|
_intersection_check_scheduled: bool = false,
|
||||||
_intersection_delivery_scheduled: bool = false,
|
_intersection_delivery_scheduled: bool = false,
|
||||||
|
|
||||||
|
// Slots that need slotchange events to be fired
|
||||||
|
_slots_pending_slotchange: std.AutoHashMapUnmanaged(*Element.Html.Slot, void) = .{},
|
||||||
|
_slotchange_delivery_scheduled: bool = false,
|
||||||
|
|
||||||
// Lookup for customized built-in elements. Maps element pointer to definition.
|
// Lookup for customized built-in elements. Maps element pointer to definition.
|
||||||
_customized_builtin_definitions: std.AutoHashMapUnmanaged(*Element, *CustomElementDefinition) = .{},
|
_customized_builtin_definitions: std.AutoHashMapUnmanaged(*Element, *CustomElementDefinition) = .{},
|
||||||
_customized_builtin_connected_callback_invoked: std.AutoHashMapUnmanaged(*Element, void) = .{},
|
_customized_builtin_connected_callback_invoked: std.AutoHashMapUnmanaged(*Element, void) = .{},
|
||||||
@@ -242,6 +246,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._element_datasets = .{};
|
self._element_datasets = .{};
|
||||||
self._element_class_lists = .{};
|
self._element_class_lists = .{};
|
||||||
self._element_shadow_roots = .{};
|
self._element_shadow_roots = .{};
|
||||||
|
self._element_assigned_slots = .{};
|
||||||
self._notified_network_idle = .init;
|
self._notified_network_idle = .init;
|
||||||
self._notified_network_almost_idle = .init;
|
self._notified_network_almost_idle = .init;
|
||||||
|
|
||||||
@@ -251,6 +256,8 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._intersection_observers = .{};
|
self._intersection_observers = .{};
|
||||||
self._intersection_check_scheduled = false;
|
self._intersection_check_scheduled = false;
|
||||||
self._intersection_delivery_scheduled = false;
|
self._intersection_delivery_scheduled = false;
|
||||||
|
self._slots_pending_slotchange = .{};
|
||||||
|
self._slotchange_delivery_scheduled = false;
|
||||||
self._customized_builtin_definitions = .{};
|
self._customized_builtin_definitions = .{};
|
||||||
self._customized_builtin_connected_callback_invoked = .{};
|
self._customized_builtin_connected_callback_invoked = .{};
|
||||||
self._customized_builtin_disconnected_callback_invoked = .{};
|
self._customized_builtin_disconnected_callback_invoked = .{};
|
||||||
@@ -770,7 +777,7 @@ pub fn tick(self: *Page) void {
|
|||||||
self.js.runMicrotasks();
|
self.js.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scriptAddedCallback(self: *Page, script: *HtmlScript) !void {
|
pub fn scriptAddedCallback(self: *Page, script: *Element.Html.Script) !void {
|
||||||
self._script_manager.addFromElement(script, "parsing") catch |err| {
|
self._script_manager.addFromElement(script, "parsing") catch |err| {
|
||||||
log.err(.page, "page.scriptAddedCallback", .{
|
log.err(.page, "page.scriptAddedCallback", .{
|
||||||
.err = err,
|
.err = err,
|
||||||
@@ -889,6 +896,14 @@ pub fn scheduleIntersectionDelivery(self: *Page) !void {
|
|||||||
try self.js.queueIntersectionDelivery();
|
try self.js.queueIntersectionDelivery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scheduleSlotchangeDelivery(self: *Page) !void {
|
||||||
|
if (self._slotchange_delivery_scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._slotchange_delivery_scheduled = true;
|
||||||
|
try self.js.queueSlotchangeDelivery();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn performScheduledIntersectionChecks(self: *Page) void {
|
pub fn performScheduledIntersectionChecks(self: *Page) void {
|
||||||
if (!self._intersection_check_scheduled) {
|
if (!self._intersection_check_scheduled) {
|
||||||
return;
|
return;
|
||||||
@@ -945,6 +960,42 @@ pub fn deliverMutations(self: *Page) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deliverSlotchangeEvents(self: *Page) void {
|
||||||
|
if (!self._slotchange_delivery_scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._slotchange_delivery_scheduled = false;
|
||||||
|
|
||||||
|
// we need to collect the pending slots, and then clear it and THEN exeute
|
||||||
|
// the slot change. We do this in case the slotchange event itself schedules
|
||||||
|
// more slot changes (which should only be executed on the next microtask)
|
||||||
|
const pending = self._slots_pending_slotchange.count();
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
var slots = self.call_arena.alloc(*Element.Html.Slot, pending) catch |err| {
|
||||||
|
log.err(.page, "deliverSlotchange.append", .{ .err = err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var it = self._slots_pending_slotchange.keyIterator();
|
||||||
|
while (it.next()) |slot| {
|
||||||
|
slots[i] = slot.*;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
self._slots_pending_slotchange.clearRetainingCapacity();
|
||||||
|
|
||||||
|
for (slots) |slot| {
|
||||||
|
const event = Event.init("slotchange", .{ .bubbles = true }, self) catch |err| {
|
||||||
|
log.err(.page, "deliverSlotchange.init", .{ .err = err });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
const target = slot.asNode().asEventTarget();
|
||||||
|
_ = target.dispatchEvent(event, self) catch |err| {
|
||||||
|
log.err(.page, "deliverSlotchange.dispatch", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn notifyNetworkIdle(self: *Page) void {
|
fn notifyNetworkIdle(self: *Page) void {
|
||||||
std.debug.assert(self._notified_network_idle == .done);
|
std.debug.assert(self._notified_network_idle == .done);
|
||||||
self._session.browser.notification.dispatch(.page_network_idle, &.{
|
self._session.browser.notification.dispatch(.page_network_idle, &.{
|
||||||
@@ -1564,6 +1615,28 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
|||||||
child._parent = null;
|
child._parent = null;
|
||||||
child._child_link = .{};
|
child._child_link = .{};
|
||||||
|
|
||||||
|
// Handle slot assignment removal before mutation observers
|
||||||
|
if (child.is(Element)) |el| {
|
||||||
|
// Check if the parent was a shadow host
|
||||||
|
if (parent.is(Element)) |parent_el| {
|
||||||
|
if (self._element_shadow_roots.get(parent_el)) |shadow_root| {
|
||||||
|
// Signal slot changes for any affected slots
|
||||||
|
const slot_name = el.getAttributeSafe("slot") orelse "";
|
||||||
|
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{});
|
||||||
|
while (tw.next()) |slot_el| {
|
||||||
|
if (slot_el.is(Element.Html.Slot)) |slot| {
|
||||||
|
if (std.mem.eql(u8, slot.getName(), slot_name)) {
|
||||||
|
self.signalSlotChange(slot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove from assigned slot lookup
|
||||||
|
_ = self._element_assigned_slots.remove(el);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.hasMutationObservers()) {
|
if (self.hasMutationObservers()) {
|
||||||
const removed = [_]*Node{child};
|
const removed = [_]*Node{child};
|
||||||
self.childListChange(parent, &.{}, &removed, previous_sibling, next_sibling);
|
self.childListChange(parent, &.{}, &removed, previous_sibling, next_sibling);
|
||||||
@@ -1733,6 +1806,12 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update slot assignments for the inserted child if parent is a shadow host
|
||||||
|
// This needs to happen even if the element isn't connected to the document
|
||||||
|
if (child.is(Element)) |el| {
|
||||||
|
self.updateElementAssignedSlot(el);
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.child_already_connected and !opts.adopting_to_new_document) {
|
if (opts.child_already_connected and !opts.adopting_to_new_document) {
|
||||||
// The child is already connected in the same document, we don't have to reconnect it
|
// The child is already connected in the same document, we don't have to reconnect it
|
||||||
return;
|
return;
|
||||||
@@ -1779,6 +1858,16 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value:
|
|||||||
log.err(.page, "attributeChange.notifyObserver", .{ .err = err });
|
log.err(.page, "attributeChange.notifyObserver", .{ .err = err });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle slot assignment changes
|
||||||
|
if (std.mem.eql(u8, name, "slot")) {
|
||||||
|
self.updateSlotAssignments(element);
|
||||||
|
} else if (std.mem.eql(u8, name, "name")) {
|
||||||
|
// Check if this is a slot element
|
||||||
|
if (element.is(Element.Html.Slot)) |slot| {
|
||||||
|
self.signalSlotChange(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_value: []const u8) void {
|
pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_value: []const u8) void {
|
||||||
@@ -1793,6 +1882,88 @@ pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_val
|
|||||||
log.err(.page, "attributeRemove.notifyObserver", .{ .err = err });
|
log.err(.page, "attributeRemove.notifyObserver", .{ .err = err });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle slot assignment changes
|
||||||
|
if (std.mem.eql(u8, name, "slot")) {
|
||||||
|
self.updateSlotAssignments(element);
|
||||||
|
} else if (std.mem.eql(u8, name, "name")) {
|
||||||
|
// Check if this is a slot element
|
||||||
|
if (element.is(Element.Html.Slot)) |slot| {
|
||||||
|
self.signalSlotChange(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signalSlotChange(self: *Page, slot: *Element.Html.Slot) void {
|
||||||
|
self._slots_pending_slotchange.put(self.arena, slot, {}) catch |err| {
|
||||||
|
log.err(.page, "signalSlotChange.put", .{ .err = err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.scheduleSlotchangeDelivery() catch |err| {
|
||||||
|
log.err(.page, "signalSlotChange.schedule", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateSlotAssignments(self: *Page, element: *Element) void {
|
||||||
|
// Find all slots in the shadow root that might be affected
|
||||||
|
const parent = element.asNode()._parent orelse return;
|
||||||
|
|
||||||
|
// Check if parent is a shadow host
|
||||||
|
const parent_el = parent.is(Element) orelse return;
|
||||||
|
_ = self._element_shadow_roots.get(parent_el) orelse return;
|
||||||
|
|
||||||
|
// Signal change for the old slot (if any)
|
||||||
|
if (self._element_assigned_slots.get(element)) |old_slot| {
|
||||||
|
self.signalSlotChange(old_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the assignedSlot lookup to the new slot
|
||||||
|
self.updateElementAssignedSlot(element);
|
||||||
|
|
||||||
|
// Signal change for the new slot (if any)
|
||||||
|
if (self._element_assigned_slots.get(element)) |new_slot| {
|
||||||
|
self.signalSlotChange(new_slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateElementAssignedSlot(self: *Page, element: *Element) void {
|
||||||
|
// Remove old assignment
|
||||||
|
_ = self._element_assigned_slots.remove(element);
|
||||||
|
|
||||||
|
// Find the new assigned slot
|
||||||
|
const parent = element.asNode()._parent orelse return;
|
||||||
|
const parent_el = parent.is(Element) orelse return;
|
||||||
|
const shadow_root = self._element_shadow_roots.get(parent_el) orelse return;
|
||||||
|
|
||||||
|
const slot_name = element.getAttributeSafe("slot") orelse "";
|
||||||
|
|
||||||
|
// Recursively search through the shadow root for a matching slot
|
||||||
|
if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| {
|
||||||
|
self._element_assigned_slots.put(self.arena, element, slot) catch |err| {
|
||||||
|
log.err(.page, "updateElementAssignedSlot.put", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findMatchingSlot(node: *Node, slot_name: []const u8) ?*Element.Html.Slot {
|
||||||
|
// Check if this node is a matching slot
|
||||||
|
if (node.is(Element)) |el| {
|
||||||
|
if (el.is(Element.Html.Slot)) |slot| {
|
||||||
|
if (std.mem.eql(u8, slot.getName(), slot_name)) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search children
|
||||||
|
var it = node.childrenIterator();
|
||||||
|
while (it.next()) |child| {
|
||||||
|
if (findMatchingSlot(child, slot_name)) |slot| {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasMutationObservers(self: *const Page) bool {
|
pub fn hasMutationObservers(self: *const Page) bool {
|
||||||
|
|||||||
@@ -2007,6 +2007,15 @@ pub fn queueIntersectionDelivery(self: *Context) !void {
|
|||||||
}.run, self.page);
|
}.run, self.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queueSlotchangeDelivery(self: *Context) !void {
|
||||||
|
self.isolate.enqueueMicrotask(struct {
|
||||||
|
fn run(data: ?*anyopaque) callconv(.c) void {
|
||||||
|
const page: *Page = @ptrCast(@alignCast(data.?));
|
||||||
|
page.deliverSlotchangeEvents();
|
||||||
|
}
|
||||||
|
}.run, self.page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void {
|
pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void {
|
||||||
self.isolate.enqueueMicrotaskFunc(cb.func.castToFunction());
|
self.isolate.enqueueMicrotaskFunc(cb.func.castToFunction());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,3 +382,137 @@
|
|||||||
testing.expectTrue(flattened[2] === span);
|
testing.expectTrue(flattened[2] === span);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#assignedSlot_returns_null_when_not_assigned">
|
||||||
|
{
|
||||||
|
const div = document.createElement('div');
|
||||||
|
testing.expectEqual(null, div.assignedSlot);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#assignedSlot_property">
|
||||||
|
{
|
||||||
|
const host = document.createElement('div');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
const slot = document.createElement('slot');
|
||||||
|
slot.name = 'header';
|
||||||
|
shadow.appendChild(slot);
|
||||||
|
|
||||||
|
const h1 = document.createElement('h1');
|
||||||
|
h1.setAttribute('slot', 'header');
|
||||||
|
host.appendChild(h1);
|
||||||
|
|
||||||
|
testing.expectEqual(slot, h1.assignedSlot);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#slotchange_on_insertion">
|
||||||
|
{
|
||||||
|
let calls = 0;
|
||||||
|
const host = document.createElement('div');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
const slot = document.createElement('slot');
|
||||||
|
slot.name = 'content';
|
||||||
|
shadow.appendChild(slot);
|
||||||
|
|
||||||
|
slot.addEventListener('slotchange', (e) => {
|
||||||
|
const nodes = slot.assignedNodes();
|
||||||
|
testing.expectEqual(1, nodes.length);
|
||||||
|
calls += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('slot', 'content');
|
||||||
|
host.appendChild(div);
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(1, calls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#slotchange_on_attribute_change">
|
||||||
|
{
|
||||||
|
let calls = 0;
|
||||||
|
const host = document.createElement('div');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
const slot = document.createElement('slot');
|
||||||
|
slot.name = 'content';
|
||||||
|
shadow.appendChild(slot);
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('slot', 'content');
|
||||||
|
host.appendChild(div);
|
||||||
|
|
||||||
|
slot.addEventListener('slotchange', (e) => {
|
||||||
|
const nodes = slot.assignedNodes();
|
||||||
|
testing.expectEqual(0, nodes.length);
|
||||||
|
calls += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
div.setAttribute('slot', 'other');
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(1, calls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#slotchange_on_removal">
|
||||||
|
{
|
||||||
|
let calls = 0;
|
||||||
|
const host = document.createElement('div');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
const slot = document.createElement('slot');
|
||||||
|
slot.name = 'content';
|
||||||
|
shadow.appendChild(slot);
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.setAttribute('slot', 'content');
|
||||||
|
host.appendChild(div);
|
||||||
|
|
||||||
|
slot.addEventListener('slotchange', (e) => {
|
||||||
|
const nodes = slot.assignedNodes();
|
||||||
|
testing.expectEqual(0, nodes.length);
|
||||||
|
calls += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
div.remove();
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(1, calls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="Slot#slotchange_with_slot_property">
|
||||||
|
{
|
||||||
|
let calls = 0;
|
||||||
|
const host = document.createElement('div');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
const slot = document.createElement('slot');
|
||||||
|
slot.name = 'content';
|
||||||
|
shadow.appendChild(slot);
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.slot = 'content';
|
||||||
|
host.appendChild(div);
|
||||||
|
|
||||||
|
slot.addEventListener('slotchange', (e) => {
|
||||||
|
const nodes = slot.assignedNodes();
|
||||||
|
testing.expectEqual(0, nodes.length);
|
||||||
|
calls += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
div.slot = 'other';
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(1, calls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
|||||||
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
||||||
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
||||||
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||||
|
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
||||||
|
|
||||||
pub const Namespace = enum(u8) {
|
pub const Namespace = enum(u8) {
|
||||||
html,
|
html,
|
||||||
@@ -400,6 +401,14 @@ pub fn setId(self: *Element, value: []const u8, page: *Page) !void {
|
|||||||
return self.setAttributeSafe("id", value, page);
|
return self.setAttributeSafe("id", value, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getSlot(self: *const Element) []const u8 {
|
||||||
|
return self.getAttributeSafe("slot") orelse "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void {
|
||||||
|
return self.setAttributeSafe("slot", value, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getDir(self: *const Element) []const u8 {
|
pub fn getDir(self: *const Element) []const u8 {
|
||||||
return self.getAttributeSafe("dir") orelse "";
|
return self.getAttributeSafe("dir") orelse "";
|
||||||
}
|
}
|
||||||
@@ -481,6 +490,10 @@ pub fn getShadowRoot(self: *Element, page: *Page) ?*ShadowRoot {
|
|||||||
return shadow_root;
|
return shadow_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getAssignedSlot(self: *Element, page: *Page) ?*Html.Slot {
|
||||||
|
return page._element_assigned_slots.get(self);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn attachShadow(self: *Element, mode_str: []const u8, page: *Page) !*ShadowRoot {
|
pub fn attachShadow(self: *Element, mode_str: []const u8, page: *Page) !*ShadowRoot {
|
||||||
if (page._element_shadow_roots.get(self)) |_| {
|
if (page._element_shadow_roots.get(self)) |_| {
|
||||||
return error.AlreadyHasShadowRoot;
|
return error.AlreadyHasShadowRoot;
|
||||||
@@ -1242,6 +1255,7 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const localName = bridge.accessor(Element.getLocalName, null, .{});
|
pub const localName = bridge.accessor(Element.getLocalName, null, .{});
|
||||||
pub const id = bridge.accessor(Element.getId, Element.setId, .{});
|
pub const id = bridge.accessor(Element.getId, Element.setId, .{});
|
||||||
|
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{});
|
||||||
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
|
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
|
||||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
||||||
pub const classList = bridge.accessor(Element.getClassList, null, .{});
|
pub const classList = bridge.accessor(Element.getClassList, null, .{});
|
||||||
@@ -1259,6 +1273,7 @@ pub const JsApi = struct {
|
|||||||
pub const getAttributeNames = bridge.function(Element.getAttributeNames, .{});
|
pub const getAttributeNames = bridge.function(Element.getAttributeNames, .{});
|
||||||
pub const removeAttributeNode = bridge.function(Element.removeAttributeNode, .{ .dom_exception = true });
|
pub const removeAttributeNode = bridge.function(Element.removeAttributeNode, .{ .dom_exception = true });
|
||||||
pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{});
|
pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{});
|
||||||
|
pub const assignedSlot = bridge.accessor(Element.getAssignedSlot, null, .{});
|
||||||
pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true });
|
pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true });
|
||||||
pub const insertAdjacentHTML = bridge.function(Element.insertAdjacentHTML, .{ .dom_exception = true });
|
pub const insertAdjacentHTML = bridge.function(Element.insertAdjacentHTML, .{ .dom_exception = true });
|
||||||
pub const insertAdjacentElement = bridge.function(Element.insertAdjacentElement, .{ .dom_exception = true });
|
pub const insertAdjacentElement = bridge.function(Element.insertAdjacentElement, .{ .dom_exception = true });
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ fn collectAssignedNodes(self: *Slot, comptime elements: bool, coll: CollectionTy
|
|||||||
const allocator = page.call_arena;
|
const allocator = page.call_arena;
|
||||||
|
|
||||||
const host = shadow_root.getHost();
|
const host = shadow_root.getHost();
|
||||||
|
const initial_count = coll.items.len;
|
||||||
var it = host.asNode().childrenIterator();
|
var it = host.asNode().childrenIterator();
|
||||||
while (it.next()) |child| {
|
while (it.next()) |child| {
|
||||||
if (!isAssignedToSlot(child, slot_name)) {
|
if (!isAssignedToSlot(child, slot_name)) {
|
||||||
@@ -87,6 +88,20 @@ fn collectAssignedNodes(self: *Slot, comptime elements: bool, coll: CollectionTy
|
|||||||
try coll.append(allocator, child);
|
try coll.append(allocator, child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If flatten is true and no assigned nodes were found, return fallback content
|
||||||
|
if (opts.flatten and coll.items.len == initial_count) {
|
||||||
|
var child_it = self.asNode().childrenIterator();
|
||||||
|
while (child_it.next()) |child| {
|
||||||
|
if (comptime elements) {
|
||||||
|
if (child.is(Element)) |el| {
|
||||||
|
try coll.append(allocator, el);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try coll.append(allocator, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assign(self: *Slot, nodes: []const *Node) void {
|
pub fn assign(self: *Slot, nodes: []const *Node) void {
|
||||||
|
|||||||
Reference in New Issue
Block a user