Skip to main content

azul_core/
dom.rs

1//! Defines the core Document Object Model (DOM) structures.
2//!
3//! This module is responsible for representing the UI as a tree of nodes,
4//! similar to the HTML DOM. It includes definitions for node types, event handling
5//! and the main `Dom` and `CompactDom` structures.
6
7#[cfg(not(feature = "std"))]
8use alloc::string::ToString;
9use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
10use core::{
11    fmt,
12    hash::{Hash, Hasher},
13    iter::FromIterator,
14    mem,
15    sync::atomic::{AtomicUsize, Ordering},
16};
17
18use azul_css::{
19    codegen::format::GetHash,
20    css::{BoxOrStatic, Css, NodeTypeTag},
21    props::{
22        basic::{FloatValue, FontRef},
23        layout::{LayoutDisplay, LayoutFloat, LayoutPosition},
24        property::CssProperty,
25    },
26    AzString, OptionString,
27};
28
29// Re-exported from a11y.rs and events.rs
30pub use crate::a11y::*;
31pub use crate::events::{
32    ApplicationEventFilter, ComponentEventFilter, EventFilter, ExternalEventFilter,
33    FocusEventFilter, HoverEventFilter, WindowEventFilter,
34};
35pub use crate::id::{Node, NodeHierarchy, NodeId};
36use crate::{
37    callbacks::{
38        CoreCallback, CoreCallbackData, CoreCallbackDataVec, CoreCallbackType, VirtualViewCallback,
39        VirtualViewCallbackType,
40    },
41    geom::LogicalPosition,
42    id::{NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut},
43    menu::Menu,
44    prop_cache::{CssPropertyCache, CssPropertyCachePtr},
45    refany::{OptionRefAny, RefAny},
46    resources::{
47        image_ref_get_hash, CoreImageCallback, ImageMask, ImageRef, ImageRefHash, RendererResources,
48    },
49    styled_dom::{
50        CompactDom, NodeHierarchyItemId, StyleFontFamilyHash, StyledDom, StyledNode,
51        StyledNodeState,
52    },
53    window::OptionVirtualKeyCodeCombo,
54};
55pub use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
56
57static TAG_ID: AtomicUsize = AtomicUsize::new(1);
58
59/// Strongly-typed input element types for HTML `<input>` elements.
60#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub enum InputType {
63    /// Text input (default)
64    Text,
65    /// Button
66    Button,
67    /// Checkbox
68    Checkbox,
69    /// Color picker
70    Color,
71    /// Date picker
72    Date,
73    /// Date and time picker
74    Datetime,
75    /// Date and time picker (local)
76    DatetimeLocal,
77    /// Email address input
78    Email,
79    /// File upload
80    File,
81    /// Hidden input
82    Hidden,
83    /// Image button
84    Image,
85    /// Month picker
86    Month,
87    /// Number input
88    Number,
89    /// Password input
90    Password,
91    /// Radio button
92    Radio,
93    /// Range slider
94    Range,
95    /// Reset button
96    Reset,
97    /// Search input
98    Search,
99    /// Submit button
100    Submit,
101    /// Telephone number input
102    Tel,
103    /// Time picker
104    Time,
105    /// URL input
106    Url,
107    /// Week picker
108    Week,
109}
110
111impl InputType {
112    /// Returns the HTML attribute value for this input type
113    #[must_use]
114    pub const fn as_str(&self) -> &'static str {
115        match self {
116            Self::Text => "text",
117            Self::Button => "button",
118            Self::Checkbox => "checkbox",
119            Self::Color => "color",
120            Self::Date => "date",
121            Self::Datetime => "datetime",
122            Self::DatetimeLocal => "datetime-local",
123            Self::Email => "email",
124            Self::File => "file",
125            Self::Hidden => "hidden",
126            Self::Image => "image",
127            Self::Month => "month",
128            Self::Number => "number",
129            Self::Password => "password",
130            Self::Radio => "radio",
131            Self::Range => "range",
132            Self::Reset => "reset",
133            Self::Search => "search",
134            Self::Submit => "submit",
135            Self::Tel => "tel",
136            Self::Time => "time",
137            Self::Url => "url",
138            Self::Week => "week",
139        }
140    }
141}
142
143#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
144#[repr(C)]
145pub struct TagId {
146    pub inner: u64,
147}
148
149impl ::core::fmt::Display for TagId {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        f.debug_struct("TagId").field("inner", &self.inner).finish()
152    }
153}
154
155impl_option!(
156    TagId,
157    OptionTagId,
158    [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
159);
160
161impl TagId {
162    #[must_use]
163    pub const fn into_crate_internal(&self) -> Self {
164        Self { inner: self.inner }
165    }
166    #[must_use]
167    pub const fn from_crate_internal(t: Self) -> Self {
168        t
169    }
170
171    /// Creates a new, unique hit-testing tag ID.
172    /// Wraps around to 1 on overflow (0 is reserved for "no tag").
173    ///
174    /// AUDIT: the wrap is only reachable after 2^64 - 1 allocations (a process
175    /// running long enough to exhaust the counter is not realistic), but note
176    /// that on wrap the freshly-issued id could theoretically collide with a
177    /// still-live tag from very early in the process. This is left as a
178    /// documented, non-triggerable limitation rather than adding a live-tag
179    /// registry to detect collisions on every allocation. AUDIT-TODO: revisit
180    /// if `TagId` is ever narrowed below 64 bits.
181    pub fn unique() -> Self {
182        loop {
183            let current = TAG_ID.load(Ordering::SeqCst);
184            let next = if current == usize::MAX {
185                1
186            } else {
187                current + 1
188            };
189            if TAG_ID
190                .compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst)
191                .is_ok()
192            {
193                return Self {
194                    inner: current as u64,
195                };
196            }
197        }
198    }
199}
200
201/// Same as the `TagId`, but only for scrollable nodes.
202/// This provides a typed distinction for tags associated with scrolling containers.
203#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
204#[repr(C)]
205pub struct ScrollTagId {
206    pub inner: TagId,
207}
208
209impl ::core::fmt::Display for ScrollTagId {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        f.debug_struct("ScrollTagId")
212            .field("inner", &self.inner)
213            .finish()
214    }
215}
216
217impl ::core::fmt::Debug for ScrollTagId {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "{self}")
220    }
221}
222
223impl ScrollTagId {
224    /// Creates a new, unique scroll tag ID. Note that this should not
225    /// be used for identifying nodes, use the `DomNodeHash` instead.
226    #[must_use]
227    pub fn unique() -> Self {
228        Self {
229            inner: TagId::unique(),
230        }
231    }
232}
233
234/// Orientation of a scrollbar.
235#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
236#[repr(C)]
237pub enum ScrollbarOrientation {
238    Horizontal,
239    Vertical,
240}
241
242/// Calculated hash of a DOM node, used for identifying identical DOM
243/// nodes across frames for efficient diffing and state preservation.
244#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
245#[repr(C)]
246pub struct DomNodeHash {
247    pub inner: u64,
248}
249
250impl ::core::fmt::Debug for DomNodeHash {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        write!(f, "DomNodeHash({})", self.inner)
253    }
254}
255
256/// List of core DOM node types built into `azul`.
257/// This enum defines the building blocks of the UI, similar to HTML tags.
258#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
259#[repr(C, u8)]
260pub enum NodeType {
261    // Root and container elements
262    /// Root HTML element.
263    Html,
264    /// Document head (metadata container).
265    Head,
266    /// Root element of the document body.
267    Body,
268    /// Generic block-level container.
269    Div,
270    /// Paragraph.
271    P,
272    /// Article content.
273    Article,
274    /// Section of a document.
275    Section,
276    /// Navigation links.
277    Nav,
278    /// Sidebar/tangential content.
279    Aside,
280    /// Header section.
281    Header,
282    /// Footer section.
283    Footer,
284    /// Main content.
285    Main,
286    /// Figure with optional caption.
287    Figure,
288    /// Caption for figure element.
289    FigCaption,
290    /// Headings.
291    H1,
292    H2,
293    H3,
294    H4,
295    H5,
296    H6,
297    /// Line break.
298    Br,
299    /// Horizontal rule.
300    Hr,
301    /// Preformatted text.
302    Pre,
303    /// Block quote.
304    BlockQuote,
305    /// Address.
306    Address,
307    /// Details disclosure widget.
308    Details,
309    /// Summary for details element.
310    Summary,
311    /// Dialog box or window.
312    Dialog,
313
314    // List elements
315    /// Unordered list.
316    Ul,
317    /// Ordered list.
318    Ol,
319    /// List item.
320    Li,
321    /// Definition list.
322    Dl,
323    /// Definition term.
324    Dt,
325    /// Definition description.
326    Dd,
327    /// Menu list.
328    Menu,
329    /// Menu item.
330    MenuItem,
331    /// Directory list (deprecated).
332    Dir,
333
334    // Table elements
335    /// Table container.
336    Table,
337    /// Table caption.
338    Caption,
339    /// Table header.
340    THead,
341    /// Table body.
342    TBody,
343    /// Table footer.
344    TFoot,
345    /// Table row.
346    Tr,
347    /// Table header cell.
348    Th,
349    /// Table data cell.
350    Td,
351    /// Table column group.
352    ColGroup,
353    /// Table column.
354    Col,
355
356    // Form elements
357    /// Form container.
358    Form,
359    /// Form fieldset.
360    FieldSet,
361    /// Fieldset legend.
362    Legend,
363    /// Label for form controls.
364    Label,
365    /// Input control.
366    Input,
367    /// Button control.
368    Button,
369    /// Select dropdown.
370    Select,
371    /// Option group.
372    OptGroup,
373    /// Select option.
374    SelectOption,
375    /// Multiline text input.
376    TextArea,
377    /// Form output element.
378    Output,
379    /// Progress indicator.
380    Progress,
381    /// Scalar measurement within a known range.
382    Meter,
383    /// List of predefined options for input.
384    DataList,
385
386    // Inline elements
387    /// Generic inline container.
388    Span,
389    /// Anchor/hyperlink.
390    A,
391    /// Emphasized text.
392    Em,
393    /// Strongly emphasized text.
394    Strong,
395    /// Bold text (deprecated - use `Dom::create_strong()` for semantic importance).
396    B,
397    /// Italic text (deprecated - use `Dom::create_em()` for emphasis or `Dom::create_cite()` for citations).
398    I,
399    /// Underline text.
400    U,
401    /// Strikethrough text.
402    S,
403    /// Marked/highlighted text.
404    Mark,
405    /// Deleted text.
406    Del,
407    /// Inserted text.
408    Ins,
409    /// Code.
410    Code,
411    /// Sample output.
412    Samp,
413    /// Keyboard input.
414    Kbd,
415    /// Variable.
416    Var,
417    /// Citation.
418    Cite,
419    /// Defining instance of a term.
420    Dfn,
421    /// Abbreviation.
422    Abbr,
423    /// Acronym.
424    Acronym,
425    /// Inline quotation.
426    Q,
427    /// Date/time.
428    Time,
429    /// Subscript.
430    Sub,
431    /// Superscript.
432    Sup,
433    /// Small text (deprecated - use CSS `font-size` instead).
434    Small,
435    /// Big text (deprecated - use CSS `font-size` instead).
436    Big,
437    /// Bi-directional override.
438    Bdo,
439    /// Bi-directional isolate.
440    Bdi,
441    /// Word break opportunity.
442    Wbr,
443    /// Ruby annotation.
444    Ruby,
445    /// Ruby text.
446    Rt,
447    /// Ruby text container.
448    Rtc,
449    /// Ruby parenthesis.
450    Rp,
451    /// Machine-readable data.
452    Data,
453
454    // Embedded content
455    /// Canvas for graphics.
456    Canvas,
457    /// Embedded object.
458    Object,
459    /// Embedded object parameter.
460    Param,
461    /// External resource embed.
462    Embed,
463    /// Audio content.
464    Audio,
465    /// Video content.
466    Video,
467    /// Media source.
468    Source,
469    /// Text track for media.
470    Track,
471    /// Image map.
472    Map,
473    /// Image map area.
474    Area,
475    // SVG elements — container
476    /// SVG `<svg>` root graphics container.
477    Svg,
478    /// SVG `<g>` group element.
479    SvgG,
480    /// SVG `<defs>` — reusable definitions (not rendered directly).
481    SvgDefs,
482    /// SVG `<symbol>` — like defs but with its own viewBox.
483    SvgSymbol,
484    /// SVG `<use>` — references and instantiates a defs element.
485    SvgUse,
486    /// SVG `<switch>` — conditional processing.
487    SvgSwitch,
488
489    // SVG elements — shape
490    /// SVG `<path>` element.
491    SvgPath,
492    /// SVG `<circle>` element.
493    SvgCircle,
494    /// SVG `<rect>` element.
495    SvgRect,
496    /// SVG `<ellipse>` element.
497    SvgEllipse,
498    /// SVG `<line>` element.
499    SvgLine,
500    /// SVG `<polygon>` element.
501    SvgPolygon,
502    /// SVG `<polyline>` element.
503    SvgPolyline,
504
505    // SVG elements — text
506    /// SVG `<text>` element.
507    SvgText(AzString),
508    /// SVG `<tspan>` element.
509    SvgTspan,
510    /// SVG `<textPath>` element.
511    SvgTextPath,
512
513    // SVG elements — paint servers
514    /// SVG `<linearGradient>` element.
515    SvgLinearGradient,
516    /// SVG `<radialGradient>` element.
517    SvgRadialGradient,
518    /// SVG `<stop>` gradient stop element.
519    SvgStop,
520    /// SVG `<pattern>` element.
521    SvgPattern,
522
523    // SVG elements — clipping / masking
524    /// SVG `<clipPath>` element.
525    SvgClipPathElement,
526    /// SVG `<mask>` element.
527    SvgMask,
528
529    // SVG elements — filter
530    /// SVG `<filter>` container element.
531    SvgFilter,
532    /// SVG `<feBlend>`.
533    SvgFeBlend,
534    /// SVG `<feColorMatrix>`.
535    SvgFeColorMatrix,
536    /// SVG `<feComponentTransfer>`.
537    SvgFeComponentTransfer,
538    /// SVG `<feComposite>`.
539    SvgFeComposite,
540    /// SVG `<feConvolveMatrix>`.
541    SvgFeConvolveMatrix,
542    /// SVG `<feDiffuseLighting>`.
543    SvgFeDiffuseLighting,
544    /// SVG `<feDisplacementMap>`.
545    SvgFeDisplacementMap,
546    /// SVG `<feDistantLight>`.
547    SvgFeDistantLight,
548    /// SVG `<feDropShadow>`.
549    SvgFeDropShadow,
550    /// SVG `<feFlood>`.
551    SvgFeFlood,
552    /// SVG `<feFuncR>`.
553    SvgFeFuncR,
554    /// SVG `<feFuncG>`.
555    SvgFeFuncG,
556    /// SVG `<feFuncB>`.
557    SvgFeFuncB,
558    /// SVG `<feFuncA>`.
559    SvgFeFuncA,
560    /// SVG `<feGaussianBlur>`.
561    SvgFeGaussianBlur,
562    /// SVG `<feImage>`.
563    SvgFeImage,
564    /// SVG `<feMerge>`.
565    SvgFeMerge,
566    /// SVG `<feMergeNode>`.
567    SvgFeMergeNode,
568    /// SVG `<feMorphology>`.
569    SvgFeMorphology,
570    /// SVG `<feOffset>`.
571    SvgFeOffset,
572    /// SVG `<fePointLight>`.
573    SvgFePointLight,
574    /// SVG `<feSpecularLighting>`.
575    SvgFeSpecularLighting,
576    /// SVG `<feSpotLight>`.
577    SvgFeSpotLight,
578    /// SVG `<feTile>`.
579    SvgFeTile,
580    /// SVG `<feTurbulence>`.
581    SvgFeTurbulence,
582
583    // SVG elements — marker / image / foreign
584    /// SVG `<marker>` element (not the CSS `::marker` pseudo-element).
585    SvgMarker,
586    /// SVG `<image>` element (embedded raster image in SVG).
587    SvgImage(ImageRef),
588    /// SVG `<foreignObject>` element.
589    SvgForeignObject,
590
591    // SVG elements — descriptive / structural
592    /// SVG `<title>` element (distinct from HTML `<title>`).
593    SvgTitle,
594    /// SVG `<desc>` element.
595    SvgDesc,
596    /// SVG `<metadata>` element.
597    SvgMetadata,
598    /// SVG `<a>` hyperlink element (distinct from HTML `<a>`).
599    SvgA,
600    /// SVG `<view>` element.
601    SvgView,
602    /// SVG `<style>` element (distinct from HTML `<style>`).
603    SvgStyle,
604    /// SVG `<script>` element (distinct from HTML `<script>`).
605    SvgScript,
606
607    // SVG elements — animation
608    /// SVG `<animate>` element.
609    SvgAnimate,
610    /// SVG `<animateMotion>` element.
611    SvgAnimateMotion,
612    /// SVG `<animateTransform>` element.
613    SvgAnimateTransform,
614    /// SVG `<set>` element.
615    SvgSet,
616    /// SVG `<mpath>` element.
617    SvgMpath,
618
619    // Metadata elements
620    /// Document title.
621    Title,
622    /// Metadata.
623    Meta,
624    /// External resource link.
625    Link,
626    /// Embedded or referenced script.
627    Script,
628    /// Style information.
629    Style,
630    /// Base URL for relative URLs.
631    Base,
632
633    // Pseudo-elements (transformed into real elements)
634    /// `::before` pseudo-element.
635    Before,
636    /// `::after` pseudo-element.
637    After,
638    /// `::marker` pseudo-element.
639    Marker,
640    /// `::placeholder` pseudo-element.
641    Placeholder,
642
643    // Special content types
644    /// Text content, `::text`.
645    /// Uses `BoxOrStatic` to keep `NodeType` small (~16B vs ~72B with inline `AzString`)
646    /// and to allow static text references in the future.
647    Text(BoxOrStatic<AzString>),
648    /// Image element, `::image`.
649    /// Uses `BoxOrStatic` to keep `NodeType` small.
650    Image(BoxOrStatic<ImageRef>),
651    /// `VirtualView` (embedded content) - payload stored in `NodeDataExt.virtual_view`
652    VirtualView,
653    /// `<transient-window>` — a popup that is a real OS window drawn from this
654    /// node's subtree. Contributes nothing to layout while closed; when `open`
655    /// the engine materialises the children as a window anchored to the PARENT.
656    /// See `crate::transient`.
657    TransientWindow(crate::transient::TransientWindowConfig),
658    /// Icon element - resolved to actual content by `IconProvider`.
659    /// The string is the icon name (e.g., "home", "settings", "search").
660    /// Uses `BoxOrStatic` to keep `NodeType` small.
661    Icon(BoxOrStatic<AzString>),
662    /// Invisible probe node that signals "this subtree needs the user's
663    /// GPS / network location". Zero-size in layout, skipped in the
664    /// display list. The `GeolocationManager` walks the styled DOM for
665    /// these at end-of-layout and starts / stops the matching native
666    /// subscription. See `SUPER_PLAN_2.md` §1.5 + research/08.
667    GeolocationProbe(crate::geolocation::GeolocationProbeConfig),
668    /// THE canonical page-break element: an empty block the UA styles with
669    /// `break-before: page`. The pagination estimator and a screen DOM
670    /// treat it identically; sibling margins collapse through it, so
671    /// materializing an estimated break does not move content. XML tag:
672    /// `<pagebreak/>`; constructor: [`Dom::create_page_break`].
673    PageBreak,
674}
675
676/// Type alias: `BoxOrStatic<ImageRef>` — used by `NodeType::Image` for FFI monomorphization.
677pub type BoxOrStaticImageRef = BoxOrStatic<ImageRef>;
678
679impl_option!(
680    NodeType,
681    OptionNodeType,
682    copy = false,
683    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
684);
685
686impl NodeType {
687    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
688    fn to_library_owned_nodetype(&self) -> Self {
689        use self::NodeType::{
690            Abbr, Acronym, Address, After, Area, Article, Aside, Audio, Base, Bdi, Bdo, Before,
691            Big, BlockQuote, Body, Br, Button, Canvas, Caption, Cite, Code, Col, ColGroup, Data,
692            DataList, Dd, Del, Details, Dfn, Dialog, Dir, Div, Dl, Dt, Em, Embed, FieldSet,
693            FigCaption, Figure, Footer, Form, GeolocationProbe, Head, Header, Hr, Html, Icon,
694            Image, Input, Ins, Kbd, Label, Legend, Li, Link, Main, Map, Mark, Marker, Menu,
695            MenuItem, Meta, Meter, Nav, Object, Ol, OptGroup, Output, Param, Placeholder, Pre,
696            Progress, Rp, Rt, Rtc, Ruby, Samp, Script, Section, Select, SelectOption, Small,
697            Source, Span, Strong, Style, Sub, Summary, Sup, Svg, SvgA, SvgAnimate,
698            SvgAnimateMotion, SvgAnimateTransform, SvgCircle, SvgClipPathElement, SvgDefs, SvgDesc,
699            SvgEllipse, SvgFeBlend, SvgFeColorMatrix, SvgFeComponentTransfer, SvgFeComposite,
700            SvgFeConvolveMatrix, SvgFeDiffuseLighting, SvgFeDisplacementMap, SvgFeDistantLight,
701            SvgFeDropShadow, SvgFeFlood, SvgFeFuncA, SvgFeFuncB, SvgFeFuncG, SvgFeFuncR,
702            SvgFeGaussianBlur, SvgFeImage, SvgFeMerge, SvgFeMergeNode, SvgFeMorphology,
703            SvgFeOffset, SvgFePointLight, SvgFeSpecularLighting, SvgFeSpotLight, SvgFeTile,
704            SvgFeTurbulence, SvgFilter, SvgForeignObject, SvgG, SvgImage, SvgLine,
705            SvgLinearGradient, SvgMarker, SvgMask, SvgMetadata, SvgMpath, SvgPath, SvgPattern,
706            SvgPolygon, SvgPolyline, SvgRadialGradient, SvgRect, SvgScript, SvgSet, SvgStop,
707            SvgStyle, SvgSwitch, SvgSymbol, SvgText, SvgTextPath, SvgTitle, SvgTspan, SvgUse,
708            SvgView, TBody, TFoot, THead, Table, Td, Text, TextArea, Th, Time, Title, Tr, Track,
709            Ul, Var, Video, VirtualView, Wbr, A, B, H1, H2, H3, H4, H5, H6, I, P, Q, S, U,
710        };
711        match self {
712            Html => Html,
713            Head => Head,
714            Body => Body,
715            Div => Div,
716            P => P,
717            Article => Article,
718            Section => Section,
719            Nav => Nav,
720            Aside => Aside,
721            Header => Header,
722            Footer => Footer,
723            Main => Main,
724            Figure => Figure,
725            FigCaption => FigCaption,
726            H1 => H1,
727            H2 => H2,
728            H3 => H3,
729            H4 => H4,
730            H5 => H5,
731            H6 => H6,
732            Br => Br,
733            Hr => Hr,
734            Pre => Pre,
735            BlockQuote => BlockQuote,
736            Address => Address,
737            Details => Details,
738            Summary => Summary,
739            Dialog => Dialog,
740            Ul => Ul,
741            Ol => Ol,
742            Li => Li,
743            Dl => Dl,
744            Dt => Dt,
745            Dd => Dd,
746            Menu => Menu,
747            MenuItem => MenuItem,
748            Dir => Dir,
749            Table => Table,
750            Caption => Caption,
751            THead => THead,
752            TBody => TBody,
753            TFoot => TFoot,
754            Tr => Tr,
755            Th => Th,
756            Td => Td,
757            ColGroup => ColGroup,
758            Col => Col,
759            Form => Form,
760            FieldSet => FieldSet,
761            Legend => Legend,
762            Label => Label,
763            Input => Input,
764            Button => Button,
765            Select => Select,
766            OptGroup => OptGroup,
767            SelectOption => SelectOption,
768            TextArea => TextArea,
769            Output => Output,
770            Progress => Progress,
771            Meter => Meter,
772            DataList => DataList,
773            Span => Span,
774            A => A,
775            Em => Em,
776            Strong => Strong,
777            B => B,
778            I => I,
779            U => U,
780            S => S,
781            Mark => Mark,
782            Del => Del,
783            Ins => Ins,
784            Code => Code,
785            Samp => Samp,
786            Kbd => Kbd,
787            Var => Var,
788            Cite => Cite,
789            Dfn => Dfn,
790            Abbr => Abbr,
791            Acronym => Acronym,
792            Q => Q,
793            Time => Time,
794            Sub => Sub,
795            Sup => Sup,
796            Small => Small,
797            Big => Big,
798            Bdo => Bdo,
799            Bdi => Bdi,
800            Wbr => Wbr,
801            Ruby => Ruby,
802            Rt => Rt,
803            Rtc => Rtc,
804            Rp => Rp,
805            Data => Data,
806            Canvas => Canvas,
807            Object => Object,
808            Param => Param,
809            Embed => Embed,
810            Audio => Audio,
811            Video => Video,
812            Source => Source,
813            Track => Track,
814            Map => Map,
815            Area => Area,
816            // SVG container
817            Svg => Svg,
818            SvgG => SvgG,
819            SvgDefs => SvgDefs,
820            SvgSymbol => SvgSymbol,
821            SvgUse => SvgUse,
822            SvgSwitch => SvgSwitch,
823            // SVG shape
824            SvgPath => SvgPath,
825            SvgCircle => SvgCircle,
826            SvgRect => SvgRect,
827            SvgEllipse => SvgEllipse,
828            SvgLine => SvgLine,
829            SvgPolygon => SvgPolygon,
830            SvgPolyline => SvgPolyline,
831            // SVG text
832            SvgText(s) => SvgText(s.clone_self()),
833            SvgTspan => SvgTspan,
834            SvgTextPath => SvgTextPath,
835            // SVG paint
836            SvgLinearGradient => SvgLinearGradient,
837            SvgRadialGradient => SvgRadialGradient,
838            SvgStop => SvgStop,
839            SvgPattern => SvgPattern,
840            // SVG clip/mask
841            SvgClipPathElement => SvgClipPathElement,
842            SvgMask => SvgMask,
843            // SVG filter
844            SvgFilter => SvgFilter,
845            SvgFeBlend => SvgFeBlend,
846            SvgFeColorMatrix => SvgFeColorMatrix,
847            SvgFeComponentTransfer => SvgFeComponentTransfer,
848            SvgFeComposite => SvgFeComposite,
849            SvgFeConvolveMatrix => SvgFeConvolveMatrix,
850            SvgFeDiffuseLighting => SvgFeDiffuseLighting,
851            SvgFeDisplacementMap => SvgFeDisplacementMap,
852            SvgFeDistantLight => SvgFeDistantLight,
853            SvgFeDropShadow => SvgFeDropShadow,
854            SvgFeFlood => SvgFeFlood,
855            SvgFeFuncR => SvgFeFuncR,
856            SvgFeFuncG => SvgFeFuncG,
857            SvgFeFuncB => SvgFeFuncB,
858            SvgFeFuncA => SvgFeFuncA,
859            SvgFeGaussianBlur => SvgFeGaussianBlur,
860            SvgFeImage => SvgFeImage,
861            SvgFeMerge => SvgFeMerge,
862            SvgFeMergeNode => SvgFeMergeNode,
863            SvgFeMorphology => SvgFeMorphology,
864            SvgFeOffset => SvgFeOffset,
865            SvgFePointLight => SvgFePointLight,
866            SvgFeSpecularLighting => SvgFeSpecularLighting,
867            SvgFeSpotLight => SvgFeSpotLight,
868            SvgFeTile => SvgFeTile,
869            SvgFeTurbulence => SvgFeTurbulence,
870            // SVG marker/image/foreign
871            SvgMarker => SvgMarker,
872            SvgImage(i) => SvgImage(i.clone()),
873            SvgForeignObject => SvgForeignObject,
874            // SVG descriptive/structural
875            SvgTitle => SvgTitle,
876            SvgDesc => SvgDesc,
877            SvgMetadata => SvgMetadata,
878            SvgA => SvgA,
879            SvgView => SvgView,
880            SvgStyle => SvgStyle,
881            SvgScript => SvgScript,
882            // SVG animation
883            SvgAnimate => SvgAnimate,
884            SvgAnimateMotion => SvgAnimateMotion,
885            SvgAnimateTransform => SvgAnimateTransform,
886            SvgSet => SvgSet,
887            SvgMpath => SvgMpath,
888            // HTML metadata
889            Title => Title,
890            Meta => Meta,
891            Link => Link,
892            Script => Script,
893            Style => Style,
894            Base => Base,
895            Before => Before,
896            After => After,
897            Marker => Marker,
898            Placeholder => Placeholder,
899
900            Text(s) => Text(BoxOrStatic::heap(s.clone_self())),
901            Image(i) => Image(i.clone()),
902            VirtualView => VirtualView,
903            Self::TransientWindow(c) => Self::TransientWindow(*c),
904            Icon(s) => Icon(BoxOrStatic::heap(s.clone_self())),
905            GeolocationProbe(cfg) => GeolocationProbe(*cfg),
906            Self::PageBreak => Self::PageBreak,
907        }
908    }
909
910    #[must_use]
911    pub fn format(&self) -> Option<String> {
912        use self::NodeType::{GeolocationProbe, Icon, Image, Text, TransientWindow, VirtualView};
913        match self {
914            Text(s) => Some(format!("{s}")),
915            Image(id) => Some(format!("image({id:?})")),
916            VirtualView => Some("virtualized-view".to_string()),
917            TransientWindow(c) => Some(format!("transient-window(open={})", c.open)),
918            Icon(s) => Some(format!("icon({s})")),
919            GeolocationProbe(cfg) => Some(format!(
920                "geolocation-probe(hi={}, bg={}, max={}m, every={}ms)",
921                cfg.high_accuracy, cfg.background, cfg.max_accuracy_m, cfg.min_interval_ms
922            )),
923            _ => None,
924        }
925    }
926
927    /// Returns the `NodeTypeTag` for CSS selector matching.
928    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
929    #[must_use]
930    pub const fn get_path(&self) -> NodeTypeTag {
931        match self {
932            Self::Html => NodeTypeTag::Html,
933            Self::Head => NodeTypeTag::Head,
934            Self::Body => NodeTypeTag::Body,
935            Self::Div => NodeTypeTag::Div,
936            Self::P => NodeTypeTag::P,
937            Self::Article => NodeTypeTag::Article,
938            Self::Section => NodeTypeTag::Section,
939            Self::Nav => NodeTypeTag::Nav,
940            Self::Aside => NodeTypeTag::Aside,
941            Self::Header => NodeTypeTag::Header,
942            Self::Footer => NodeTypeTag::Footer,
943            Self::Main => NodeTypeTag::Main,
944            Self::Figure => NodeTypeTag::Figure,
945            Self::FigCaption => NodeTypeTag::FigCaption,
946            Self::H1 => NodeTypeTag::H1,
947            Self::H2 => NodeTypeTag::H2,
948            Self::H3 => NodeTypeTag::H3,
949            Self::H4 => NodeTypeTag::H4,
950            Self::H5 => NodeTypeTag::H5,
951            Self::H6 => NodeTypeTag::H6,
952            Self::Br => NodeTypeTag::Br,
953            Self::Hr => NodeTypeTag::Hr,
954            Self::Pre => NodeTypeTag::Pre,
955            Self::BlockQuote => NodeTypeTag::BlockQuote,
956            Self::Address => NodeTypeTag::Address,
957            Self::Details => NodeTypeTag::Details,
958            Self::Summary => NodeTypeTag::Summary,
959            Self::Dialog => NodeTypeTag::Dialog,
960            Self::Ul => NodeTypeTag::Ul,
961            Self::Ol => NodeTypeTag::Ol,
962            Self::Li => NodeTypeTag::Li,
963            Self::Dl => NodeTypeTag::Dl,
964            Self::Dt => NodeTypeTag::Dt,
965            Self::Dd => NodeTypeTag::Dd,
966            Self::Menu => NodeTypeTag::Menu,
967            Self::MenuItem => NodeTypeTag::MenuItem,
968            Self::Dir => NodeTypeTag::Dir,
969            Self::Table => NodeTypeTag::Table,
970            Self::Caption => NodeTypeTag::Caption,
971            Self::THead => NodeTypeTag::THead,
972            Self::TBody => NodeTypeTag::TBody,
973            Self::TFoot => NodeTypeTag::TFoot,
974            Self::Tr => NodeTypeTag::Tr,
975            Self::Th => NodeTypeTag::Th,
976            Self::Td => NodeTypeTag::Td,
977            Self::ColGroup => NodeTypeTag::ColGroup,
978            Self::Col => NodeTypeTag::Col,
979            Self::Form => NodeTypeTag::Form,
980            Self::FieldSet => NodeTypeTag::FieldSet,
981            Self::Legend => NodeTypeTag::Legend,
982            Self::Label => NodeTypeTag::Label,
983            Self::Input => NodeTypeTag::Input,
984            Self::Button => NodeTypeTag::Button,
985            Self::Select => NodeTypeTag::Select,
986            Self::OptGroup => NodeTypeTag::OptGroup,
987            Self::SelectOption => NodeTypeTag::SelectOption,
988            Self::TextArea => NodeTypeTag::TextArea,
989            Self::Output => NodeTypeTag::Output,
990            Self::Progress => NodeTypeTag::Progress,
991            Self::Meter => NodeTypeTag::Meter,
992            Self::DataList => NodeTypeTag::DataList,
993            Self::Span => NodeTypeTag::Span,
994            Self::A => NodeTypeTag::A,
995            Self::Em => NodeTypeTag::Em,
996            Self::Strong => NodeTypeTag::Strong,
997            Self::B => NodeTypeTag::B,
998            Self::I => NodeTypeTag::I,
999            Self::U => NodeTypeTag::U,
1000            Self::S => NodeTypeTag::S,
1001            Self::Mark => NodeTypeTag::Mark,
1002            Self::Del => NodeTypeTag::Del,
1003            Self::Ins => NodeTypeTag::Ins,
1004            Self::Code => NodeTypeTag::Code,
1005            Self::Samp => NodeTypeTag::Samp,
1006            Self::Kbd => NodeTypeTag::Kbd,
1007            Self::Var => NodeTypeTag::Var,
1008            Self::Cite => NodeTypeTag::Cite,
1009            Self::Dfn => NodeTypeTag::Dfn,
1010            Self::Abbr => NodeTypeTag::Abbr,
1011            Self::Acronym => NodeTypeTag::Acronym,
1012            Self::Q => NodeTypeTag::Q,
1013            Self::Time => NodeTypeTag::Time,
1014            Self::Sub => NodeTypeTag::Sub,
1015            Self::Sup => NodeTypeTag::Sup,
1016            Self::Small => NodeTypeTag::Small,
1017            Self::Big => NodeTypeTag::Big,
1018            Self::Bdo => NodeTypeTag::Bdo,
1019            Self::Bdi => NodeTypeTag::Bdi,
1020            Self::Wbr => NodeTypeTag::Wbr,
1021            Self::Ruby => NodeTypeTag::Ruby,
1022            Self::Rt => NodeTypeTag::Rt,
1023            Self::Rtc => NodeTypeTag::Rtc,
1024            Self::Rp => NodeTypeTag::Rp,
1025            Self::Data => NodeTypeTag::Data,
1026            Self::Canvas => NodeTypeTag::Canvas,
1027            Self::Object => NodeTypeTag::Object,
1028            Self::Param => NodeTypeTag::Param,
1029            Self::Embed => NodeTypeTag::Embed,
1030            Self::Audio => NodeTypeTag::Audio,
1031            Self::Video => NodeTypeTag::Video,
1032            Self::Source => NodeTypeTag::Source,
1033            Self::Track => NodeTypeTag::Track,
1034            Self::Map => NodeTypeTag::Map,
1035            Self::Area => NodeTypeTag::Area,
1036            // SVG — all variants map 1:1 to NodeTypeTag
1037            Self::Svg => NodeTypeTag::Svg,
1038            Self::SvgG => NodeTypeTag::SvgG,
1039            Self::SvgDefs => NodeTypeTag::SvgDefs,
1040            Self::SvgSymbol => NodeTypeTag::SvgSymbol,
1041            Self::SvgUse => NodeTypeTag::SvgUse,
1042            Self::SvgSwitch => NodeTypeTag::SvgSwitch,
1043            Self::SvgPath => NodeTypeTag::SvgPath,
1044            Self::SvgCircle => NodeTypeTag::SvgCircle,
1045            Self::SvgRect => NodeTypeTag::SvgRect,
1046            Self::SvgEllipse => NodeTypeTag::SvgEllipse,
1047            Self::SvgLine => NodeTypeTag::SvgLine,
1048            Self::SvgPolygon => NodeTypeTag::SvgPolygon,
1049            Self::SvgPolyline => NodeTypeTag::SvgPolyline,
1050            Self::SvgText(_) => NodeTypeTag::SvgText,
1051            Self::SvgTspan => NodeTypeTag::SvgTspan,
1052            Self::SvgTextPath => NodeTypeTag::SvgTextPath,
1053            Self::SvgLinearGradient => NodeTypeTag::SvgLinearGradient,
1054            Self::SvgRadialGradient => NodeTypeTag::SvgRadialGradient,
1055            Self::SvgStop => NodeTypeTag::SvgStop,
1056            Self::SvgPattern => NodeTypeTag::SvgPattern,
1057            Self::SvgClipPathElement => NodeTypeTag::SvgClipPathElement,
1058            Self::SvgMask => NodeTypeTag::SvgMask,
1059            Self::SvgFilter => NodeTypeTag::SvgFilter,
1060            Self::SvgFeBlend => NodeTypeTag::SvgFeBlend,
1061            Self::SvgFeColorMatrix => NodeTypeTag::SvgFeColorMatrix,
1062            Self::SvgFeComponentTransfer => NodeTypeTag::SvgFeComponentTransfer,
1063            Self::SvgFeComposite => NodeTypeTag::SvgFeComposite,
1064            Self::SvgFeConvolveMatrix => NodeTypeTag::SvgFeConvolveMatrix,
1065            Self::SvgFeDiffuseLighting => NodeTypeTag::SvgFeDiffuseLighting,
1066            Self::SvgFeDisplacementMap => NodeTypeTag::SvgFeDisplacementMap,
1067            Self::SvgFeDistantLight => NodeTypeTag::SvgFeDistantLight,
1068            Self::SvgFeDropShadow => NodeTypeTag::SvgFeDropShadow,
1069            Self::SvgFeFlood => NodeTypeTag::SvgFeFlood,
1070            Self::SvgFeFuncR => NodeTypeTag::SvgFeFuncR,
1071            Self::SvgFeFuncG => NodeTypeTag::SvgFeFuncG,
1072            Self::SvgFeFuncB => NodeTypeTag::SvgFeFuncB,
1073            Self::SvgFeFuncA => NodeTypeTag::SvgFeFuncA,
1074            Self::SvgFeGaussianBlur => NodeTypeTag::SvgFeGaussianBlur,
1075            Self::SvgFeImage => NodeTypeTag::SvgFeImage,
1076            Self::SvgFeMerge => NodeTypeTag::SvgFeMerge,
1077            Self::SvgFeMergeNode => NodeTypeTag::SvgFeMergeNode,
1078            Self::SvgFeMorphology => NodeTypeTag::SvgFeMorphology,
1079            Self::SvgFeOffset => NodeTypeTag::SvgFeOffset,
1080            Self::SvgFePointLight => NodeTypeTag::SvgFePointLight,
1081            Self::SvgFeSpecularLighting => NodeTypeTag::SvgFeSpecularLighting,
1082            Self::SvgFeSpotLight => NodeTypeTag::SvgFeSpotLight,
1083            Self::SvgFeTile => NodeTypeTag::SvgFeTile,
1084            Self::SvgFeTurbulence => NodeTypeTag::SvgFeTurbulence,
1085            Self::SvgMarker => NodeTypeTag::SvgMarker,
1086            Self::SvgImage(_) => NodeTypeTag::SvgImage,
1087            Self::SvgForeignObject => NodeTypeTag::SvgForeignObject,
1088            Self::SvgTitle => NodeTypeTag::SvgTitle,
1089            Self::SvgDesc => NodeTypeTag::SvgDesc,
1090            Self::SvgMetadata => NodeTypeTag::SvgMetadata,
1091            Self::SvgA => NodeTypeTag::SvgA,
1092            Self::SvgView => NodeTypeTag::SvgView,
1093            Self::SvgStyle => NodeTypeTag::SvgStyle,
1094            Self::SvgScript => NodeTypeTag::SvgScript,
1095            Self::SvgAnimate => NodeTypeTag::SvgAnimate,
1096            Self::SvgAnimateMotion => NodeTypeTag::SvgAnimateMotion,
1097            Self::SvgAnimateTransform => NodeTypeTag::SvgAnimateTransform,
1098            Self::SvgSet => NodeTypeTag::SvgSet,
1099            Self::SvgMpath => NodeTypeTag::SvgMpath,
1100            // HTML metadata
1101            Self::Title => NodeTypeTag::Title,
1102            Self::Meta => NodeTypeTag::Meta,
1103            Self::Link => NodeTypeTag::Link,
1104            Self::Script => NodeTypeTag::Script,
1105            Self::Style => NodeTypeTag::Style,
1106            Self::Base => NodeTypeTag::Base,
1107            Self::Text(_) => NodeTypeTag::Text,
1108            Self::Image(_) => NodeTypeTag::Img,
1109            Self::VirtualView => NodeTypeTag::VirtualView,
1110            Self::TransientWindow(_) => NodeTypeTag::TransientWindow,
1111            Self::Icon(_) => NodeTypeTag::Icon,
1112            Self::GeolocationProbe(_) => NodeTypeTag::GeolocationProbe,
1113            Self::PageBreak => NodeTypeTag::PageBreak,
1114            Self::Before => NodeTypeTag::Before,
1115            Self::After => NodeTypeTag::After,
1116            Self::Marker => NodeTypeTag::Marker,
1117            Self::Placeholder => NodeTypeTag::Placeholder,
1118        }
1119    }
1120
1121    /// Returns whether this node type is a semantic HTML element that should
1122    /// automatically generate an accessibility tree node.
1123    ///
1124    /// These are elements with inherent semantic meaning that assistive
1125    /// technologies should be aware of, even without explicit ARIA attributes.
1126    #[must_use]
1127    pub const fn is_semantic_for_accessibility(&self) -> bool {
1128        matches!(
1129            self,
1130            Self::Button
1131                | Self::Input
1132                | Self::TextArea
1133                | Self::Select
1134                | Self::A
1135                | Self::H1
1136                | Self::H2
1137                | Self::H3
1138                | Self::H4
1139                | Self::H5
1140                | Self::H6
1141                | Self::Article
1142                | Self::Section
1143                | Self::Nav
1144                | Self::Main
1145                | Self::Header
1146                | Self::Footer
1147                | Self::Aside
1148        )
1149    }
1150}
1151
1152/// Represents the CSS formatting context for an element
1153#[derive(Clone, Copy, PartialEq, Eq)]
1154// [g147f az-web-lift] `#[repr(C, u8)]` forces an explicit u8 discriminant at offset 0 instead of letting
1155// Rust niche-pack the other variants' discriminants into the payload variants' (Block{bool}/Float/OutOfFlow)
1156// invalid byte values. The remill lift mis-decodes that niche encoding: `Block` (byte 0/1) reads correctly
1157// but `Inline` (a niche value) reads as garbage → `match` falls to `_` → nested <div>text</div> dispatches
1158// to layout_bfc instead of layout_ifc and its text never lays out (g147 root cause). Same fix pattern as the
1159// text3 enums (InlineContent/LogicalItem/ShapedItem/FontStack/LayoutError). Harmless + correct for native.
1160#[repr(C, u8)]
1161// +spec:display-property:844893 - block-level box establishing a new formatting context (BFC) modeled here
1162pub enum FormattingContext {
1163    /// Block-level formatting context
1164    Block {
1165        /// Whether this element establishes a new block formatting context
1166        establishes_new_context: bool,
1167    },
1168    /// Inline-level formatting context
1169    Inline,
1170    /// Inline-block (participates in an IFC but creates a BFC)
1171    InlineBlock,
1172    /// Flex formatting context
1173    Flex,
1174    /// Float (left or right)
1175    Float(LayoutFloat),
1176    /// Absolutely positioned (out of flow)
1177    OutOfFlow(LayoutPosition),
1178    /// Table formatting context (container)
1179    Table,
1180    /// Table row group formatting context (thead, tbody, tfoot)
1181    TableRowGroup,
1182    /// Table row formatting context
1183    TableRow,
1184    /// Table cell formatting context (td, th)
1185    TableCell,
1186    /// Table column group formatting context
1187    TableColumnGroup,
1188    /// Table caption formatting context
1189    TableCaption,
1190    /// Grid formatting context
1191    Grid,
1192    /// display:contents - element generates no box, children promoted to parent
1193    Contents,
1194    /// No formatting context (display: none)
1195    None,
1196}
1197
1198impl fmt::Debug for FormattingContext {
1199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1200        match self {
1201            Self::Block {
1202                establishes_new_context,
1203            } => write!(
1204                f,
1205                "Block {{ establishes_new_context: {establishes_new_context:?} }}"
1206            ),
1207            Self::Inline => write!(f, "Inline"),
1208            Self::InlineBlock => write!(f, "InlineBlock"),
1209            Self::Flex => write!(f, "Flex"),
1210            Self::Float(layout_float) => write!(f, "Float({layout_float:?})"),
1211            Self::OutOfFlow(layout_position) => {
1212                write!(f, "OutOfFlow({layout_position:?})")
1213            }
1214            Self::Grid => write!(f, "Grid"),
1215            Self::None => write!(f, "None"),
1216            Self::Table => write!(f, "Table"),
1217            Self::TableRowGroup => write!(f, "TableRowGroup"),
1218            Self::TableRow => write!(f, "TableRow"),
1219            Self::TableCell => write!(f, "TableCell"),
1220            Self::TableColumnGroup => write!(f, "TableColumnGroup"),
1221            Self::TableCaption => write!(f, "TableCaption"),
1222            Self::Contents => write!(f, "Contents"),
1223        }
1224    }
1225}
1226
1227impl Default for FormattingContext {
1228    fn default() -> Self {
1229        Self::Block {
1230            establishes_new_context: false,
1231        }
1232    }
1233}
1234
1235/// Defines the type of event that can trigger a callback action.
1236#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1237#[repr(C)]
1238pub enum On {
1239    /// Mouse cursor is hovering over the element.
1240    MouseOver,
1241    /// Mouse cursor is over the element and a button is pressed.
1242    ///
1243    /// NOT a click: a press is not an activation (the user can still release
1244    /// elsewhere, and it never fires for keyboard or assistive-technology
1245    /// activation). Use [`On::Click`] for "the user activated this".
1246    MouseDown,
1247    /// (Specialization of `MouseDown`). Fires only if the left mouse button
1248    /// has been pressed while cursor was over the element.
1249    LeftMouseDown,
1250    /// (Specialization of `MouseDown`). Fires only if the middle mouse button
1251    /// has been pressed while cursor was over the element.
1252    MiddleMouseDown,
1253    /// (Specialization of `MouseDown`). Fires only if the right mouse button
1254    /// has been pressed while cursor was over the element.
1255    RightMouseDown,
1256    /// The element was ACTIVATED: pressed and released on the same node (W3C
1257    /// `click`), or activated from the keyboard (Enter / Space on a focused
1258    /// element) or by an assistive technology's default action.
1259    ///
1260    /// THIS is what a "click handler" wants. `MouseUp` is the raw pointer
1261    /// release: it fires for any button, it fires when the press began
1262    /// elsewhere, and it never fires for a keyboard or screen-reader
1263    /// activation - which is why activation handlers written against it were
1264    /// unreachable from the keyboard (2026-09-01).
1265    Click,
1266    /// Mouse button has been released while cursor was over the element.
1267    MouseUp,
1268    /// (Specialization of `MouseUp`). Fires only if the left mouse button has
1269    /// been released while cursor was over the element.
1270    LeftMouseUp,
1271    /// (Specialization of `MouseUp`). Fires only if the middle mouse button has
1272    /// been released while cursor was over the element.
1273    MiddleMouseUp,
1274    /// (Specialization of `MouseUp`). Fires only if the right mouse button has
1275    /// been released while cursor was over the element.
1276    RightMouseUp,
1277    /// Mouse cursor has entered the element.
1278    MouseEnter,
1279    /// Mouse cursor has left the element.
1280    MouseLeave,
1281    /// Mousewheel / touchpad scrolling.
1282    Scroll,
1283    /// The window received a unicode character (also respects the system locale).
1284    /// Check `keyboard_state.current_char` to get the current pressed character.
1285    TextInput,
1286    /// A **virtual keycode** was pressed. Note: This is only the virtual keycode,
1287    /// not the actual char. If you want to get the character, use `TextInput` instead.
1288    /// A virtual key does not have to map to a printable character.
1289    ///
1290    /// You can get all currently pressed virtual keycodes in the
1291    /// `keyboard_state.current_virtual_keycodes` and / or just the last keycode in the
1292    /// `keyboard_state.latest_virtual_keycode`.
1293    VirtualKeyDown,
1294    /// A **virtual keycode** was release. See `VirtualKeyDown` for more info.
1295    VirtualKeyUp,
1296    /// A file has been dropped on the element.
1297    HoveredFile,
1298    /// A file is being hovered on the element.
1299    DroppedFile,
1300    /// A file was hovered, but has exited the window.
1301    HoveredFileCancelled,
1302    /// Equivalent to `onfocus`.
1303    FocusReceived,
1304    /// Equivalent to `onblur`.
1305    FocusLost,
1306
1307    // Accessibility-specific events
1308    /// Default action triggered by screen reader (usually same as click/activate)
1309    Default,
1310    /// Element should collapse (e.g., accordion panel, tree node)
1311    Collapse,
1312    /// Element should expand (e.g., accordion panel, tree node)
1313    Expand,
1314    /// Increment value (e.g., number input, slider)
1315    Increment,
1316    /// Decrement value (e.g., number input, slider)
1317    Decrement,
1318    /// A structural document edit (split / merge / wrap / replace…) was
1319    /// recorded on (or under) this element and awaits the app's
1320    /// apply-and-ack (fires once per changeset; focus-scoped, bubbles to
1321    /// the contenteditable root). APPENDED at the enum tail for ABI
1322    /// stability.
1323    DocumentEdit,
1324    /// The focused editable's text was COMMITTED (typing, IME, deletion,
1325    /// paste, undo/redo) - the post-commit counterpart of `TextInput`, which
1326    /// fires before the pending insertion is applied. Read the committed
1327    /// text through `get_unsynced_text_edits`. APPENDED at the enum tail for
1328    /// ABI stability.
1329    TextChanged,
1330    /// Mouse cursor MOVED while over the element. APPENDED at the end.
1331    ///
1332    /// W3C `mousemove`. [`On::MouseOver`] is the ENTRY event; this is the
1333    /// one that fires continuously while the pointer travels across the
1334    /// element.
1335    MouseMove,
1336}
1337
1338/// Contains the necessary information to render an embedded `VirtualView` node.
1339#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1340#[repr(C)]
1341pub struct VirtualViewNode {
1342    /// The callback function that returns the DOM for the virtualized view's content.
1343    pub callback: VirtualViewCallback,
1344    /// The application data passed to the virtualized view's layout callback.
1345    pub refany: RefAny,
1346}
1347
1348/// An enum that holds either a CSS ID or a class name as a string.
1349#[repr(C, u8)]
1350#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1351pub enum IdOrClass {
1352    Id(AzString),
1353    Class(AzString),
1354}
1355
1356impl_option!(
1357    IdOrClass,
1358    OptionIdOrClass,
1359    copy = false,
1360    [Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord]
1361);
1362
1363impl_vec!(
1364    IdOrClass,
1365    IdOrClassVec,
1366    IdOrClassVecDestructor,
1367    IdOrClassVecDestructorType,
1368    IdOrClassVecSlice,
1369    OptionIdOrClass
1370);
1371impl_vec_debug!(IdOrClass, IdOrClassVec);
1372impl_vec_partialord!(IdOrClass, IdOrClassVec);
1373impl_vec_ord!(IdOrClass, IdOrClassVec);
1374impl_vec_clone!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor);
1375impl_vec_partialeq!(IdOrClass, IdOrClassVec);
1376impl_vec_eq!(IdOrClass, IdOrClassVec);
1377impl_vec_hash!(IdOrClass, IdOrClassVec);
1378
1379impl IdOrClass {
1380    #[must_use]
1381    pub fn as_id(&self) -> Option<&str> {
1382        match self {
1383            Self::Id(s) => Some(s.as_str()),
1384            Self::Class(_) => None,
1385        }
1386    }
1387    #[must_use]
1388    pub fn as_class(&self) -> Option<&str> {
1389        match self {
1390            Self::Class(s) => Some(s.as_str()),
1391            Self::Id(_) => None,
1392        }
1393    }
1394}
1395
1396/// Name-value pair for custom attributes (data-*, aria-*, etc.)
1397#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1398#[repr(C)]
1399pub struct AttributeNameValue {
1400    pub attr_name: AzString,
1401    pub value: AzString,
1402}
1403
1404/// Strongly-typed HTML attribute with type-safe values.
1405///
1406/// This enum provides a type-safe way to represent HTML attributes, ensuring that
1407/// values are validated at compile-time and properly converted to their string
1408/// representations at runtime.
1409#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1410#[repr(C, u8)]
1411pub enum AttributeType {
1412    /// Element ID attribute (`id="..."`)
1413    Id(AzString),
1414    /// CSS class attribute (`class="..."`)
1415    Class(AzString),
1416    /// Accessible name/label (`aria-label="..."`)
1417    AriaLabel(AzString),
1418    /// Element that labels this one (`aria-labelledby="..."`)
1419    AriaLabelledBy(AzString),
1420    /// Element that describes this one (`aria-describedby="..."`)
1421    AriaDescribedBy(AzString),
1422    /// Role for accessibility (`role="..."`)
1423    AriaRole(AzString),
1424    /// Current state of an element (`aria-checked`, `aria-selected`, etc.)
1425    AriaState(AttributeNameValue),
1426    /// ARIA property (`aria-*`)
1427    AriaProperty(AttributeNameValue),
1428
1429    /// Hyperlink target URL (`href="..."`)
1430    Href(AzString),
1431    /// Link relationship (`rel="..."`)
1432    Rel(AzString),
1433    /// Link target frame (`target="..."`)
1434    Target(AzString),
1435
1436    /// Image source URL (`src="..."`)
1437    Src(AzString),
1438    /// Alternative text for images (`alt="..."`)
1439    Alt(AzString),
1440    /// Image title (tooltip) (`title="..."`)
1441    Title(AzString),
1442
1443    /// Form input name (`name="..."`)
1444    Name(AzString),
1445    /// Form input value (`value="..."`)
1446    Value(AzString),
1447    /// Input type (`type="text|password|email|..."`)
1448    InputType(AzString),
1449    /// Placeholder text (`placeholder="..."`)
1450    Placeholder(AzString),
1451    /// Input is required (`required`)
1452    Required,
1453    /// Input is disabled (`disabled`)
1454    Disabled,
1455    /// Input is readonly (`readonly`)
1456    Readonly,
1457    /// Input is checked (checkbox/radio) (`checked`)
1458    CheckedTrue,
1459    /// Input is unchecked (checkbox/radio)
1460    CheckedFalse,
1461    /// Input is selected (option) (`selected`)
1462    Selected,
1463    /// Maximum value for number inputs (`max="..."`)
1464    Max(AzString),
1465    /// Minimum value for number inputs (`min="..."`)
1466    Min(AzString),
1467    /// Step value for number inputs (`step="..."`)
1468    Step(AzString),
1469    /// Input pattern for validation (`pattern="..."`)
1470    Pattern(AzString),
1471    /// Minimum length (`minlength="..."`)
1472    MinLength(i32),
1473    /// Maximum length (`maxlength="..."`)
1474    MaxLength(i32),
1475    /// Autocomplete behavior (`autocomplete="on|off|..."`)
1476    Autocomplete(AzString),
1477
1478    /// Table header scope (`scope="row|col|rowgroup|colgroup"`)
1479    Scope(AzString),
1480    /// Number of columns to span (`colspan="..."`)
1481    ColSpan(i32),
1482    /// Number of rows to span (`rowspan="..."`)
1483    RowSpan(i32),
1484
1485    /// Tab index for keyboard navigation (`tabindex="..."`)
1486    TabIndex(i32),
1487    /// Element can receive focus (`tabindex="0"` equivalent)
1488    Focusable,
1489    /// Take focus when the window first shows this element (`autofocus`).
1490    ///
1491    /// HTML semantics: the FIRST autofocus element in the document wins, and
1492    /// it is focused WITHOUT showing a focus ring - the ring is for keyboard
1493    /// navigation (`:focus-visible`), and a form that opens with its first
1494    /// field ringed looks like the user pressed Tab when they did not.
1495    Autofocus,
1496
1497    /// Language code (`lang="..."`)
1498    Lang(AzString),
1499    /// Text direction (`dir="ltr|rtl|auto"`)
1500    Dir(AzString),
1501
1502    /// Content is editable (`contenteditable="true|false"`)
1503    ContentEditable(bool),
1504    /// Element is draggable (`draggable="true|false"`)
1505    Draggable(bool),
1506    /// Element is hidden (`hidden`)
1507    Hidden,
1508
1509    /// Generic data attribute (`data-*="..."`)
1510    Data(AttributeNameValue),
1511    /// Generic custom attribute (for future extensibility)
1512    Custom(AttributeNameValue),
1513}
1514
1515impl_option!(
1516    AttributeType,
1517    OptionAttributeType,
1518    copy = false,
1519    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1520);
1521
1522impl_vec!(
1523    AttributeType,
1524    AttributeTypeVec,
1525    AttributeTypeVecDestructor,
1526    AttributeTypeVecDestructorType,
1527    AttributeTypeVecSlice,
1528    OptionAttributeType
1529);
1530impl_vec_debug!(AttributeType, AttributeTypeVec);
1531impl_vec_partialord!(AttributeType, AttributeTypeVec);
1532impl_vec_ord!(AttributeType, AttributeTypeVec);
1533impl_vec_clone!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor);
1534impl_vec_partialeq!(AttributeType, AttributeTypeVec);
1535impl_vec_eq!(AttributeType, AttributeTypeVec);
1536impl_vec_hash!(AttributeType, AttributeTypeVec);
1537
1538impl AttributeType {
1539    /// Returns the id string if this is an `Id` attribute, `None` otherwise.
1540    #[must_use]
1541    pub fn as_id(&self) -> Option<&str> {
1542        match self {
1543            Self::Id(s) => Some(s.as_str()),
1544            _ => None,
1545        }
1546    }
1547    /// Returns the class string if this is a `Class` attribute, `None` otherwise.
1548    #[must_use]
1549    pub fn as_class(&self) -> Option<&str> {
1550        match self {
1551            Self::Class(s) => Some(s.as_str()),
1552            _ => None,
1553        }
1554    }
1555    /// Get the attribute name (e.g., "href", "aria-label", "data-foo")
1556    #[must_use]
1557    pub fn name(&self) -> &str {
1558        match self {
1559            Self::Id(_) => "id",
1560            Self::Class(_) => "class",
1561            Self::AriaLabel(_) => "aria-label",
1562            Self::AriaLabelledBy(_) => "aria-labelledby",
1563            Self::AriaDescribedBy(_) => "aria-describedby",
1564            Self::AriaRole(_) => "role",
1565            Self::AriaState(nv) | Self::AriaProperty(nv) | Self::Data(nv) | Self::Custom(nv) => {
1566                nv.attr_name.as_str()
1567            }
1568            Self::Href(_) => "href",
1569            Self::Rel(_) => "rel",
1570            Self::Target(_) => "target",
1571            Self::Src(_) => "src",
1572            Self::Alt(_) => "alt",
1573            Self::Title(_) => "title",
1574            Self::Name(_) => "name",
1575            Self::Value(_) => "value",
1576            Self::InputType(_) => "type",
1577            Self::Placeholder(_) => "placeholder",
1578            Self::Required => "required",
1579            Self::Disabled => "disabled",
1580            Self::Readonly => "readonly",
1581            Self::CheckedTrue | Self::CheckedFalse => "checked",
1582            Self::Selected => "selected",
1583            Self::Max(_) => "max",
1584            Self::Min(_) => "min",
1585            Self::Step(_) => "step",
1586            Self::Pattern(_) => "pattern",
1587            Self::MinLength(_) => "minlength",
1588            Self::MaxLength(_) => "maxlength",
1589            Self::Autocomplete(_) => "autocomplete",
1590            Self::Scope(_) => "scope",
1591            Self::ColSpan(_) => "colspan",
1592            Self::RowSpan(_) => "rowspan",
1593            Self::TabIndex(_) | Self::Focusable => "tabindex",
1594            Self::Autofocus => "autofocus",
1595            Self::Lang(_) => "lang",
1596            Self::Dir(_) => "dir",
1597            Self::ContentEditable(_) => "contenteditable",
1598            Self::Draggable(_) => "draggable",
1599            Self::Hidden => "hidden",
1600        }
1601    }
1602
1603    /// Get the attribute value as a string
1604    #[must_use]
1605    pub fn value(&self) -> AzString {
1606        match self {
1607            Self::Id(v)
1608            | Self::Class(v)
1609            | Self::AriaLabel(v)
1610            | Self::AriaLabelledBy(v)
1611            | Self::AriaDescribedBy(v)
1612            | Self::AriaRole(v)
1613            | Self::Href(v)
1614            | Self::Rel(v)
1615            | Self::Target(v)
1616            | Self::Src(v)
1617            | Self::Alt(v)
1618            | Self::Title(v)
1619            | Self::Name(v)
1620            | Self::Value(v)
1621            | Self::InputType(v)
1622            | Self::Placeholder(v)
1623            | Self::Max(v)
1624            | Self::Min(v)
1625            | Self::Step(v)
1626            | Self::Pattern(v)
1627            | Self::Autocomplete(v)
1628            | Self::Scope(v)
1629            | Self::Lang(v)
1630            | Self::Dir(v) => v.clone(),
1631
1632            Self::AriaState(nv) | Self::AriaProperty(nv) | Self::Data(nv) | Self::Custom(nv) => {
1633                nv.value.clone()
1634            }
1635
1636            Self::MinLength(n)
1637            | Self::MaxLength(n)
1638            | Self::ColSpan(n)
1639            | Self::RowSpan(n)
1640            | Self::TabIndex(n) => n.to_string().into(),
1641
1642            Self::Focusable => "0".into(),
1643            // Boolean attribute: present means true, as in HTML.
1644            Self::Autofocus => "true".into(),
1645            Self::ContentEditable(b) | Self::Draggable(b) => {
1646                if *b {
1647                    "true".into()
1648                } else {
1649                    "false".into()
1650                }
1651            }
1652
1653            Self::Required
1654            | Self::Disabled
1655            | Self::Readonly
1656            | Self::CheckedTrue
1657            | Self::CheckedFalse
1658            | Self::Selected
1659            | Self::Hidden => "".into(), // Boolean attributes
1660        }
1661    }
1662
1663    /// Check if this is a boolean attribute (present = true, absent = false)
1664    #[must_use]
1665    pub const fn is_boolean(&self) -> bool {
1666        matches!(
1667            self,
1668            Self::Required
1669                | Self::Disabled
1670                | Self::Readonly
1671                | Self::CheckedTrue
1672                | Self::CheckedFalse
1673                | Self::Selected
1674                | Self::Hidden
1675        )
1676    }
1677}
1678
1679/// Represents all data associated with a single DOM node, such as its type,
1680/// classes, IDs, callbacks, and inline styles.
1681#[repr(C)]
1682#[derive(Debug)]
1683pub struct NodeData {
1684    /// `div`, `p`, `img`, etc.
1685    pub node_type: NodeType,
1686    /// Callbacks attached to this node:
1687    ///
1688    /// `On::Click` -> `Callback(my_button_click_handler)`
1689    pub callbacks: CoreCallbackDataVec,
1690    /// Inline style: a `Css` value that applies only to this node (implicit `:scope`).
1691    /// Each rule carries conditions (@media/@os/:hover/...) and declarations; rules
1692    /// produced by parsing inline strings are tagged `rule_priority::INLINE`, while
1693    /// widget defaults pushed via `with_css_props` keep the same INLINE priority so
1694    /// they override author CSS — preserving the cascade priority that the previous
1695    /// per-property `css_props` field had.
1696    pub style: azul_css::css::Css,
1697    /// Packed flags: `tab_index` + contenteditable + `is_anonymous`.
1698    pub flags: NodeFlags,
1699    /// Optional extra accessibility information about this DOM node (MSAA, AT-SPI, UA).
1700    /// 8 bytes (Option<Box<T>> is pointer-sized).
1701    pub accessibility: Option<Box<AccessibilityInfo>>,
1702    /// Stores "extra", not commonly used data of the node: clip-mask, menus, etc.
1703    ///
1704    /// SHOULD NOT EXPOSED IN THE API - necessary to retroactively add functionality
1705    /// to the node without breaking the ABI.
1706    extra: Option<Box<NodeDataExt>>,
1707}
1708
1709impl_option!(
1710    NodeData,
1711    OptionNodeData,
1712    copy = false,
1713    [Debug, PartialEq, Eq, PartialOrd, Ord]
1714);
1715
1716// Manual equality/ordering (was derived) for ONE reason: an `extra` that is
1717// identity-empty - it exists only to carry a MARKER - must compare equal to
1718// no `extra` at all. Markers are minted fresh (UUID strings) every `layout()`
1719// and are excluded from node identity wholesale (USER ruling 2026-08-29); the
1720// `NodeDataExt` impls already skip the field, and this normalization covers
1721// the marked-vs-never-extended pair.
1722impl NodeData {
1723    /// `extra` as it counts toward IDENTITY: `None` when absent or when the
1724    /// ext carries nothing but a marker.
1725    fn identity_extra(&self) -> Option<&NodeDataExt> {
1726        self.extra.as_deref().filter(|e| !e.is_identity_empty())
1727    }
1728}
1729impl PartialEq for NodeData {
1730    fn eq(&self, other: &Self) -> bool {
1731        self.node_type == other.node_type
1732            && self.callbacks == other.callbacks
1733            && self.style == other.style
1734            && self.flags == other.flags
1735            && self.accessibility == other.accessibility
1736            && self.identity_extra() == other.identity_extra()
1737    }
1738}
1739impl Eq for NodeData {}
1740impl PartialOrd for NodeData {
1741    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1742        Some(self.cmp(other))
1743    }
1744}
1745impl Ord for NodeData {
1746    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1747        (
1748            &self.node_type,
1749            &self.callbacks,
1750            &self.style,
1751            &self.flags,
1752            &self.accessibility,
1753            self.identity_extra(),
1754        )
1755            .cmp(&(
1756                &other.node_type,
1757                &other.callbacks,
1758                &other.style,
1759                &other.flags,
1760                &other.accessibility,
1761                other.identity_extra(),
1762            ))
1763    }
1764}
1765
1766impl Hash for NodeData {
1767    fn hash<H: Hasher>(&self, state: &mut H) {
1768        self.node_type.hash(state);
1769        self.attributes().as_ref().hash(state);
1770        self.flags.hash(state);
1771
1772        // NOTE: callbacks are NOT hashed regularly, otherwise
1773        // they'd cause inconsistencies because of the scroll callback
1774        for callback in self.callbacks.as_ref() {
1775            callback.event.hash(state);
1776            callback.callback.hash(state);
1777            callback.refany.get_type_id().hash(state);
1778        }
1779
1780        // Hash inline CSS properties (Static declarations only — same set the
1781        // legacy `css_props` field hashed). Conditions are intentionally
1782        // skipped to match the previous behaviour.
1783        for (prop, _conds) in self.style.iter_inline_properties() {
1784            mem::discriminant(prop).hash(state);
1785        }
1786        if let Some(ext) = self.extra.as_ref() {
1787            if let Some(ds) = ext.dataset.as_ref() {
1788                ds.hash(state);
1789            }
1790            if let Some(c) = ext.svg_data.as_ref() {
1791                c.hash(state);
1792            }
1793            if let Some(c) = ext.menu_bar.as_ref() {
1794                c.hash(state);
1795            }
1796            if let Some(c) = ext.context_menu.as_ref() {
1797                c.hash(state);
1798            }
1799            if let Some(vv) = ext.virtual_view.as_ref() {
1800                vv.hash(state);
1801            }
1802        }
1803    }
1804}
1805
1806/// Tracks which component rendered a DOM subtree.
1807///
1808/// When a component's `render_fn` returns a `StyledDom`, the framework stamps the
1809/// root node(s) of the output with a `ComponentOrigin`. This enables:
1810/// - The debugger to show a "Component Tree" alongside the DOM tree
1811/// - Code generation roundtrips (rendered DOM → component invocations → code)
1812/// - Clicking a DOM node to navigate to the component that produced it
1813#[derive(Debug, Clone, PartialEq)]
1814pub struct ComponentOrigin {
1815    /// Qualified component name, e.g. "shadcn:card", "builtin:div"
1816    pub component_id: AzString,
1817    /// Snapshot of the data model at render time, stored as a JSON value.
1818    /// The debug server can inspect typed values; the frontend serializes
1819    /// them back to JSON for display and editing.
1820    pub data_model_json: crate::json::Json,
1821}
1822
1823// Manual impls because Json contains f64 (no Eq/Ord/Hash derive),
1824// but we need them for NodeDataExt. We compare on the Display string.
1825impl Eq for ComponentOrigin {}
1826
1827impl PartialOrd for ComponentOrigin {
1828    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1829        Some(self.cmp(other))
1830    }
1831}
1832
1833impl Ord for ComponentOrigin {
1834    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1835        self.component_id.cmp(&other.component_id).then_with(|| {
1836            let a = alloc::format!("{}", self.data_model_json);
1837            let b = alloc::format!("{}", other.data_model_json);
1838            a.cmp(&b)
1839        })
1840    }
1841}
1842
1843impl Hash for ComponentOrigin {
1844    fn hash<H: Hasher>(&self, state: &mut H) {
1845        self.component_id.hash(state);
1846        alloc::format!("{}", self.data_model_json).hash(state);
1847    }
1848}
1849
1850impl Default for ComponentOrigin {
1851    fn default() -> Self {
1852        Self {
1853            component_id: AzString::from_const_str(""),
1854            data_model_json: crate::json::Json::null(),
1855        }
1856    }
1857}
1858
1859/// SVG-specific data stored on a DOM node.
1860///
1861/// Each SVG element type stores its parsed attribute data here.
1862/// Also used for raster image clip masks (legacy C API).
1863#[derive(Debug, Clone, PartialOrd)]
1864pub enum SvgNodeData {
1865    /// Raster R8 image clip mask (legacy C API for chart.c style manual masks).
1866    ImageClipMask(ImageMask),
1867    /// `<path d="...">` — resolved path geometry.
1868    Path(crate::svg::SvgMultiPolygon),
1869    /// `<circle cx="" cy="" r="">`.
1870    Circle { cx: f32, cy: f32, r: f32 },
1871    /// `<rect x="" y="" width="" height="" rx="" ry="">`.
1872    Rect {
1873        x: f32,
1874        y: f32,
1875        width: f32,
1876        height: f32,
1877        rx: f32,
1878        ry: f32,
1879    },
1880    /// `<ellipse cx="" cy="" rx="" ry="">`.
1881    Ellipse { cx: f32, cy: f32, rx: f32, ry: f32 },
1882    /// `<line x1="" y1="" x2="" y2="">`.
1883    Line { x1: f32, y1: f32, x2: f32, y2: f32 },
1884    /// `<polygon points="">` / `<polyline points="">` — parsed point list.
1885    PointsList {
1886        points: alloc::vec::Vec<azul_css::props::basic::SvgPoint>,
1887        closed: bool,
1888    },
1889    /// `<svg viewBox="" width="" height="">` — viewport attributes.
1890    ViewBox {
1891        min_x: f32,
1892        min_y: f32,
1893        width: f32,
1894        height: f32,
1895    },
1896    /// `<linearGradient>` attributes.
1897    LinearGradient { x1: f32, y1: f32, x2: f32, y2: f32 },
1898    /// `<radialGradient>` attributes.
1899    RadialGradient {
1900        cx: f32,
1901        cy: f32,
1902        r: f32,
1903        fx: f32,
1904        fy: f32,
1905    },
1906    /// `<stop offset="" stop-color="" stop-opacity="">`.
1907    GradientStop { offset: f32 },
1908    /// `<use href="" x="" y="">`.
1909    Use { href: AzString, x: f32, y: f32 },
1910    /// `<image href="" x="" y="" width="" height="">`.
1911    SvgImageData {
1912        href: AzString,
1913        x: f32,
1914        y: f32,
1915        width: f32,
1916        height: f32,
1917    },
1918}
1919
1920// PartialEq compares f32 fields by BIT PATTERN (to_bits), mirroring the Hash impl
1921// below, so a NaN coordinate is equal to itself and Eq/Hash agree. A derived PartialEq
1922// used raw float `==` (NaN != NaN), breaking Eq's reflexivity for e.g. a NaN Rect —
1923// and NodeType embeds this type, so the break propagated.
1924impl PartialEq for SvgNodeData {
1925    #[allow(clippy::match_same_arms, clippy::similar_names, clippy::too_many_lines)] // SVG coord names (cx/cy/fx/fy, min_x/min_y) are domain-standard; one arm per variant
1926    fn eq(&self, other: &Self) -> bool {
1927        // f32 bit-equality (matches Hash's to_bits).
1928        const fn fb(a: f32, b: f32) -> bool {
1929            a.to_bits() == b.to_bits()
1930        }
1931        use self::SvgNodeData::{
1932            Circle, Ellipse, GradientStop, ImageClipMask, Line, LinearGradient, Path, PointsList,
1933            RadialGradient, Rect, SvgImageData, Use, ViewBox,
1934        };
1935        match (self, other) {
1936            (ImageClipMask(a), ImageClipMask(b)) => a == b,
1937            (Path(a), Path(b)) => {
1938                let ra = a.rings.as_ref();
1939                let rb = b.rings.as_ref();
1940                ra.len() == rb.len()
1941                    && ra.iter().zip(rb.iter()).all(|(x, y)| {
1942                        let ia = x.items.as_ref();
1943                        let ib = y.items.as_ref();
1944                        ia.len() == ib.len()
1945                            && ia
1946                                .iter()
1947                                .zip(ib.iter())
1948                                .all(|(p, q)| svg_path_element_bits_eq(p, q))
1949                    })
1950            }
1951            (
1952                Circle { cx, cy, r },
1953                Circle {
1954                    cx: cx2,
1955                    cy: cy2,
1956                    r: r2,
1957                },
1958            ) => fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2),
1959            (
1960                Rect {
1961                    x,
1962                    y,
1963                    width,
1964                    height,
1965                    rx,
1966                    ry,
1967                },
1968                Rect {
1969                    x: x2,
1970                    y: y2,
1971                    width: w2,
1972                    height: h2,
1973                    rx: rx2,
1974                    ry: ry2,
1975                },
1976            ) => {
1977                fb(*x, *x2)
1978                    && fb(*y, *y2)
1979                    && fb(*width, *w2)
1980                    && fb(*height, *h2)
1981                    && fb(*rx, *rx2)
1982                    && fb(*ry, *ry2)
1983            }
1984            (
1985                Ellipse { cx, cy, rx, ry },
1986                Ellipse {
1987                    cx: cx2,
1988                    cy: cy2,
1989                    rx: rx2,
1990                    ry: ry2,
1991                },
1992            ) => fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*rx, *rx2) && fb(*ry, *ry2),
1993            (
1994                Line { x1, y1, x2, y2 },
1995                Line {
1996                    x1: a1,
1997                    y1: b1,
1998                    x2: a2,
1999                    y2: b2,
2000                },
2001            )
2002            | (
2003                LinearGradient { x1, y1, x2, y2 },
2004                LinearGradient {
2005                    x1: a1,
2006                    y1: b1,
2007                    x2: a2,
2008                    y2: b2,
2009                },
2010            ) => fb(*x1, *a1) && fb(*y1, *b1) && fb(*x2, *a2) && fb(*y2, *b2),
2011            (
2012                PointsList {
2013                    points: pa,
2014                    closed: ca,
2015                },
2016                PointsList {
2017                    points: pb,
2018                    closed: cb,
2019                },
2020            ) => {
2021                ca == cb
2022                    && pa.len() == pb.len()
2023                    && pa
2024                        .iter()
2025                        .zip(pb.iter())
2026                        .all(|(p, q)| fb(p.x, q.x) && fb(p.y, q.y))
2027            }
2028            (
2029                ViewBox {
2030                    min_x,
2031                    min_y,
2032                    width,
2033                    height,
2034                },
2035                ViewBox {
2036                    min_x: a,
2037                    min_y: b,
2038                    width: w,
2039                    height: h,
2040                },
2041            ) => fb(*min_x, *a) && fb(*min_y, *b) && fb(*width, *w) && fb(*height, *h),
2042            (
2043                RadialGradient { cx, cy, r, fx, fy },
2044                RadialGradient {
2045                    cx: cx2,
2046                    cy: cy2,
2047                    r: r2,
2048                    fx: fx2,
2049                    fy: fy2,
2050                },
2051            ) => fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2) && fb(*fx, *fx2) && fb(*fy, *fy2),
2052            (GradientStop { offset: a }, GradientStop { offset: b }) => fb(*a, *b),
2053            (
2054                Use { href, x, y },
2055                Use {
2056                    href: h2,
2057                    x: x2,
2058                    y: y2,
2059                },
2060            ) => href == h2 && fb(*x, *x2) && fb(*y, *y2),
2061            (
2062                SvgImageData {
2063                    href,
2064                    x,
2065                    y,
2066                    width,
2067                    height,
2068                },
2069                SvgImageData {
2070                    href: h2,
2071                    x: x2,
2072                    y: y2,
2073                    width: w2,
2074                    height: hh2,
2075                },
2076            ) => href == h2 && fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2) && fb(*height, *hh2),
2077            // Different variants are never equal.
2078            _ => false,
2079        }
2080    }
2081}
2082
2083/// Bit-equality for two `SvgPathElement`s (matches the Hash impl's per-coordinate
2084/// `to_bits`), so NaN path coordinates are self-equal.
2085const fn svg_path_element_bits_eq(
2086    a: &crate::svg::SvgPathElement,
2087    b: &crate::svg::SvgPathElement,
2088) -> bool {
2089    use crate::svg::SvgPathElement::{CubicCurve, Line, QuadraticCurve};
2090    const fn pb(a: azul_css::props::basic::SvgPoint, b: azul_css::props::basic::SvgPoint) -> bool {
2091        a.x.to_bits() == b.x.to_bits() && a.y.to_bits() == b.y.to_bits()
2092    }
2093    match (a, b) {
2094        (Line(a), Line(b)) => pb(a.start, b.start) && pb(a.end, b.end),
2095        (QuadraticCurve(a), QuadraticCurve(b)) => {
2096            pb(a.start, b.start) && pb(a.ctrl, b.ctrl) && pb(a.end, b.end)
2097        }
2098        (CubicCurve(a), CubicCurve(b)) => {
2099            pb(a.start, b.start)
2100                && pb(a.ctrl_1, b.ctrl_1)
2101                && pb(a.ctrl_2, b.ctrl_2)
2102                && pb(a.end, b.end)
2103        }
2104        _ => false,
2105    }
2106}
2107
2108impl Eq for SvgNodeData {}
2109
2110// SvgNodeData contains f32 (svg coords) so Ord can't be derived; this Ord is
2111// defined *in terms of* the derived field-wise PartialOrd (unwrap_or Equal), so
2112// the two cannot disagree — the derive_ord_xor_partial_ord concern doesn't apply.
2113#[allow(clippy::derive_ord_xor_partial_ord)]
2114impl Ord for SvgNodeData {
2115    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2116        self.partial_cmp(other)
2117            .unwrap_or(core::cmp::Ordering::Equal)
2118    }
2119}
2120
2121impl Hash for SvgNodeData {
2122    #[allow(clippy::too_many_lines)] // large but cohesive: one arm per variant
2123    fn hash<H: Hasher>(&self, state: &mut H) {
2124        mem::discriminant(self).hash(state);
2125        match self {
2126            Self::ImageClipMask(m) => m.hash(state),
2127            Self::Path(mp) => {
2128                for ring in mp.rings.as_ref() {
2129                    for item in ring.items.as_ref() {
2130                        match item {
2131                            crate::svg::SvgPathElement::Line(l) => {
2132                                0u8.hash(state);
2133                                l.start.x.to_bits().hash(state);
2134                                l.start.y.to_bits().hash(state);
2135                                l.end.x.to_bits().hash(state);
2136                                l.end.y.to_bits().hash(state);
2137                            }
2138                            crate::svg::SvgPathElement::QuadraticCurve(q) => {
2139                                1u8.hash(state);
2140                                q.start.x.to_bits().hash(state);
2141                                q.start.y.to_bits().hash(state);
2142                                q.ctrl.x.to_bits().hash(state);
2143                                q.ctrl.y.to_bits().hash(state);
2144                                q.end.x.to_bits().hash(state);
2145                                q.end.y.to_bits().hash(state);
2146                            }
2147                            crate::svg::SvgPathElement::CubicCurve(c) => {
2148                                2u8.hash(state);
2149                                c.start.x.to_bits().hash(state);
2150                                c.start.y.to_bits().hash(state);
2151                                c.ctrl_1.x.to_bits().hash(state);
2152                                c.ctrl_1.y.to_bits().hash(state);
2153                                c.ctrl_2.x.to_bits().hash(state);
2154                                c.ctrl_2.y.to_bits().hash(state);
2155                                c.end.x.to_bits().hash(state);
2156                                c.end.y.to_bits().hash(state);
2157                            }
2158                        }
2159                    }
2160                }
2161            }
2162            Self::Circle { cx, cy, r } => {
2163                cx.to_bits().hash(state);
2164                cy.to_bits().hash(state);
2165                r.to_bits().hash(state);
2166            }
2167            Self::Rect {
2168                x,
2169                y,
2170                width,
2171                height,
2172                rx,
2173                ry,
2174            } => {
2175                x.to_bits().hash(state);
2176                y.to_bits().hash(state);
2177                width.to_bits().hash(state);
2178                height.to_bits().hash(state);
2179                rx.to_bits().hash(state);
2180                ry.to_bits().hash(state);
2181            }
2182            Self::Ellipse { cx, cy, rx, ry } => {
2183                cx.to_bits().hash(state);
2184                cy.to_bits().hash(state);
2185                rx.to_bits().hash(state);
2186                ry.to_bits().hash(state);
2187            }
2188            // Line and LinearGradient share a { x1, y1, x2, y2 } shape and hash
2189            // identically (Eq still distinguishes the variants); fold the duplicate bodies.
2190            Self::Line { x1, y1, x2, y2 } | Self::LinearGradient { x1, y1, x2, y2 } => {
2191                x1.to_bits().hash(state);
2192                y1.to_bits().hash(state);
2193                x2.to_bits().hash(state);
2194                y2.to_bits().hash(state);
2195            }
2196            Self::PointsList { points, closed } => {
2197                for p in points {
2198                    p.x.to_bits().hash(state);
2199                    p.y.to_bits().hash(state);
2200                }
2201                closed.hash(state);
2202            }
2203            Self::ViewBox {
2204                min_x,
2205                min_y,
2206                width,
2207                height,
2208            } => {
2209                min_x.to_bits().hash(state);
2210                min_y.to_bits().hash(state);
2211                width.to_bits().hash(state);
2212                height.to_bits().hash(state);
2213            }
2214            Self::RadialGradient { cx, cy, r, fx, fy } => {
2215                cx.to_bits().hash(state);
2216                cy.to_bits().hash(state);
2217                r.to_bits().hash(state);
2218                fx.to_bits().hash(state);
2219                fy.to_bits().hash(state);
2220            }
2221            Self::GradientStop { offset } => {
2222                offset.to_bits().hash(state);
2223            }
2224            Self::Use { href, x, y } => {
2225                href.hash(state);
2226                x.to_bits().hash(state);
2227                y.to_bits().hash(state);
2228            }
2229            Self::SvgImageData {
2230                href,
2231                x,
2232                y,
2233                width,
2234                height,
2235            } => {
2236                href.hash(state);
2237                x.to_bits().hash(state);
2238                y.to_bits().hash(state);
2239                width.to_bits().hash(state);
2240                height.to_bits().hash(state);
2241            }
2242        }
2243    }
2244}
2245
2246/// NOTE: NOT EXPOSED IN THE API! Stores extra,
2247/// not commonly used information for the `NodeData`.
2248/// This helps keep the primary `NodeData` struct smaller for common cases.
2249#[repr(C)]
2250#[derive(Debug, Default, Clone)]
2251pub struct NodeDataExt {
2252    /// Strongly-typed HTML attributes (aria-*, href, alt, etc.)
2253    /// IDs and classes are stored as `AttributeType::Id` and `AttributeType::Class` entries.
2254    /// Moved from `NodeData` to save 48B for the ~95% of nodes with no attributes.
2255    pub attributes: AttributeTypeVec,
2256    /// `VirtualView` callback data, only set when `node_type` == `NodeType::VirtualView`.
2257    pub virtual_view: Option<VirtualViewNode>,
2258    /// `data-*` attributes for this node, useful to store UI-related data on the node itself.
2259    pub dataset: Option<RefAny>,
2260    /// SVG-specific data or raster clip mask for this DOM node.
2261    pub svg_data: Option<SvgNodeData>,
2262    /// Menu bar that should be displayed at the top of this nodes rect.
2263    pub menu_bar: Option<Box<Menu>>,
2264    /// Context menu that should be opened when the item is left-clicked.
2265    pub context_menu: Option<Box<Menu>>,
2266    /// Stable key for reconciliation. If provided, allows the framework to track
2267    /// this node across frames even if its position in the array changes.
2268    /// This is crucial for correct lifecycle events when lists are reordered.
2269    pub key: Option<u64>,
2270    /// App-chosen MARKER string, resolvable to this node's `(DomId, NodeId)`
2271    /// via `CallbackInfo::get_node_id_by_marker`. The clean spelling of the
2272    /// "find that other widget from this callback" jump: the app mints a
2273    /// unique string at build time (a UUID, a counter — anything unique in
2274    /// the window), stamps it on the node AND keeps it in whatever dataset
2275    /// the jumping callback holds. Replaces the old
2276    /// `get_node_id_of_root_dataset` search, which needed an EMPTY instance
2277    /// of the target's private dataset type just to name it — coupling the
2278    /// caller to widget internals the architecture forbids it from seeing.
2279    /// Unlike an `Id` attribute, a marker is invisible to CSS matching.
2280    pub marker: Option<AzString>,
2281    /// Callback to merge dataset state from a previous frame's node into the current node.
2282    /// This enables heavy resource preservation (video decoders, GL textures) across frames.
2283    pub dataset_merge_callback: Option<DatasetMergeCallback>,
2284    /// Tracks which component rendered this DOM subtree.
2285    /// Set by the framework during component rendering — the root node(s) of a
2286    /// component's output DOM get stamped with the component's qualified name.
2287    /// Enables the debugger to reconstruct the component invocation tree from the
2288    /// flat rendered DOM, and enables code generation roundtrips.
2289    pub component_origin: Option<ComponentOrigin>,
2290    /// COMPONENT-ATTACHED presence-animation functions, resolvable by NAME
2291    /// from this node's `-azul-animation-in` / `-azul-animation-out` (after
2292    /// stylesheet `@keyframes` — the web mechanism stays the only default
2293    /// name source). This replaced the global `AppConfig` registry (USER
2294    /// ruling 2026-08-17): a sidebar widget ships its fly-out next to its
2295    /// own DOM, not in app-global state.
2296    pub animation_callbacks: Vec<crate::resources::AnimationFunction>,
2297}
2298
2299// The MARKER is EXCLUDED from equality, ordering and hashing (USER ruling
2300// 2026-08-29): markers are minted fresh - UUID strings - at every `layout()`
2301// and exist only to be RESOLVED back to a node, never to describe it. Two
2302// otherwise identical nodes must compare equal across rebuilds, or every
2303// marked node would look "changed" to the DOM diff on every frame, defeating
2304// reconciliation for exactly the widgets the fast path is for.
2305impl NodeDataExt {
2306    /// Every field EXCEPT `marker`, as one comparable/hashable tuple - the
2307    /// single place that decides what "same ext" means.
2308    #[allow(clippy::type_complexity)]
2309    fn cmp_key(
2310        &self,
2311    ) -> (
2312        &AttributeTypeVec,
2313        &Option<VirtualViewNode>,
2314        &Option<RefAny>,
2315        &Option<SvgNodeData>,
2316        &Option<Box<Menu>>,
2317        &Option<Box<Menu>>,
2318        &Option<u64>,
2319        &Option<DatasetMergeCallback>,
2320        &Option<ComponentOrigin>,
2321        &Vec<crate::resources::AnimationFunction>,
2322    ) {
2323        (
2324            &self.attributes,
2325            &self.virtual_view,
2326            &self.dataset,
2327            &self.svg_data,
2328            &self.menu_bar,
2329            &self.context_menu,
2330            &self.key,
2331            &self.dataset_merge_callback,
2332            &self.component_origin,
2333            &self.animation_callbacks,
2334        )
2335    }
2336}
2337
2338impl NodeDataExt {
2339    /// `true` when every identity-relevant field (everything [`cmp_key`]
2340    /// (Self::cmp_key) compares, i.e. everything but the marker) is at its
2341    /// default - such an ext exists only to carry a marker, and
2342    /// `NodeData::identity_extra` treats it as absent.
2343    fn is_identity_empty(&self) -> bool {
2344        self.attributes.as_ref().is_empty()
2345            && self.virtual_view.is_none()
2346            && self.dataset.is_none()
2347            && self.svg_data.is_none()
2348            && self.menu_bar.is_none()
2349            && self.context_menu.is_none()
2350            && self.key.is_none()
2351            && self.dataset_merge_callback.is_none()
2352            && self.component_origin.is_none()
2353            && self.animation_callbacks.is_empty()
2354    }
2355}
2356
2357impl PartialEq for NodeDataExt {
2358    fn eq(&self, other: &Self) -> bool {
2359        self.cmp_key() == other.cmp_key()
2360    }
2361}
2362impl Eq for NodeDataExt {}
2363impl PartialOrd for NodeDataExt {
2364    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2365        Some(self.cmp(other))
2366    }
2367}
2368impl Ord for NodeDataExt {
2369    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2370        self.cmp_key().cmp(&other.cmp_key())
2371    }
2372}
2373impl core::hash::Hash for NodeDataExt {
2374    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
2375        self.cmp_key().hash(state);
2376    }
2377}
2378
2379/// A callback function used to merge the state of an old dataset into a new one.
2380///
2381/// This enables components with heavy internal state (video players, WebGL contexts)
2382/// to preserve their resources across frames, while the DOM tree is recreated.
2383///
2384/// The callback receives both the old and new datasets as `RefAny` (cheap shallow clones)
2385/// and returns the dataset that should be used for the new node.
2386///
2387/// # Example
2388///
2389/// ```rust,ignore
2390/// fn merge_video_state(new_data: RefAny, old_data: RefAny) -> RefAny {
2391///     // Transfer heavy resources from old to new
2392///     if let (Some(mut new), Some(old)) = (
2393///         new_data.downcast_mut::<VideoState>(),
2394///         old_data.downcast_ref::<VideoState>()
2395///     ) {
2396///         new.decoder = old.decoder.take();
2397///         new.gl_texture = old.gl_texture.take();
2398///     }
2399///     new_data // Return the merged state
2400/// }
2401/// ```
2402#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2403#[repr(C)]
2404pub struct DatasetMergeCallback {
2405    /// The function pointer that performs the merge.
2406    /// Signature: `fn(new_data: RefAny, old_data: RefAny) -> RefAny`
2407    pub cb: DatasetMergeCallbackType,
2408    /// Optional callable for FFI language bindings (Python, etc.)
2409    /// When set, the FFI layer can invoke this instead of `cb`.
2410    pub callable: OptionRefAny,
2411}
2412
2413impl fmt::Debug for DatasetMergeCallback {
2414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2415        f.debug_struct("DatasetMergeCallback")
2416            .field("cb", &(self.cb as usize))
2417            .field("callable", &self.callable)
2418            .finish()
2419    }
2420}
2421
2422/// Allow creating `DatasetMergeCallback` from a raw function pointer.
2423/// This enables the `Into<DatasetMergeCallback>` pattern for Python bindings.
2424impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
2425    fn from(cb: DatasetMergeCallbackType) -> Self {
2426        Self {
2427            cb,
2428            callable: OptionRefAny::None,
2429        }
2430    }
2431}
2432
2433impl DatasetMergeCallback {
2434    /// Build from a raw `DatasetMergeCallbackType` function pointer (callable =
2435    /// None). The concrete parameter is a coercion site, so callers can pass a
2436    /// bare `extern "C" fn` item without an `as DatasetMergeCallbackType` cast.
2437    #[must_use]
2438    pub fn from_ptr(cb: DatasetMergeCallbackType) -> Self {
2439        Self::from(cb)
2440    }
2441}
2442
2443impl_option!(
2444    DatasetMergeCallback,
2445    OptionDatasetMergeCallback,
2446    copy = false,
2447    [Debug, Clone]
2448);
2449
2450/// Function pointer type for dataset merge callbacks.
2451///
2452/// Arguments:
2453/// - `new_data`: The new node's dataset (shallow clone, cheap)
2454/// - `old_data`: The old node's dataset (shallow clone, cheap)
2455///
2456/// Returns:
2457/// - The `RefAny` that should be used as the dataset for the new node
2458pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
2459
2460impl Clone for NodeData {
2461    #[inline]
2462    fn clone(&self) -> Self {
2463        Self {
2464            node_type: self.node_type.to_library_owned_nodetype(),
2465            style: self.style.clone(),
2466            callbacks: self.callbacks.clone(),
2467            flags: self.flags,
2468            accessibility: self.accessibility.clone(),
2469            extra: self.extra.clone(),
2470        }
2471    }
2472}
2473
2474// Clone, PartialEq, Eq, Hash, PartialOrd, Ord
2475impl_vec!(
2476    NodeData,
2477    NodeDataVec,
2478    NodeDataVecDestructor,
2479    NodeDataVecDestructorType,
2480    NodeDataVecSlice,
2481    OptionNodeData
2482);
2483impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
2484impl_vec_mut!(NodeData, NodeDataVec);
2485impl_vec_debug!(NodeData, NodeDataVec);
2486impl_vec_partialord!(NodeData, NodeDataVec);
2487impl_vec_ord!(NodeData, NodeDataVec);
2488impl_vec_partialeq!(NodeData, NodeDataVec);
2489impl_vec_eq!(NodeData, NodeDataVec);
2490impl_vec_hash!(NodeData, NodeDataVec);
2491
2492impl NodeDataVec {
2493    #[inline]
2494    #[must_use]
2495    pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeData> {
2496        NodeDataContainerRef {
2497            internal: self.as_ref(),
2498        }
2499    }
2500    #[inline]
2501    pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeData> {
2502        NodeDataContainerRefMut {
2503            internal: self.as_mut(),
2504        }
2505    }
2506}
2507
2508// SAFETY: All fields in NodeData are either Send (NodeType, NodeFlags, CssPropertyWithConditionsVec),
2509// Arc-wrapped (RefAny), or plain data (Box<AccessibilityInfo>, Box<NodeDataExt>).
2510// Function pointers (callbacks) are inherently Send. The RefAny uses atomic reference counting.
2511unsafe impl Send for NodeData {}
2512
2513/// Determines the behavior of an element in sequential focus navigation
2514// (e.g., using the Tab key).
2515#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2516#[repr(C, u8)]
2517#[derive(Default)]
2518pub enum TabIndex {
2519    /// Automatic tab index, similar to simply setting `focusable = "true"` or `tabindex = 0`
2520    /// (both have the effect of making the element focusable).
2521    ///
2522    /// Sidenote: See <https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute>
2523    /// for interesting notes on tabindex and accessibility
2524    #[default]
2525    Auto,
2526    /// Set the tab index in relation to its parent element. I.e. if you have a list of elements,
2527    /// the focusing order is restricted to the current parent.
2528    ///
2529    /// When pressing tab repeatedly, the focusing order will be
2530    /// determined by `OverrideInParent` elements taking precedence among global order.
2531    OverrideInParent(u32),
2532    /// Elements can be focused in callbacks, but are not accessible via
2533    /// keyboard / tab navigation (-1).
2534    NoKeyboardFocus,
2535}
2536
2537impl_option!(
2538    TabIndex,
2539    OptionTabIndex,
2540    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2541);
2542
2543impl TabIndex {
2544    /// Returns the HTML-compatible number of the `tabindex` element.
2545    // const fn: TryFrom isn't const, and u32 -> isize is lossless on every
2546    // supported (>= 32-bit) target, so the `as` cast cannot actually wrap here.
2547    #[allow(clippy::cast_possible_wrap)]
2548    #[must_use]
2549    pub const fn get_index(&self) -> isize {
2550        use self::TabIndex::{Auto, NoKeyboardFocus, OverrideInParent};
2551        match self {
2552            Auto => 0,
2553            OverrideInParent(x) => *x as isize,
2554            NoKeyboardFocus => -1,
2555        }
2556    }
2557}
2558
2559/// Packed representation of tab index + contenteditable flag.
2560///
2561/// Bit layout (32 bits):
2562///   [31]     contenteditable flag (1 = true)
2563///   [30:29]  `tab_index` variant:
2564///              00 = None (no tab index set)
2565///              01 = Auto
2566///              10 = `OverrideInParent` (value in bits [28:0])
2567///              11 = `NoKeyboardFocus`
2568///   [28]     `is_anonymous` (1 = anonymous box for table layout)
2569///   [27:0]   `OverrideInParent` value (max ~268 million)
2570#[repr(C)]
2571#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2572pub struct NodeFlags {
2573    pub inner: u32,
2574}
2575
2576impl NodeFlags {
2577    const CONTENTEDITABLE_BIT: u32 = 1 << 31;
2578    const TAB_INDEX_MASK: u32 = 0b11 << 29;
2579    const ANONYMOUS_BIT: u32 = 1 << 28;
2580    const TAB_VALUE_MASK: u32 = (1 << 28) - 1;
2581
2582    const TAB_NONE: u32 = 0b00 << 29;
2583    const TAB_AUTO: u32 = 0b01 << 29;
2584    const TAB_OVERRIDE: u32 = 0b10 << 29;
2585    const TAB_NO_KEYBOARD: u32 = 0b11 << 29;
2586
2587    #[must_use]
2588    pub const fn new() -> Self {
2589        Self { inner: 0 }
2590    }
2591
2592    #[must_use]
2593    pub const fn is_contenteditable(&self) -> bool {
2594        (self.inner & Self::CONTENTEDITABLE_BIT) != 0
2595    }
2596
2597    #[must_use]
2598    pub const fn set_contenteditable(mut self, v: bool) -> Self {
2599        if v {
2600            self.inner |= Self::CONTENTEDITABLE_BIT;
2601        } else {
2602            self.inner &= !Self::CONTENTEDITABLE_BIT;
2603        }
2604        self
2605    }
2606
2607    pub const fn set_contenteditable_mut(&mut self, v: bool) {
2608        if v {
2609            self.inner |= Self::CONTENTEDITABLE_BIT;
2610        } else {
2611            self.inner &= !Self::CONTENTEDITABLE_BIT;
2612        }
2613    }
2614
2615    #[must_use]
2616    pub const fn get_tab_index(&self) -> Option<TabIndex> {
2617        match self.inner & Self::TAB_INDEX_MASK {
2618            x if x == Self::TAB_NONE => None,
2619            x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
2620            x if x == Self::TAB_OVERRIDE => {
2621                let val = self.inner & Self::TAB_VALUE_MASK;
2622                Some(TabIndex::OverrideInParent(val))
2623            }
2624            x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
2625            _ => None,
2626        }
2627    }
2628
2629    /// Returns whether this node is an anonymous box generated for table layout.
2630    #[must_use]
2631    pub const fn is_anonymous(&self) -> bool {
2632        (self.inner & Self::ANONYMOUS_BIT) != 0
2633    }
2634
2635    pub const fn set_anonymous(&mut self, v: bool) {
2636        if v {
2637            self.inner |= Self::ANONYMOUS_BIT;
2638        } else {
2639            self.inner &= !Self::ANONYMOUS_BIT;
2640        }
2641    }
2642
2643    pub const fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
2644        // Clear tab index bits (bits 29-30) and value bits (bits 0-27)
2645        // keep contenteditable bit (31) and anonymous bit (28)
2646        self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
2647        match tab_index {
2648            None => { /* TAB_NONE = 0, already cleared */ }
2649            Some(TabIndex::Auto) => {
2650                self.inner |= Self::TAB_AUTO;
2651            }
2652            Some(TabIndex::OverrideInParent(val)) => {
2653                self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
2654            }
2655            Some(TabIndex::NoKeyboardFocus) => {
2656                self.inner |= Self::TAB_NO_KEYBOARD;
2657            }
2658        }
2659    }
2660}
2661
2662impl Default for NodeData {
2663    fn default() -> Self {
2664        Self::create_node(NodeType::Div)
2665    }
2666}
2667
2668impl fmt::Display for NodeData {
2669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2670        let html_type = self.node_type.get_path();
2671        let attributes_string = node_data_to_string(self);
2672
2673        match self.node_type.format() {
2674            Some(content) => write!(f, "<{html_type}{attributes_string}>{content}</{html_type}>"),
2675            None => write!(f, "<{html_type}{attributes_string}/>"),
2676        }
2677    }
2678}
2679
2680fn node_data_to_string(node_data: &NodeData) -> String {
2681    let mut id_string = String::new();
2682    let ids = node_data
2683        .attributes()
2684        .as_ref()
2685        .iter()
2686        .filter_map(|s| s.as_id())
2687        .collect::<Vec<_>>()
2688        .join(" ");
2689
2690    if !ids.is_empty() {
2691        id_string = format!(" id=\"{ids}\" ");
2692    }
2693
2694    let mut class_string = String::new();
2695    let classes = node_data
2696        .attributes()
2697        .as_ref()
2698        .iter()
2699        .filter_map(|s| s.as_class())
2700        .collect::<Vec<_>>()
2701        .join(" ");
2702
2703    if !classes.is_empty() {
2704        class_string = format!(" class=\"{classes}\" ");
2705    }
2706
2707    let tabindex_string = node_data.get_tab_index().map_or_else(String::new, |tab_index| {
2708        format!(" tabindex=\"{}\" ", tab_index.get_index())
2709    });
2710
2711    format!("{id_string}{class_string}{tabindex_string}")
2712}
2713
2714impl NodeData {
2715    /// Creates a new `NodeData` instance from a given `NodeType`.
2716    #[inline]
2717    #[must_use]
2718    pub const fn create_node(node_type: NodeType) -> Self {
2719        Self {
2720            node_type,
2721            callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2722            style: azul_css::css::Css {
2723                rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2724                keyframes: azul_css::css::KeyframesVec::from_const_slice(&[]),
2725            },
2726            flags: NodeFlags::new(),
2727            accessibility: None,
2728            extra: None,
2729        }
2730    }
2731
2732    /// Returns a reference to the node's attributes (from `NodeDataExt`).
2733    /// Returns an empty slice if no attributes have been set.
2734    #[inline]
2735    #[must_use]
2736    pub fn attributes(&self) -> &AttributeTypeVec {
2737        static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
2738        self.extra.as_ref().map_or(&EMPTY, |ext| &ext.attributes)
2739    }
2740
2741    /// Returns a mutable reference to the node's attributes,
2742    /// lazily allocating `NodeDataExt` if needed.
2743    #[inline]
2744    pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
2745        &mut self
2746            .extra
2747            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2748            .attributes
2749    }
2750
2751    /// Sets the node's attributes, replacing any existing ones.
2752    #[inline]
2753    pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
2754        self.extra
2755            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2756            .attributes = attrs;
2757    }
2758
2759    /// Shorthand for `NodeData::create_node(NodeType::Body)`.
2760    #[inline]
2761    #[must_use]
2762    pub const fn create_body() -> Self {
2763        Self::create_node(NodeType::Body)
2764    }
2765
2766    /// Shorthand for `NodeData::create_node(NodeType::Div)`.
2767    #[inline]
2768    #[must_use]
2769    pub const fn create_div() -> Self {
2770        Self::create_node(NodeType::Div)
2771    }
2772
2773    /// Shorthand for `NodeData::create_node(NodeType::Br)`.
2774    #[inline]
2775    #[must_use]
2776    pub const fn create_br() -> Self {
2777        Self::create_node(NodeType::Br)
2778    }
2779
2780    /// Creates a RAW text node - read this before using it.
2781    ///
2782    /// **WARNING**: azul does NOT auto-wrap raw text in an anonymous block
2783    /// the way browsers do. A bare text node has NO box of its own: it gets
2784    /// no rect, no clip, and no layout constraints. Every box-model CSS
2785    /// property (width/height/position/overflow/background/border/padding),
2786    /// every callback, every `tab_index` and every `dataset` attached to a
2787    /// text node is silently INERT. This has broken shipped widgets before
2788    /// (text escaping its container, click targets that never fire).
2789    ///
2790    /// A raw text node is only correct as the bare leaf INSIDE a block-level
2791    /// wrapper that carries the styling - `p`, `div`, `h1`... Prefer the
2792    /// `create_*_with_text` family (`Dom::create_p_with_text`,
2793    /// `Dom::create_div_with_text`, `Dom::create_span_with_text`, ...), which
2794    /// builds that shape for you. The engine also logs a warning after layout
2795    /// when it finds a text node used without a containing block.
2796    ///
2797    /// Shorthand for `NodeData::create_node(NodeType::Text(value.into()))`.
2798    #[inline]
2799    pub fn create_text_do_not_use_without_block_level_wrapper<S: Into<AzString>>(value: S) -> Self {
2800        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
2801    }
2802
2803    /// Shorthand for `NodeData::create_node(NodeType::Image(image_id))`.
2804    #[inline]
2805    #[must_use]
2806    pub fn create_image(image: ImageRef) -> Self {
2807        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
2808    }
2809
2810    #[inline]
2811    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
2812        let mut nd = Self::create_node(NodeType::VirtualView);
2813        let ext = nd
2814            .extra
2815            .get_or_insert_with(|| Box::new(NodeDataExt::default()));
2816        ext.virtual_view = Some(VirtualViewNode {
2817            callback: callback.into(),
2818            refany: data,
2819        });
2820        nd
2821    }
2822
2823    // -- Accessibility-aware NodeData constructors --
2824    // Each a11y-able element has two constructors: the canonical one takes a
2825    // `SmallAriaInfo` so the caller must opt in to an accessible name, and the
2826    // `*_no_a11y` variant is a deliberate escape hatch with a longer name.
2827
2828    fn with_attribute(mut self, attr: AttributeType) -> Self {
2829        let mut v = self.attributes().clone().into_library_owned_vec();
2830        v.push(attr);
2831        self.set_attributes(v.into());
2832        self
2833    }
2834
2835    /// Creates a button `NodeData` with accessibility information.
2836    #[inline]
2837    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2838    #[must_use]
2839    pub fn create_button(aria: SmallAriaInfo) -> Self {
2840        let mut nd = Self::create_node(NodeType::Button);
2841        nd.set_accessibility_info(aria.to_full_info());
2842        nd
2843    }
2844
2845    /// Creates a button `NodeData` without accessibility information.
2846    #[inline]
2847    #[must_use]
2848    pub const fn create_button_no_a11y() -> Self {
2849        Self::create_node(NodeType::Button)
2850    }
2851
2852    /// Creates an anchor `NodeData` with an href and accessibility information.
2853    #[inline]
2854    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2855    #[must_use]
2856    pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2857        let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2858        nd.set_accessibility_info(aria.to_full_info());
2859        nd
2860    }
2861
2862    /// Creates an anchor `NodeData` with an href but no accessibility information.
2863    #[inline]
2864    #[must_use]
2865    pub fn create_a_no_a11y(href: AzString) -> Self {
2866        Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
2867    }
2868
2869    /// Creates an input `NodeData` with accessibility information.
2870    #[inline]
2871    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2872    #[must_use]
2873    pub fn create_input(
2874        input_type: AzString,
2875        name: AzString,
2876        label: AzString,
2877        aria: SmallAriaInfo,
2878    ) -> Self {
2879        let mut nd = Self::create_node(NodeType::Input)
2880            .with_attribute(AttributeType::InputType(input_type))
2881            .with_attribute(AttributeType::Name(name))
2882            .with_attribute(AttributeType::AriaLabel(label));
2883        nd.set_accessibility_info(aria.to_full_info());
2884        nd
2885    }
2886
2887    /// Creates an input `NodeData` without accessibility information.
2888    #[inline]
2889    #[must_use]
2890    pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2891        Self::create_node(NodeType::Input)
2892            .with_attribute(AttributeType::InputType(input_type))
2893            .with_attribute(AttributeType::Name(name))
2894            .with_attribute(AttributeType::AriaLabel(label))
2895    }
2896
2897    /// Creates a textarea `NodeData` with accessibility information.
2898    #[inline]
2899    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2900    #[must_use]
2901    pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2902        let mut nd = Self::create_node(NodeType::TextArea)
2903            .with_attribute(AttributeType::Name(name))
2904            .with_attribute(AttributeType::AriaLabel(label));
2905        nd.set_accessibility_info(aria.to_full_info());
2906        nd
2907    }
2908
2909    /// Creates a textarea `NodeData` without accessibility information.
2910    #[inline]
2911    #[must_use]
2912    pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2913        Self::create_node(NodeType::TextArea)
2914            .with_attribute(AttributeType::Name(name))
2915            .with_attribute(AttributeType::AriaLabel(label))
2916    }
2917
2918    /// Creates a select `NodeData` with accessibility information.
2919    #[inline]
2920    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2921    #[must_use]
2922    pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2923        let mut nd = Self::create_node(NodeType::Select)
2924            .with_attribute(AttributeType::Name(name))
2925            .with_attribute(AttributeType::AriaLabel(label));
2926        nd.set_accessibility_info(aria.to_full_info());
2927        nd
2928    }
2929
2930    /// Creates a select `NodeData` without accessibility information.
2931    #[inline]
2932    #[must_use]
2933    pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2934        Self::create_node(NodeType::Select)
2935            .with_attribute(AttributeType::Name(name))
2936            .with_attribute(AttributeType::AriaLabel(label))
2937    }
2938
2939    /// Creates a table `NodeData` with accessibility information.
2940    #[inline]
2941    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2942    #[must_use]
2943    pub fn create_table(aria: SmallAriaInfo) -> Self {
2944        let mut nd = Self::create_node(NodeType::Table);
2945        nd.set_accessibility_info(aria.to_full_info());
2946        nd
2947    }
2948
2949    /// Creates a table `NodeData` without accessibility information.
2950    #[inline]
2951    #[must_use]
2952    pub const fn create_table_no_a11y() -> Self {
2953        Self::create_node(NodeType::Table)
2954    }
2955
2956    /// Creates a label `NodeData` with an associated control ID and accessibility
2957    /// information.
2958    #[inline]
2959    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2960    #[must_use]
2961    pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
2962        let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2963            AttributeNameValue {
2964                attr_name: "for".into(),
2965                value: for_id,
2966            },
2967        ));
2968        nd.set_accessibility_info(aria.to_full_info());
2969        nd
2970    }
2971
2972    /// Creates a label `NodeData` with an associated control ID but no
2973    /// accessibility information.
2974    #[inline]
2975    #[must_use]
2976    pub fn create_label_no_a11y(for_id: AzString) -> Self {
2977        Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2978            AttributeNameValue {
2979                attr_name: "for".into(),
2980                value: for_id,
2981            },
2982        ))
2983    }
2984
2985    /// Checks whether this node is of the given node type (div, image, text).
2986    #[inline]
2987    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2988    #[must_use]
2989    pub fn is_node_type(&self, searched_type: NodeType) -> bool {
2990        self.node_type == searched_type
2991    }
2992
2993    /// Checks whether this node has the searched ID attached.
2994    #[must_use]
2995    pub fn has_id(&self, id: &str) -> bool {
2996        self.attributes()
2997            .iter()
2998            .any(|attr| attr.as_id() == Some(id))
2999    }
3000
3001    /// Checks whether this node has the searched class attached.
3002    #[must_use]
3003    pub fn has_class(&self, class: &str) -> bool {
3004        self.attributes()
3005            .iter()
3006            .any(|attr| attr.as_class() == Some(class))
3007    }
3008
3009    #[must_use]
3010    pub fn has_context_menu(&self) -> bool {
3011        self.extra
3012            .as_ref()
3013            .is_some_and(|m| m.context_menu.is_some())
3014    }
3015
3016    #[must_use]
3017    pub const fn is_text_node(&self) -> bool {
3018        matches!(self.node_type, NodeType::Text(_))
3019    }
3020
3021    #[must_use]
3022    pub const fn is_virtual_view_node(&self) -> bool {
3023        matches!(self.node_type, NodeType::VirtualView)
3024    }
3025
3026    // NOTE: Getters are used here in order to allow changing the memory allocator for the NodeData
3027    // in the future (which is why the fields are all private).
3028
3029    #[inline]
3030    #[must_use]
3031    pub const fn get_node_type(&self) -> &NodeType {
3032        &self.node_type
3033    }
3034    #[inline]
3035    pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
3036        self.extra.as_mut().and_then(|e| e.dataset.as_mut())
3037    }
3038    #[inline]
3039    #[must_use]
3040    pub fn get_dataset(&self) -> Option<&RefAny> {
3041        self.extra.as_ref().and_then(|e| e.dataset.as_ref())
3042    }
3043    /// Take the dataset out of the node, replacing it with None.
3044    pub fn take_dataset(&mut self) -> Option<RefAny> {
3045        self.extra.as_mut().and_then(|e| e.dataset.take())
3046    }
3047    /// Returns IDs and classes as a computed `IdOrClassVec`.
3048    /// Note: this allocates a new vec each time, prefer `has_id()`/`has_class()` for checks.
3049    #[inline]
3050    #[must_use]
3051    pub fn get_ids_and_classes(&self) -> IdOrClassVec {
3052        let v: Vec<IdOrClass> = self
3053            .attributes()
3054            .as_ref()
3055            .iter()
3056            .filter_map(|attr| match attr {
3057                AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
3058                AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
3059                _ => None,
3060            })
3061            .collect();
3062        v.into()
3063    }
3064    #[inline]
3065    #[must_use]
3066    pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
3067        &self.callbacks
3068    }
3069    #[inline]
3070    #[must_use]
3071    pub const fn get_style(&self) -> &azul_css::css::Css {
3072        &self.style
3073    }
3074
3075    #[inline]
3076    #[must_use]
3077    pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
3078        self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
3079    }
3080
3081    /// Is this node an image whose content is a null/placeholder image?
3082    ///
3083    /// Capture widgets (camera, screencapture, microphone level) build their
3084    /// node with `ImageRef::null_image(...)` and fill it by writeback later, so
3085    /// "placeholder" means "no frame yet" — see `diff::transfer_states`, which
3086    /// uses this to keep the previous frame visible across a DOM rebuild
3087    /// instead of flashing back to the placeholder.
3088    #[must_use]
3089    pub fn image_is_placeholder(&self) -> bool {
3090        match &self.node_type {
3091            NodeType::Image(img) => img.as_ref().is_null_image(),
3092            _ => false,
3093        }
3094    }
3095
3096    /// Clone this node's `ImageRef`, if it is an image node.
3097    #[must_use]
3098    pub fn get_image_ref_cloned(&self) -> Option<ImageRef> {
3099        match &self.node_type {
3100            NodeType::Image(img) => Some(img.as_ref().clone()),
3101            _ => None,
3102        }
3103    }
3104
3105    /// Replace this node's image. No-op on non-image nodes.
3106    pub fn set_image_ref(&mut self, image: ImageRef) {
3107        if let NodeType::Image(slot) = &mut self.node_type {
3108            *slot = BoxOrStatic::heap(image);
3109        }
3110    }
3111
3112    /// Legacy accessor for raster clip mask. Returns `Some` only for `SvgNodeData::ImageClipMask`.
3113    #[inline]
3114    #[must_use]
3115    pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
3116        match self.get_svg_data()? {
3117            SvgNodeData::ImageClipMask(m) => Some(m),
3118            _ => None,
3119        }
3120    }
3121    #[inline]
3122    #[must_use]
3123    pub const fn get_tab_index(&self) -> Option<TabIndex> {
3124        self.flags.get_tab_index()
3125    }
3126    #[inline]
3127    #[must_use]
3128    pub fn get_accessibility_info(&self) -> Option<&AccessibilityInfo> {
3129        self.accessibility.as_deref()
3130    }
3131    #[inline]
3132    #[must_use]
3133    pub fn get_menu_bar(&self) -> Option<&Menu> {
3134        self.extra.as_ref().and_then(|e| e.menu_bar.as_deref())
3135    }
3136    #[inline]
3137    #[must_use]
3138    pub fn get_context_menu(&self) -> Option<&Menu> {
3139        self.extra.as_ref().and_then(|e| e.context_menu.as_deref())
3140    }
3141
3142    /// Returns whether this node is an anonymous box generated for table layout.
3143    #[inline]
3144    #[must_use]
3145    pub const fn is_anonymous(&self) -> bool {
3146        self.flags.is_anonymous()
3147    }
3148
3149    #[inline]
3150    pub fn set_node_type(&mut self, node_type: NodeType) {
3151        self.node_type = node_type;
3152    }
3153    #[inline]
3154    /// The node's component-attached animation functions (empty for the
3155    /// ~100% of nodes that have none — no `extra` allocation is made by
3156    /// reading).
3157    #[must_use]
3158    pub fn animation_callbacks(&self) -> &[crate::resources::AnimationFunction] {
3159        self.extra
3160            .as_ref()
3161            .map_or(&[], |e| e.animation_callbacks.as_slice())
3162    }
3163
3164    /// Attach a presence-animation function under `name`, resolvable from
3165    /// this node's `-azul-animation-in` / `-azul-animation-out` after
3166    /// stylesheet `@keyframes`. `callback` is the TYPE-ERASED
3167    /// `azul_layout::callbacks::ZombieAnimFnType` fn pointer cast to
3168    /// `usize` (the type-erased `ZombieAnimCallback.cb`).
3169    pub fn add_animation_callback(
3170        &mut self,
3171        name: AzString,
3172        callback: crate::resources::ZombieAnimCallback,
3173        data: RefAny,
3174    ) {
3175        self.extra
3176            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3177            .animation_callbacks
3178            .push(crate::resources::AnimationFunction {
3179                name,
3180                callback,
3181                data,
3182            });
3183    }
3184
3185    /// Stamp an app-chosen marker string on this node — see
3186    /// [`NodeDataExt::marker`]. `None` clears it.
3187    pub fn set_marker(&mut self, marker: azul_css::OptionString) {
3188        match marker {
3189            azul_css::OptionString::None => {
3190                if let Some(ext) = self.extra.as_mut() {
3191                    ext.marker = None;
3192                }
3193            }
3194            azul_css::OptionString::Some(s) => {
3195                self.extra
3196                    .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3197                    .marker = Some(s);
3198            }
3199        }
3200    }
3201
3202    /// The marker string stamped on this node, if any.
3203    #[must_use]
3204    pub fn get_marker(&self) -> Option<&AzString> {
3205        self.extra.as_ref().and_then(|ext| ext.marker.as_ref())
3206    }
3207
3208    /// Builder form of [`Self::set_marker`].
3209    #[inline]
3210    #[must_use]
3211    pub fn with_marker(mut self, marker: azul_css::OptionString) -> Self {
3212        self.set_marker(marker);
3213        self
3214    }
3215
3216    pub fn set_dataset(&mut self, data: OptionRefAny) {
3217        match data {
3218            OptionRefAny::None => {
3219                if let Some(ext) = self.extra.as_mut() {
3220                    ext.dataset = None;
3221                }
3222            }
3223            OptionRefAny::Some(r) => {
3224                self.extra
3225                    .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3226                    .dataset = Some(r);
3227            }
3228        }
3229    }
3230    /// Sets the IDs and classes by converting `IdOrClassVec` entries into
3231    /// `AttributeType::Id`/`AttributeType::Class` and merging them into `self.attributes`.
3232    /// Any existing Id/Class attributes are removed first.
3233    #[inline]
3234    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
3235    pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
3236        // Remove existing Id/Class from attributes
3237        let mut v: AttributeTypeVec = Vec::new().into();
3238        mem::swap(&mut v, self.attributes_mut());
3239        let mut v = v.into_library_owned_vec();
3240        v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
3241        // Convert and append
3242        for ioc in ids_and_classes.as_ref() {
3243            match ioc {
3244                IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
3245                IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
3246            }
3247        }
3248        self.set_attributes(v.into());
3249    }
3250    #[inline]
3251    pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
3252        self.callbacks = callbacks;
3253    }
3254    /// Legacy: replace this node's inline style with a flat list of property+conditions.
3255    /// Each entry becomes a single-declaration rule at `rule_priority::INLINE`. Prefer
3256    /// `set_style` (or `with_style` / `with_css(&str)`) for new code.
3257    #[inline]
3258    pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
3259        self.style = css_props.into();
3260    }
3261    /// Upsert one runtime-patched CSS property into this node's inline style.
3262    ///
3263    /// Every UNCONDITIONAL inline declaration of the same property type is
3264    /// removed (a patch replaces the property's resting value), then the new
3265    /// value is appended as its own unconditional rule at
3266    /// `rule_priority::INLINE`. Conditional declarations (`:hover` styles,
3267    /// `@media` rules) are left untouched: a runtime patch changes the
3268    /// property's base value, not the node's whole style.
3269    ///
3270    /// The content chokepoint used to `set_css_props(vec![patch])` here,
3271    /// which REPLACED the entire inline style — a gallery panel patched to
3272    /// `display: flex` lost its `position: absolute; top: ...` and flowed
3273    /// into the row, off-window. Remove-then-append also keeps repeated
3274    /// toggles from growing the style without bound.
3275    pub fn upsert_inline_css_property(&mut self, prop: azul_css::props::property::CssProperty) {
3276        use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
3277
3278        let ty = prop.get_type();
3279        let mut rules = mem::take(&mut self.style.rules).into_library_owned_vec();
3280        for rule in &mut rules {
3281            if !rule.conditions.as_ref().is_empty() {
3282                continue;
3283            }
3284            let mut decls = mem::take(&mut rule.declarations).into_library_owned_vec();
3285            decls.retain(|d| match d {
3286                CssDeclaration::Static(p) => p.get_type() != ty,
3287                CssDeclaration::Dynamic(_) => true,
3288            });
3289            rule.declarations = decls.into();
3290        }
3291        // Drop rules the retain above emptied out entirely.
3292        rules.retain(|r| !r.declarations.as_ref().is_empty());
3293        rules.push(CssRuleBlock {
3294            path: CssPath {
3295                selectors: Vec::new().into(),
3296            },
3297            declarations: alloc::vec![CssDeclaration::Static(prop)].into(),
3298            conditions: Vec::new().into(),
3299            priority: rule_priority::INLINE,
3300        });
3301        self.style.rules = rules.into();
3302    }
3303    /// Replace this node's inline style with a `Css` value. The Css's rules apply only
3304    /// to this node (implicit `:scope`).
3305    #[inline]
3306    pub fn set_style(&mut self, style: azul_css::css::Css) {
3307        self.style = style;
3308    }
3309    #[inline]
3310    pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
3311        self.extra
3312            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3313            .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
3314    }
3315    #[inline]
3316    pub fn set_svg_data(&mut self, data: SvgNodeData) {
3317        self.extra
3318            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3319            .svg_data = Some(data);
3320    }
3321    #[inline]
3322    pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
3323        self.flags.set_tab_index(Some(tab_index));
3324    }
3325    #[inline]
3326    pub const fn set_contenteditable(&mut self, contenteditable: bool) {
3327        self.flags.set_contenteditable_mut(contenteditable);
3328    }
3329    #[inline]
3330    #[must_use]
3331    pub const fn is_contenteditable(&self) -> bool {
3332        self.flags.is_contenteditable()
3333    }
3334    #[inline]
3335    pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
3336        self.accessibility = Some(Box::new(accessibility_info));
3337    }
3338
3339    /// Marks this node as an anonymous box (generated for table layout).
3340    #[inline]
3341    pub const fn set_anonymous(&mut self, is_anonymous: bool) {
3342        self.flags.set_anonymous(is_anonymous);
3343    }
3344    #[inline]
3345    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
3346        self.extra
3347            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3348            .menu_bar = Some(Box::new(menu_bar));
3349    }
3350    #[inline]
3351    pub fn set_context_menu(&mut self, context_menu: Menu) {
3352        self.extra
3353            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3354            .context_menu = Some(Box::new(context_menu));
3355    }
3356
3357    /// Sets a stable key for this node used in reconciliation.
3358    ///
3359    /// This key is used to track node identity across DOM updates, enabling
3360    /// the framework to distinguish between "moving" a node and "destroying/creating" one.
3361    /// This is crucial for correct lifecycle events when lists are reordered.
3362    ///
3363    /// # Example
3364    /// ```rust
3365    /// # use azul_core::dom::NodeData;
3366    /// # let mut node_data = NodeData::create_div();
3367    /// node_data.set_key("user-123");
3368    /// ```
3369    #[inline]
3370    pub fn set_key<K: Hash>(&mut self, key: K) {
3371        use core::hash::Hasher;
3372        let mut hasher = crate::hash::DefaultHasher::new();
3373        key.hash(&mut hasher);
3374        self.extra
3375            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3376            .key = Some(hasher.finish());
3377    }
3378
3379    /// Gets the key for this node, if set.
3380    #[inline]
3381    #[must_use]
3382    pub fn get_key(&self) -> Option<u64> {
3383        self.extra.as_ref().and_then(|ext| ext.key)
3384    }
3385
3386    /// Sets a dataset merge callback for this node.
3387    ///
3388    /// The merge callback is invoked during reconciliation when a node from the
3389    /// previous frame is matched with a node in the new frame. It allows heavy
3390    /// resources (video decoders, GL textures, network connections) to be
3391    /// transferred from the old node to the new node instead of being destroyed.
3392    ///
3393    /// # Type Safety
3394    ///
3395    /// The callback stores the `TypeId` of `T`. During execution, both the old
3396    /// and new datasets must match this type, otherwise the merge is skipped.
3397    ///
3398    /// # Example
3399    /// ```rust,ignore
3400    /// struct VideoPlayer {
3401    ///     url: String,
3402    ///     decoder: Option<DecoderHandle>,
3403    /// }
3404    ///
3405    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
3406    ///     // Transfer the heavy decoder handle from old to new
3407    ///     if let (Some(mut new), Some(old)) = (
3408    ///         new_data.downcast_mut::<VideoPlayer>(),
3409    ///         old_data.downcast_ref::<VideoPlayer>()
3410    ///     ) {
3411    ///         new.decoder = old.decoder.take();
3412    ///     }
3413    ///     new_data
3414    /// }
3415    ///
3416    /// node_data.set_merge_callback(merge_video);
3417    /// ```
3418    #[inline]
3419    pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
3420        self.extra
3421            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3422            .dataset_merge_callback = Some(callback.into());
3423    }
3424
3425    /// Gets the merge callback for this node, if set.
3426    #[inline]
3427    #[must_use]
3428    pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
3429        self.extra
3430            .as_ref()
3431            .and_then(|ext| ext.dataset_merge_callback.clone())
3432    }
3433
3434    /// Sets the component origin for this node.
3435    ///
3436    /// This stamps the node with information about which component rendered it,
3437    /// enabling the debugger to reconstruct the component invocation tree.
3438    #[inline]
3439    pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
3440        self.extra
3441            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
3442            .component_origin = Some(origin);
3443    }
3444
3445    /// Gets the component origin for this node, if set.
3446    #[inline]
3447    #[must_use]
3448    pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
3449        self.extra
3450            .as_ref()
3451            .and_then(|ext| ext.component_origin.as_ref())
3452    }
3453
3454    #[inline]
3455    #[must_use]
3456    pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
3457        self.set_menu_bar(menu_bar);
3458        self
3459    }
3460
3461    #[inline]
3462    #[must_use]
3463    pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
3464        self.set_context_menu(context_menu);
3465        self
3466    }
3467
3468    #[inline]
3469    pub fn add_callback<C: Into<CoreCallback>>(
3470        &mut self,
3471        event: EventFilter,
3472        data: RefAny,
3473        callback: C,
3474    ) {
3475        let callback = callback.into();
3476        let mut v: CoreCallbackDataVec = Vec::new().into();
3477        mem::swap(&mut v, &mut self.callbacks);
3478        let mut v = v.into_library_owned_vec();
3479        v.push(CoreCallbackData {
3480            event,
3481            refany: data,
3482            callback,
3483        });
3484        self.callbacks = v.into();
3485    }
3486
3487    #[inline]
3488    pub fn add_id(&mut self, s: AzString) {
3489        let mut v: AttributeTypeVec = Vec::new().into();
3490        mem::swap(&mut v, self.attributes_mut());
3491        let mut v = v.into_library_owned_vec();
3492        v.push(AttributeType::Id(s));
3493        self.set_attributes(v.into());
3494    }
3495    #[inline]
3496    pub fn add_class(&mut self, s: AzString) {
3497        let mut v: AttributeTypeVec = Vec::new().into();
3498        mem::swap(&mut v, self.attributes_mut());
3499        let mut v = v.into_library_owned_vec();
3500        v.push(AttributeType::Class(s));
3501        self.set_attributes(v.into());
3502    }
3503
3504    /// Add a CSS property with optional conditions (hover, focus, active, etc.).
3505    ///
3506    /// Wraps the property in a single-declaration rule at `rule_priority::INLINE`
3507    /// and appends it to this node's inline style.
3508    #[inline]
3509    pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
3510        use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
3511        let rule = CssRuleBlock {
3512            path: CssPath {
3513                selectors: Vec::new().into(),
3514            },
3515            declarations: vec![CssDeclaration::Static(p.property)].into(),
3516            conditions: p.apply_if,
3517            priority: rule_priority::INLINE,
3518        };
3519        let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
3520        mem::swap(&mut v, &mut self.style.rules);
3521        let mut v = v.into_library_owned_vec();
3522        v.push(rule);
3523        self.style.rules = v.into();
3524    }
3525
3526    /// Calculates a deterministic node hash for this node.
3527    #[must_use]
3528    pub fn calculate_node_data_hash(&self) -> DomNodeHash {
3529        use core::hash::Hasher;
3530        let mut hasher = crate::hash::DefaultHasher::new();
3531        self.hash(&mut hasher);
3532        let h = hasher.finish();
3533        DomNodeHash { inner: h }
3534    }
3535
3536    /// Calculates a structural hash for DOM reconciliation that ignores text content.
3537    ///
3538    /// This hash is used for matching nodes across DOM frames where the text content
3539    /// may have changed (e.g., contenteditable text being edited). It hashes:
3540    /// - Node type discriminant (but NOT the text content for Text nodes)
3541    /// - IDs and classes
3542    /// - Attributes (but NOT contenteditable state which may change with focus)
3543    /// - Callback events and types
3544    ///
3545    /// This allows a Text("Hello") node to match Text("Hello World") during reconciliation,
3546    /// preserving cursor position and selection state.
3547    #[must_use]
3548    pub fn calculate_structural_hash(&self) -> DomNodeHash {
3549        use core::hash::Hasher;
3550        use core::hash::Hasher as StdHasher;
3551
3552        let mut hasher = crate::hash::DefaultHasher::new();
3553
3554        // Hash node type discriminant only, not content
3555        // This means Text("A") and Text("B") have the same structural hash
3556        mem::discriminant(&self.node_type).hash(&mut hasher);
3557
3558        // For VirtualView nodes, hash the callback to distinguish different virtualized views
3559        if self.node_type == NodeType::VirtualView {
3560            if let Some(ext) = self.extra.as_ref() {
3561                if let Some(vv) = ext.virtual_view.as_ref() {
3562                    vv.hash(&mut hasher);
3563                }
3564            }
3565        }
3566
3567        // For Image nodes, hash the image reference to distinguish different images.
3568        // For callback images, hash the callback function pointer and RefAny type ID
3569        // instead of the heap pointer, so that the same callback produces the same
3570        // structural hash across frames (the heap pointer differs each frame because
3571        // ImageRef::new() does Box::into_raw(Box::new(...))).
3572        if let NodeType::Image(ref img_ref) = self.node_type {
3573            match img_ref.get_data() {
3574                crate::resources::DecodedImage::Callback(cb) => {
3575                    // Hash callback function pointer (stable across frames)
3576                    cb.callback.cb.hash(&mut hasher);
3577                    // Hash RefAny type ID (not instance pointer)
3578                    cb.refany.get_type_id().hash(&mut hasher);
3579                }
3580                _ => {
3581                    // Raw images / GL textures: hash normally (pointer identity)
3582                    img_ref.hash(&mut hasher);
3583                }
3584            }
3585        }
3586
3587        // Hash IDs and classes - these are structural and shouldn't change
3588        // (They are now stored as AttributeType::Id / AttributeType::Class in attributes)
3589        for attr in self.attributes().as_ref() {
3590            match attr {
3591                AttributeType::Id(s) => {
3592                    0u8.hash(&mut hasher);
3593                    s.as_str().hash(&mut hasher);
3594                }
3595                AttributeType::Class(s) => {
3596                    1u8.hash(&mut hasher);
3597                    s.as_str().hash(&mut hasher);
3598                }
3599                _ => {}
3600            }
3601        }
3602
3603        // Hash other attributes - but skip contenteditable since that might change
3604        // Also skip Id/Class since they were already hashed above
3605        for attr in self.attributes().as_ref() {
3606            if !matches!(
3607                attr,
3608                AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)
3609            ) {
3610                attr.hash(&mut hasher);
3611            }
3612        }
3613
3614        // Hash callback events (not the actual callback function pointers)
3615        for callback in self.callbacks.as_ref() {
3616            callback.event.hash(&mut hasher);
3617        }
3618
3619        let h = hasher.finish();
3620        DomNodeHash { inner: h }
3621    }
3622
3623    #[inline]
3624    #[must_use]
3625    pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
3626        self.set_tab_index(tab_index);
3627        self
3628    }
3629    #[inline]
3630    #[must_use]
3631    pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
3632        self.set_contenteditable(contenteditable);
3633        self
3634    }
3635    #[inline]
3636    #[must_use]
3637    pub fn with_node_type(mut self, node_type: NodeType) -> Self {
3638        self.set_node_type(node_type);
3639        self
3640    }
3641    #[inline]
3642    #[must_use]
3643    pub fn with_callback<C: Into<CoreCallback>>(
3644        mut self,
3645        event: EventFilter,
3646        data: RefAny,
3647        callback: C,
3648    ) -> Self {
3649        self.add_callback(event, data, callback);
3650        self
3651    }
3652    #[inline]
3653    #[must_use]
3654    pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
3655        self.set_dataset(data);
3656        self
3657    }
3658    #[inline]
3659    #[must_use]
3660    pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
3661        self.set_ids_and_classes(ids_and_classes);
3662        self
3663    }
3664    #[inline]
3665    #[must_use]
3666    pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
3667        self.callbacks = callbacks;
3668        self
3669    }
3670    /// Legacy: builder-form of `set_css_props`. Each `CssPropertyWithConditions`
3671    /// becomes a single-declaration rule at `rule_priority::INLINE`.
3672    /// Prefer `with_style(Css)` for new code.
3673    #[inline]
3674    #[must_use]
3675    pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
3676        self.style = css_props.into();
3677        self
3678    }
3679    /// Builder-form of `set_style`.
3680    #[inline]
3681    #[must_use]
3682    pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
3683        self.style = style;
3684        self
3685    }
3686
3687    /// Assigns a stable key to this node for reconciliation.
3688    ///
3689    /// This is crucial for performance and correct state preservation when
3690    /// lists of items change order or items are inserted/removed. Without keys,
3691    /// the reconciliation algorithm falls back to hash-based matching.
3692    ///
3693    /// # Example
3694    /// ```rust
3695    /// # use azul_core::dom::NodeData;
3696    /// NodeData::create_div()
3697    ///     .with_key("user-avatar-123");
3698    /// ```
3699    #[inline]
3700    #[must_use]
3701    pub fn with_key<K: Hash>(mut self, key: K) -> Self {
3702        self.set_key(key);
3703        self
3704    }
3705
3706    /// Registers a callback to merge dataset state from the previous frame.
3707    ///
3708    /// This is used for components that maintain heavy internal state (video players,
3709    /// WebGL contexts, network connections) that should not be destroyed and recreated
3710    /// on every render frame.
3711    ///
3712    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
3713    /// returns the `RefAny` that should be used for the new node.
3714    ///
3715    /// # Example
3716    /// ```rust,ignore
3717    /// struct VideoPlayer {
3718    ///     url: String,
3719    ///     decoder_handle: Option<DecoderHandle>,
3720    /// }
3721    ///
3722    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
3723    ///     if let (Some(mut new), Some(old)) = (
3724    ///         new_data.downcast_mut::<VideoPlayer>(),
3725    ///         old_data.downcast_ref::<VideoPlayer>()
3726    ///     ) {
3727    ///         new.decoder_handle = old.decoder_handle.take();
3728    ///     }
3729    ///     new_data
3730    /// }
3731    ///
3732    /// NodeData::create_div()
3733    ///     .with_dataset(RefAny::new(VideoPlayer::new("movie.mp4")).into())
3734    ///     .with_merge_callback(merge_video)
3735    /// ```
3736    #[inline]
3737    #[must_use]
3738    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
3739        self.set_merge_callback(callback);
3740        self
3741    }
3742
3743    /// Parse and set CSS styles with full selector support.
3744    ///
3745    /// This is the unified API for setting inline CSS on a node. It supports:
3746    /// - Simple properties: `color: red; font-size: 14px;`
3747    /// - Pseudo-selectors: `:hover { background: blue; }`
3748    /// - @-rules: `@os linux { font-size: 14px; }`
3749    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
3750    ///
3751    /// # Examples
3752    /// ```rust
3753    /// # use azul_core::dom::NodeData;
3754    /// NodeData::create_div().with_css("
3755    ///     color: blue;
3756    ///     :hover { color: red; }
3757    ///     @os linux { font-size: 14px; }
3758    /// ");
3759    /// ```
3760    pub fn set_css(&mut self, style: &str) {
3761        // Parse via Css::parse_inline so the inline path goes through the same
3762        // selector + nesting machinery as author CSS. Rules are tagged
3763        // `rule_priority::INLINE` and appended to whatever this node already has.
3764        let parsed = azul_css::css::Css::parse_inline(style);
3765        let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
3766        mem::swap(&mut current, &mut self.style.rules);
3767        let mut v = current.into_library_owned_vec();
3768        v.extend(parsed.rules.into_library_owned_vec());
3769        self.style.rules = v.into();
3770    }
3771
3772    /// Builder method for `set_css`
3773    #[must_use]
3774    pub fn with_css(mut self, style: &str) -> Self {
3775        self.set_css(style);
3776        self
3777    }
3778
3779    #[inline]
3780    #[must_use]
3781    pub const fn swap_with_default(&mut self) -> Self {
3782        let mut s = Self::create_div();
3783        mem::swap(&mut s, self);
3784        s
3785    }
3786
3787    #[inline]
3788    #[must_use]
3789    pub fn copy_special(&self) -> Self {
3790        Self {
3791            node_type: self.node_type.to_library_owned_nodetype(),
3792            style: self.style.clone(),
3793            callbacks: self.callbacks.clone(),
3794            flags: self.flags,
3795            accessibility: self.accessibility.clone(),
3796            extra: self.extra.clone(),
3797        }
3798    }
3799
3800    /// Like [`copy_special`], but MOVES the inline `style` and the `extra` (`NodeDataExt`)
3801    /// box out of `self` into the returned copy instead of cloning them.
3802    ///
3803    /// Both the derived `Clone` for the `CssProperty` values inside `style` AND the derived
3804    /// `Clone` for `Box<NodeDataExt>` (which transitively clones an `AttributeTypeVec` of
3805    /// `AzString`s, menus, etc.) lower to indirect-jump jump tables that remill mis-lifts on
3806    /// the web backend: the mis-lifted clone reads/writes wrong-sized data, which on the
3807    /// stack clobbers the adjacent `style` temporary inside `copy_special` and produces a
3808    /// "memory access out of bounds" later in the cascade (`StyledDom::create` → `restyle`'s
3809    /// inheritance loop reads the corrupted `style`). Native builds are unaffected.
3810    ///
3811    /// `convert_dom_into_compact_dom` consumes the `Dom`, so moving these fields out is sound:
3812    /// `copy_special` then clones an EMPTY style + `None` extra (no broken clone runs), and we
3813    /// restore the moved-out values afterward. Mirrors the pre-existing `style`-only fix.
3814    pub(crate) fn copy_special_moving_complex(&mut self) -> Self {
3815        // WEB-LIFT (2026-06-03): `copy_special`'s `to_library_owned_nodetype()` RECONSTRUCTS the
3816        // node_type (Text/Image arms clone the boxed AzString + rebuild the variant); the lifted
3817        // sret store of that data-bearing variant DROPS the whole thing (disc 177->0 AND the box
3818        // ptr -> styled_dom text node_type = all-zero, box LOST). Earlier attempts to fix this
3819        // "trapped" — but that was the missing `-C target-feature=-lse` build flag (LSE atomics
3820        // remill can't lift), NOT this code. With -lse + the fork remill, MOVE the node_type out
3821        // bitwise instead of reconstructing it: transfers the ORIGINAL box (preserving disc + the
3822        // AzString) with no clone. The Dom is consumed by convert_dom_into_compact_dom so moving is
3823        // sound; self.node_type becomes Div (no heap) -> dropped trivially. ptr::write avoids
3824        // dropping copy's placeholder Div (whose auto-Drop disc-match could mis-lift).
3825        let taken_style = mem::take(&mut self.style);
3826        let taken_extra = self.extra.take();
3827        let taken_node_type = mem::replace(&mut self.node_type, NodeType::Div);
3828        let mut copy = self.copy_special();
3829        // SAFETY: `&raw mut copy.node_type` is aligned and points at an initialized
3830        // `NodeType` (the placeholder `Div` that `copy_special` reconstructed from
3831        // `self.node_type`, which we replaced with `NodeType::Div` above). `ptr::write`
3832        // overwrites it WITHOUT running its `Drop` — this is deliberate (the Drop
3833        // mis-lifts on the web backend) and leaks nothing, because the overwritten
3834        // value is a heap-free `Div`. Kept unsafe (not a plain `=` assignment)
3835        // specifically to skip that Drop.
3836        unsafe {
3837            core::ptr::write(&raw mut copy.node_type, taken_node_type);
3838        }
3839        copy.style = taken_style;
3840        copy.extra = taken_extra;
3841        copy
3842    }
3843
3844    #[must_use]
3845    pub fn is_focusable(&self) -> bool {
3846        // Inherently focusable elements per HTML spec
3847        if matches!(
3848            self.node_type,
3849            NodeType::A
3850                | NodeType::Button
3851                | NodeType::Input
3852                | NodeType::Select
3853                | NodeType::TextArea
3854        ) {
3855            return true;
3856        }
3857        // Contenteditable elements are implicitly focusable (W3C spec)
3858        if self.is_contenteditable() {
3859            return true;
3860        }
3861        // Element is focusable if it has a tab index or any focus-related callback
3862        self.get_tab_index().is_some()
3863            || self
3864                .get_callbacks()
3865                .iter()
3866                .any(|cb| cb.event.is_focus_callback())
3867    }
3868
3869    /// Returns true if this element has "activation behavior" per HTML5 spec.
3870    ///
3871    /// Elements with activation behavior can be activated via Enter or Space key
3872    /// when focused, which generates a synthetic click event.
3873    ///
3874    /// Per HTML5 spec, elements with activation behavior include:
3875    /// - Button elements
3876    /// - Input elements (submit, button, reset, checkbox, radio)
3877    /// - Anchor elements with href
3878    /// - Any element with a click callback (implicit activation)
3879    ///
3880    /// See: <https://html.spec.whatwg.org/multipage/interaction.html#activation-behavior>
3881    #[must_use]
3882    pub fn has_activation_behavior(&self) -> bool {
3883        use crate::events::{EventFilter, HoverEventFilter};
3884
3885        // Inherently activatable elements per HTML spec
3886        if matches!(self.node_type, NodeType::A | NodeType::Button) {
3887            return true;
3888        }
3889
3890        // Check for click callback (most common case for Azul)
3891        // In Azul, "click" is typically LeftMouseUp
3892        // `Click` is the activation filter; MouseUp / LeftMouseUp stay
3893        // recognised so a widget that still listens for a raw release (a
3894        // drag-release, or code predating `On::Click`) is still activatable
3895        // from the keyboard and from assistive technology.
3896        let has_click_callback = self.get_callbacks().iter().any(|cb| {
3897            matches!(
3898                cb.event,
3899                EventFilter::Hover(
3900                    HoverEventFilter::Click
3901                        | HoverEventFilter::MouseUp
3902                        | HoverEventFilter::LeftMouseUp
3903                )
3904            )
3905        });
3906
3907        if has_click_callback {
3908            return true;
3909        }
3910
3911        // Check accessibility role for button-like elements
3912        if let Some(ref accessibility) = self.accessibility {
3913            use crate::a11y::AccessibilityRole;
3914            match accessibility.role {
3915                AccessibilityRole::PushButton  // Button
3916                | AccessibilityRole::Link
3917                | AccessibilityRole::CheckButton  // Checkbox
3918                | AccessibilityRole::RadioButton  // Radio
3919                | AccessibilityRole::MenuItem
3920                | AccessibilityRole::PageTab  // Tab
3921                => return true,
3922                _ => {}
3923            }
3924        }
3925
3926        false
3927    }
3928
3929    /// Returns true if this element is currently activatable.
3930    ///
3931    /// An element is activatable if it has activation behavior AND is not disabled.
3932    /// This checks for common disability patterns (aria-disabled, disabled attribute).
3933    #[must_use]
3934    pub fn is_activatable(&self) -> bool {
3935        if !self.has_activation_behavior() {
3936            return false;
3937        }
3938
3939        // Check for disabled state in accessibility info
3940        if let Some(ref accessibility) = self.accessibility {
3941            // Check if explicitly marked as unavailable
3942            if accessibility
3943                .states
3944                .as_ref()
3945                .iter()
3946                .any(|s| matches!(s, AccessibilityState::Unavailable))
3947            {
3948                return false;
3949            }
3950        }
3951
3952        // Not disabled, so activatable
3953        true
3954    }
3955
3956    /// Returns the tab index for this element.
3957    ///
3958    /// Tab index determines keyboard navigation order:
3959    /// - `None`: Not in tab order (unless naturally focusable)
3960    /// - `Some(-1)`: Focusable programmatically but not via Tab
3961    /// - `Some(0)`: In natural tab order
3962    /// - `Some(n > 0)`: In tab order with priority n (higher = later)
3963    #[must_use]
3964    pub fn get_effective_tabindex(&self) -> Option<i32> {
3965        self.flags.get_tab_index().map_or_else(
3966            || {
3967                if self
3968                    .get_callbacks()
3969                    .iter()
3970                    .any(|cb| cb.event.is_focus_callback())
3971                {
3972                    Some(0)
3973                } else {
3974                    None
3975                }
3976            },
3977            |tab_idx| match tab_idx {
3978                TabIndex::Auto => Some(0),
3979                TabIndex::OverrideInParent(n) => Some(i32::try_from(n).unwrap_or(i32::MAX)),
3980                TabIndex::NoKeyboardFocus => Some(-1),
3981            },
3982        )
3983    }
3984
3985    /// Returns the accessible label for this node.
3986    ///
3987    /// Priority: `aria-label` attribute > `alt` attribute > `title` attribute > None.
3988    /// Does NOT include child text — the caller should collect that separately
3989    /// using the DOM hierarchy.
3990    #[must_use]
3991    pub fn get_accessible_label(&self) -> Option<&str> {
3992        for attr in self.attributes().as_ref() {
3993            if let AttributeType::AriaLabel(s) = attr {
3994                return Some(s.as_str());
3995            }
3996        }
3997        for attr in self.attributes().as_ref() {
3998            match attr {
3999                AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
4000                _ => {}
4001            }
4002        }
4003        None
4004    }
4005
4006    /// Returns the accessible value for this node.
4007    ///
4008    /// Priority: `value` attribute > None.
4009    /// For text inputs, this is the input's current value.
4010    #[must_use]
4011    pub fn get_accessible_value(&self) -> Option<&str> {
4012        for attr in self.attributes().as_ref() {
4013            if let AttributeType::Value(s) = attr {
4014                return Some(s.as_str());
4015            }
4016        }
4017        None
4018    }
4019
4020    /// Whether this node asked to take focus when its window first appears
4021    /// (the `autofocus` attribute).
4022    #[must_use]
4023    pub fn has_autofocus(&self) -> bool {
4024        self.attributes()
4025            .as_ref()
4026            .iter()
4027            .any(|a| matches!(a, AttributeType::Autofocus))
4028    }
4029
4030    /// Returns the placeholder text for this node.
4031    #[must_use]
4032    pub fn get_placeholder(&self) -> Option<&str> {
4033        for attr in self.attributes().as_ref() {
4034            if let AttributeType::Placeholder(s) = attr {
4035                return Some(s.as_str());
4036            }
4037        }
4038        None
4039    }
4040
4041    pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
4042        self.extra.as_mut()?.virtual_view.as_mut()
4043    }
4044
4045    #[must_use]
4046    pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
4047        self.extra.as_ref()?.virtual_view.as_ref()
4048    }
4049
4050    pub fn get_render_image_callback_node(
4051        &mut self,
4052    ) -> Option<(&mut CoreImageCallback, ImageRefHash)> {
4053        match &mut self.node_type {
4054            NodeType::Image(ref mut img) => {
4055                let hash = image_ref_get_hash(img.as_ref());
4056                img.as_mut().get_image_callback_mut().map(|r| (r, hash))
4057            }
4058            _ => None,
4059        }
4060    }
4061
4062    pub fn debug_print_start(
4063        &self,
4064        css_cache: &CssPropertyCache,
4065        node_id: &NodeId,
4066        node_state: &StyledNodeState,
4067    ) -> String {
4068        let html_type = self.node_type.get_path();
4069        let attributes_string = node_data_to_string(self);
4070        let style = css_cache.get_computed_css_style_string(self, node_id, node_state);
4071        format!(
4072            "<{} data-az-node-id=\"{}\" {} {style}>",
4073            html_type,
4074            node_id.index(),
4075            attributes_string,
4076            style = if style.trim().is_empty() {
4077                String::new()
4078            } else {
4079                format!("style=\"{style}\"")
4080            }
4081        )
4082    }
4083
4084    #[must_use]
4085    pub fn debug_print_end(&self) -> String {
4086        let html_type = self.node_type.get_path();
4087        format!("</{html_type}>")
4088    }
4089}
4090
4091impl crate::events::ActivationBehavior for NodeData {
4092    fn has_activation_behavior(&self) -> bool {
4093        Self::has_activation_behavior(self)
4094    }
4095
4096    fn is_activatable(&self) -> bool {
4097        Self::is_activatable(self)
4098    }
4099}
4100
4101impl crate::events::Focusable for NodeData {
4102    fn get_tabindex(&self) -> Option<i32> {
4103        self.get_effective_tabindex()
4104    }
4105
4106    fn is_focusable(&self) -> bool {
4107        Self::is_focusable(self)
4108    }
4109
4110    fn is_naturally_focusable(&self) -> bool {
4111        matches!(
4112            self.node_type,
4113            NodeType::A
4114                | NodeType::Button
4115                | NodeType::Input
4116                | NodeType::Select
4117                | NodeType::TextArea
4118        )
4119    }
4120}
4121
4122/// A unique, runtime-generated identifier for a single `Dom` instance.
4123#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
4124#[repr(C)]
4125pub struct DomId {
4126    pub inner: usize,
4127}
4128
4129impl fmt::Display for DomId {
4130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4131        write!(f, "{}", self.inner)
4132    }
4133}
4134
4135impl DomId {
4136    pub const ROOT_ID: Self = Self { inner: 0 };
4137}
4138
4139impl Default for DomId {
4140    fn default() -> Self {
4141        Self::ROOT_ID
4142    }
4143}
4144
4145impl_option!(
4146    DomId,
4147    OptionDomId,
4148    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
4149);
4150
4151impl_vec!(
4152    DomId,
4153    DomIdVec,
4154    DomIdVecDestructor,
4155    DomIdVecDestructorType,
4156    DomIdVecSlice,
4157    OptionDomId
4158);
4159impl_vec_debug!(DomId, DomIdVec);
4160impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
4161impl_vec_partialeq!(DomId, DomIdVec);
4162impl_vec_partialord!(DomId, DomIdVec);
4163
4164/// A UUID for a DOM node within a `LayoutWindow`.
4165#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
4166#[repr(C)]
4167pub struct DomNodeId {
4168    /// The ID of the `Dom` this node belongs to.
4169    pub dom: DomId,
4170    /// The hierarchical ID of the node within its `Dom`.
4171    pub node: NodeHierarchyItemId,
4172}
4173
4174impl_option!(
4175    DomNodeId,
4176    OptionDomNodeId,
4177    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
4178);
4179
4180impl_vec!(
4181    DomNodeId,
4182    DomNodeIdVec,
4183    DomNodeIdVecDestructor,
4184    DomNodeIdVecDestructorType,
4185    DomNodeIdVecSlice,
4186    OptionDomNodeId
4187);
4188impl_vec_debug!(DomNodeId, DomNodeIdVec);
4189impl_vec_clone!(DomNodeId, DomNodeIdVec, DomNodeIdVecDestructor);
4190impl_vec_partialeq!(DomNodeId, DomNodeIdVec);
4191impl_vec_partialord!(DomNodeId, DomNodeIdVec);
4192
4193impl DomNodeId {
4194    pub const ROOT: Self = Self {
4195        dom: DomId::ROOT_ID,
4196        node: NodeHierarchyItemId::NONE,
4197    };
4198}
4199
4200/// The document model, similar to HTML. This is a create-only structure, you don't actually read
4201/// anything back from it. It's designed for ease of construction.
4202///
4203/// This is the "slow" tree-based DOM. For bulk construction (XML parsing),
4204/// use `FastDom` which builds flat arenas directly and skips the tree→arena conversion.
4205#[repr(C)]
4206#[derive(PartialEq, Clone)]
4207pub struct Dom {
4208    /// The data for the root node of this DOM (or sub-DOM).
4209    pub root: NodeData,
4210    /// The children of this DOM node.
4211    pub children: DomVec,
4212    /// Ordered list of CSS stylesheets to apply to this DOM subtree.
4213    /// Stylesheets are applied in push order during the single deferred cascade pass.
4214    /// Later entries override earlier ones (higher cascade priority).
4215    pub css: azul_css::css::CssVec,
4216    // Tracks the number of sub-children of the current children, so that
4217    // the `Dom` can be converted into a `CompactDom`.
4218    //
4219    // AUDIT: this is a cached count that MUST equal the recursive
4220    // `1-per-descendant` total of `children`. The builder methods
4221    // (`add_child` / `set_children` / `with_child*` / `FromIterator`) keep it in
4222    // sync, but `children` is a public field — mutating it directly desyncs this
4223    // counter. A too-small value makes `convert_dom_into_compact_dom` under-allocate
4224    // its arenas and panic on out-of-bounds writes. Call
4225    // `fixup_children_estimated()` after any direct `children` mutation;
4226    // `StyledDom::new` already does so as a safety net. Debug builds assert
4227    // consistency in the builder methods (see `recompute_estimated_total_children`).
4228    pub estimated_total_children: usize,
4229}
4230
4231/// CSS stylesheet associated with a specific node ID in the flat arena.
4232///
4233/// In the tree DOM, each node carries its own `css` field. In the flat arena,
4234/// we record which node a stylesheet scopes to (e.g. for `<style>` tags
4235/// in different parts of the document).
4236#[repr(C)]
4237#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
4238pub struct CssWithNodeId {
4239    /// 1-based encoded `NodeId` (0 = root / global scope).
4240    pub node_id: usize,
4241    /// The CSS stylesheet.
4242    pub css: azul_css::css::Css,
4243}
4244
4245impl_vec!(
4246    CssWithNodeId,
4247    CssWithNodeIdVec,
4248    CssWithNodeIdVecDestructor,
4249    CssWithNodeIdVecDestructorType,
4250    CssWithNodeIdVecSlice,
4251    OptionCssWithNodeId
4252);
4253impl_option!(
4254    CssWithNodeId,
4255    OptionCssWithNodeId,
4256    copy = false,
4257    [Debug, Clone, PartialEq, Eq, PartialOrd]
4258);
4259impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
4260impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
4261impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
4262impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
4263impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
4264
4265/// Arena-based DOM for bulk construction (e.g. XML/XHTML parsing).
4266/// The hierarchy and node data are stored in two parallel flat vectors,
4267/// skipping the tree→arena conversion step entirely.
4268///
4269/// Use `FastDom::into_dom()` to convert to a tree-based `Dom` if needed.
4270/// `StyledDom::create_from_fast_dom()` consumes this directly without conversion.
4271#[repr(C)]
4272#[derive(Debug, Clone, PartialEq, PartialOrd)]
4273pub struct FastDom {
4274    /// Flat arena of parent/child/sibling relationships.
4275    pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
4276    /// Flat arena of node data, parallel to `node_hierarchy`.
4277    pub node_data: NodeDataVec,
4278    /// CSS stylesheets with the node ID they scope to.
4279    pub css: CssWithNodeIdVec,
4280}
4281
4282// Manual Eq/Hash/Ord impls that skip the transient `css` field,
4283// since CssVec does not implement Eq/Hash/Ord.
4284impl Eq for Dom {}
4285
4286impl Hash for Dom {
4287    fn hash<H: Hasher>(&self, state: &mut H) {
4288        self.root.hash(state);
4289        self.children.hash(state);
4290        self.estimated_total_children.hash(state);
4291    }
4292}
4293
4294// PartialOrd delegates to the field-wise Ord so the two never diverge.
4295impl PartialOrd for Dom {
4296    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
4297        Some(self.cmp(other))
4298    }
4299}
4300impl Ord for Dom {
4301    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
4302        self.root
4303            .cmp(&other.root)
4304            .then_with(|| self.children.cmp(&other.children))
4305            .then_with(|| {
4306                self.estimated_total_children
4307                    .cmp(&other.estimated_total_children)
4308            })
4309    }
4310}
4311
4312impl_option!(
4313    Dom,
4314    OptionDom,
4315    copy = false,
4316    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
4317);
4318
4319impl_vec!(
4320    Dom,
4321    DomVec,
4322    DomVecDestructor,
4323    DomVecDestructorType,
4324    DomVecSlice,
4325    OptionDom
4326);
4327impl_vec_clone!(Dom, DomVec, DomVecDestructor);
4328impl_vec_mut!(Dom, DomVec);
4329impl_vec_debug!(Dom, DomVec);
4330impl_vec_partialord!(Dom, DomVec);
4331impl_vec_ord!(Dom, DomVec);
4332impl_vec_partialeq!(Dom, DomVec);
4333impl_vec_eq!(Dom, DomVec);
4334impl_vec_hash!(Dom, DomVec);
4335
4336/// An empty `<body>` DOM. Used as the safe fallback return value when a layout
4337/// callback cannot produce a DOM (e.g. a foreign-language binding's trampoline
4338/// raised, or an app-data downcast failed). `StyledDom` is the post-cascade
4339/// CSSOM; layout callbacks return an un-cascaded `Dom`, so an empty body is the
4340/// natural "nothing to show" default.
4341impl Default for Dom {
4342    fn default() -> Self {
4343        Self::create_body()
4344    }
4345}
4346
4347impl Dom {
4348    // ----- DOM CONSTRUCTORS
4349
4350    /// Creates an empty DOM with a give `NodeType`. Note: This is a `const fn` and
4351    /// doesn't allocate, it only allocates once you add at least one child node.
4352    #[inline]
4353    #[must_use]
4354    pub fn create_node(node_type: NodeType) -> Self {
4355        Self {
4356            root: NodeData::create_node(node_type),
4357            children: Vec::new().into(),
4358            css: Vec::new().into(),
4359            estimated_total_children: 0,
4360        }
4361    }
4362    #[inline]
4363    #[must_use]
4364    pub fn create_from_data(node_data: NodeData) -> Self {
4365        Self {
4366            root: node_data,
4367            children: Vec::new().into(),
4368            css: Vec::new().into(),
4369            estimated_total_children: 0,
4370        }
4371    }
4372
4373    // Document Structure Elements
4374
4375    /// Creates the root HTML element.
4376    ///
4377    /// **Accessibility**: The `<html>` element is the root of an HTML document and should have a
4378    /// `lang` attribute.
4379    #[inline]
4380    #[must_use]
4381    pub const fn create_html() -> Self {
4382        Self {
4383            root: NodeData::create_node(NodeType::Html),
4384            children: DomVec::from_const_slice(&[]),
4385            css: azul_css::css::CssVec::from_const_slice(&[]),
4386            estimated_total_children: 0,
4387        }
4388    }
4389
4390    /// Creates the document head element.
4391    ///
4392    /// **Accessibility**: The `<head>` contains metadata. Use `<title>` for page titles.
4393    #[inline]
4394    #[must_use]
4395    pub const fn create_head() -> Self {
4396        Self {
4397            root: NodeData::create_node(NodeType::Head),
4398            children: DomVec::from_const_slice(&[]),
4399            css: azul_css::css::CssVec::from_const_slice(&[]),
4400            estimated_total_children: 0,
4401        }
4402    }
4403
4404    #[inline]
4405    #[must_use]
4406    pub const fn create_body() -> Self {
4407        Self {
4408            root: NodeData::create_node(NodeType::Body),
4409            children: DomVec::from_const_slice(&[]),
4410            css: azul_css::css::CssVec::from_const_slice(&[]),
4411            estimated_total_children: 0,
4412        }
4413    }
4414
4415    /// Creates a generic block-level container.
4416    ///
4417    /// **Accessibility**: Prefer semantic elements like `<article>`, `<section>`, `<nav>` when
4418    /// applicable.
4419    #[inline]
4420    #[must_use]
4421    pub const fn create_div() -> Self {
4422        Self {
4423            root: NodeData::create_node(NodeType::Div),
4424            children: DomVec::from_const_slice(&[]),
4425            css: azul_css::css::CssVec::from_const_slice(&[]),
4426            estimated_total_children: 0,
4427        }
4428    }
4429
4430    // Semantic Structure Elements
4431
4432    /// Creates an article element.
4433    ///
4434    /// **Accessibility**: Represents self-contained content that could be distributed
4435    /// independently. Screen readers can navigate by articles. Consider adding aria-label for
4436    /// multiple articles.
4437    #[inline]
4438    #[must_use]
4439    pub const fn create_article() -> Self {
4440        Self {
4441            root: NodeData::create_node(NodeType::Article),
4442            children: DomVec::from_const_slice(&[]),
4443            css: azul_css::css::CssVec::from_const_slice(&[]),
4444            estimated_total_children: 0,
4445        }
4446    }
4447
4448    /// Creates a section element.
4449    ///
4450    /// **Accessibility**: Represents a thematic grouping of content with a heading.
4451    /// Should typically have a heading (h1-h6) as a child. Consider aria-labelledby.
4452    #[inline]
4453    #[must_use]
4454    pub const fn create_section() -> Self {
4455        Self {
4456            root: NodeData::create_node(NodeType::Section),
4457            children: DomVec::from_const_slice(&[]),
4458            css: azul_css::css::CssVec::from_const_slice(&[]),
4459            estimated_total_children: 0,
4460        }
4461    }
4462
4463    /// Creates a navigation element.
4464    ///
4465    /// **Accessibility**: Represents navigation links. Screen readers can jump to navigation.
4466    /// Use aria-label to distinguish multiple nav elements (e.g., "Main navigation", "Footer
4467    /// links").
4468    #[inline]
4469    #[must_use]
4470    pub const fn create_nav() -> Self {
4471        Self {
4472            root: NodeData::create_node(NodeType::Nav),
4473            children: DomVec::from_const_slice(&[]),
4474            css: azul_css::css::CssVec::from_const_slice(&[]),
4475            estimated_total_children: 0,
4476        }
4477    }
4478
4479    /// Creates an aside element.
4480    ///
4481    /// **Accessibility**: Represents content tangentially related to main content (sidebars,
4482    /// callouts). Screen readers announce this as complementary content.
4483    #[inline]
4484    #[must_use]
4485    pub const fn create_aside() -> Self {
4486        Self {
4487            root: NodeData::create_node(NodeType::Aside),
4488            children: DomVec::from_const_slice(&[]),
4489            css: azul_css::css::CssVec::from_const_slice(&[]),
4490            estimated_total_children: 0,
4491        }
4492    }
4493
4494    /// Creates a header element.
4495    ///
4496    /// **Accessibility**: Represents introductory content or navigational aids.
4497    /// Can be used for page headers or section headers.
4498    #[inline]
4499    #[must_use]
4500    pub const fn create_header() -> Self {
4501        Self {
4502            root: NodeData::create_node(NodeType::Header),
4503            children: DomVec::from_const_slice(&[]),
4504            css: azul_css::css::CssVec::from_const_slice(&[]),
4505            estimated_total_children: 0,
4506        }
4507    }
4508
4509    /// Creates a footer element.
4510    ///
4511    /// **Accessibility**: Represents footer for nearest section or page.
4512    /// Typically contains copyright, author info, or related links.
4513    #[inline]
4514    #[must_use]
4515    pub const fn create_footer() -> Self {
4516        Self {
4517            root: NodeData::create_node(NodeType::Footer),
4518            children: DomVec::from_const_slice(&[]),
4519            css: azul_css::css::CssVec::from_const_slice(&[]),
4520            estimated_total_children: 0,
4521        }
4522    }
4523
4524    /// Creates a main content element.
4525    ///
4526    /// **Accessibility**: Represents the dominant content. There should be only ONE main per page.
4527    /// Screen readers can jump directly to main content. Do not nest inside
4528    /// article/aside/footer/header/nav.
4529    #[inline]
4530    #[must_use]
4531    pub const fn create_main() -> Self {
4532        Self {
4533            root: NodeData::create_node(NodeType::Main),
4534            children: DomVec::from_const_slice(&[]),
4535            css: azul_css::css::CssVec::from_const_slice(&[]),
4536            estimated_total_children: 0,
4537        }
4538    }
4539
4540    /// Creates a figure element.
4541    ///
4542    /// **Accessibility**: Represents self-contained content like diagrams, photos, code listings.
4543    /// Use with `<figcaption>` to provide a caption. Screen readers associate caption with figure.
4544    #[inline]
4545    #[must_use]
4546    pub const fn create_figure() -> Self {
4547        Self {
4548            root: NodeData::create_node(NodeType::Figure),
4549            children: DomVec::from_const_slice(&[]),
4550            css: azul_css::css::CssVec::from_const_slice(&[]),
4551            estimated_total_children: 0,
4552        }
4553    }
4554
4555    /// Creates a figure caption element.
4556    ///
4557    /// **Accessibility**: Provides a caption for `<figure>`. Screen readers announce this as the
4558    /// figure description.
4559    #[inline]
4560    #[must_use]
4561    pub const fn create_figcaption() -> Self {
4562        Self {
4563            root: NodeData::create_node(NodeType::FigCaption),
4564            children: DomVec::from_const_slice(&[]),
4565            css: azul_css::css::CssVec::from_const_slice(&[]),
4566            estimated_total_children: 0,
4567        }
4568    }
4569
4570    // Interactive Elements
4571
4572    /// Creates a details disclosure element without accessibility information.
4573    ///
4574    /// Prefer [`Dom::create_details`] so that screen readers announce the
4575    /// disclosure widget's purpose.
4576    #[inline]
4577    #[must_use]
4578    pub const fn create_details_no_a11y() -> Self {
4579        Self {
4580            root: NodeData::create_node(NodeType::Details),
4581            children: DomVec::from_const_slice(&[]),
4582            css: azul_css::css::CssVec::from_const_slice(&[]),
4583            estimated_total_children: 0,
4584        }
4585    }
4586
4587    /// Creates a details disclosure element with accessibility information.
4588    ///
4589    /// **Accessibility**: Creates a disclosure widget. Screen readers announce expanded/collapsed
4590    /// state. Must contain a `<summary>` element. Keyboard accessible by default.
4591    ///
4592    /// Use [`Dom::create_details_no_a11y`] only as a deliberate escape hatch.
4593    #[inline]
4594    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
4595    #[must_use]
4596    pub fn create_details(aria: SmallAriaInfo) -> Self {
4597        Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
4598    }
4599
4600    /// Creates an empty summary element for details without accessibility information.
4601    ///
4602    /// Prefer [`Dom::create_summary`] so that screen readers can announce the
4603    /// disclosure heading.
4604    #[inline]
4605    #[must_use]
4606    pub const fn create_summary_no_a11y() -> Self {
4607        Self {
4608            root: NodeData::create_node(NodeType::Summary),
4609            children: DomVec::from_const_slice(&[]),
4610            css: azul_css::css::CssVec::from_const_slice(&[]),
4611            estimated_total_children: 0,
4612        }
4613    }
4614
4615    /// Creates an empty summary element for details with accessibility information.
4616    ///
4617    /// **Accessibility**: The visible heading/label for `<details>`.
4618    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
4619    ///
4620    /// Use [`Dom::create_summary_no_a11y`] only as a deliberate escape hatch.
4621    #[inline]
4622    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
4623    #[must_use]
4624    pub fn create_summary(aria: SmallAriaInfo) -> Self {
4625        Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
4626    }
4627
4628    /// Creates a summary element with text without accessibility information.
4629    ///
4630    /// Prefer [`Dom::create_summary_with_text`] so that screen readers
4631    /// announce the disclosure heading.
4632    #[inline]
4633    pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
4634        Self::create_summary_no_a11y().with_child(
4635            Self::create_text_do_not_use_without_block_level_wrapper(text),
4636        )
4637    }
4638
4639    /// Creates a summary element with text and accessibility information for details.
4640    ///
4641    /// **Accessibility**: The visible heading/label for `<details>`.
4642    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
4643    ///
4644    /// Use [`Dom::create_summary_with_text_no_a11y`] only as a deliberate escape hatch.
4645    #[inline]
4646    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
4647    pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
4648        Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
4649    }
4650
4651    /// Creates a dialog element without accessibility information.
4652    ///
4653    /// Prefer [`Dom::create_dialog`] so that the dialog's purpose, modality,
4654    /// and described-by relationship are surfaced to assistive technologies.
4655    #[inline]
4656    #[must_use]
4657    pub const fn create_dialog_no_a11y() -> Self {
4658        Self {
4659            root: NodeData::create_node(NodeType::Dialog),
4660            children: DomVec::from_const_slice(&[]),
4661            css: azul_css::css::CssVec::from_const_slice(&[]),
4662            estimated_total_children: 0,
4663        }
4664    }
4665
4666    /// Creates a dialog element with accessibility information.
4667    ///
4668    /// **Accessibility**: Represents a modal or non-modal dialog.
4669    /// When opened as modal, focus is trapped. Use aria-label or aria-labelledby.
4670    /// Escape key should close modal dialogs.
4671    ///
4672    /// Use [`Dom::create_dialog_no_a11y`] only as a deliberate escape hatch.
4673    #[inline]
4674    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
4675    #[must_use]
4676    pub fn create_dialog(aria: DialogAriaInfo) -> Self {
4677        Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
4678    }
4679
4680    // Basic Structural Elements
4681
4682    #[inline]
4683    #[must_use]
4684    pub const fn create_br() -> Self {
4685        Self {
4686            root: NodeData::create_node(NodeType::Br),
4687            children: DomVec::from_const_slice(&[]),
4688            css: azul_css::css::CssVec::from_const_slice(&[]),
4689            estimated_total_children: 0,
4690        }
4691    }
4692    /// Creates a RAW text node - read this before using it.
4693    ///
4694    /// **WARNING**: azul does NOT auto-wrap raw text in an anonymous block
4695    /// the way browsers do. A bare text node has NO box of its own: it gets
4696    /// no rect, no clip, and no layout constraints. Every box-model CSS
4697    /// property (width/height/position/overflow/background/border/padding),
4698    /// every callback, every `tab_index` and every `dataset` attached to a
4699    /// text node is silently INERT. This has broken shipped widgets before
4700    /// (text escaping its container, click targets that never fire).
4701    ///
4702    /// A raw text node is only correct as the bare leaf INSIDE a block-level
4703    /// wrapper that carries the styling - `p`, `div`, `h1`... Prefer the
4704    /// `create_*_with_text` family ([`Dom::create_p_with_text`],
4705    /// [`Dom::create_div_with_text`], [`Dom::create_span_with_text`], ...),
4706    /// which builds that shape for you. The engine also logs a warning after
4707    /// layout when it finds a text node used without a containing block.
4708    #[inline]
4709    pub fn create_text_do_not_use_without_block_level_wrapper<S: Into<AzString>>(value: S) -> Self {
4710        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
4711    }
4712    #[inline]
4713    #[must_use]
4714    pub fn create_image(image: ImageRef) -> Self {
4715        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
4716    }
4717    /// Creates an icon node with the given icon name.
4718    ///
4719    /// The icon name should match names from the icon provider (e.g., "home", "settings", "search").
4720    /// Icons are resolved to actual content (font glyph, image, etc.) during `StyledDom` creation
4721    /// based on the configured `IconProvider`.
4722    ///
4723    /// # Example
4724    /// ```rust,ignore
4725    /// Dom::create_icon("home")
4726    ///     .with_class("nav-icon")
4727    /// ```
4728    #[inline]
4729    pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
4730        // The empty text leaf is where icon resolution writes a font icon's
4731        // glyph (the node itself becomes the inline `<span>` box around it):
4732        // the arena is DFS-ordered, so the leaf has to exist from the start -
4733        // see `icon::resolve_icons_in_styled_dom`. `<icon>name</icon>` in
4734        // markup has its spec text in that place.
4735        Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
4736            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(""))
4737    }
4738
4739    /// An icon that can be SWAPPED at runtime, without a full `layout()`.
4740    ///
4741    /// [`create_icon`](Self::create_icon) bakes the spec into the tree the
4742    /// cascade consumes, so changing it means producing a different tree - a
4743    /// full `layout()` - and by the time a callback runs, the DOM it would edit
4744    /// has already been built. This wraps the icon in a `VirtualView` instead:
4745    /// the view owns the spec, renders `create_icon(spec)` as its child DOM,
4746    /// and `CallbackInfo::set_icon` rewrites the spec and asks that ONE view to
4747    /// re-render. Nothing else in the window relayouts.
4748    ///
4749    /// A view rather than something narrower (the way `set_text` rewrites a
4750    /// text node in place) because AN ICON IS NOT ONE NODE: it resolves to a
4751    /// subtree - an SVG icon is a tree of paths - and a nested DOM of arbitrary
4752    /// shape, re-materialized in place, is exactly what a `VirtualView` is.
4753    ///
4754    /// # Sizing
4755    ///
4756    /// The view reports the icon's MEASURED size, so `width`/`height: auto`
4757    /// gives the icon's natural size - a `VirtualView` is a replaced element
4758    /// and sizes like an `<img>`. A definite size still wins, again like an
4759    /// `<img>`, and is what a toolbar or titlebar button should give it: the
4760    /// box is part of that design, and a stated box is right on the FIRST
4761    /// layout rather than once the measurement comes back.
4762    ///
4763    /// # Example
4764    /// ```rust,ignore
4765    /// Dom::create_icon_view("system:window-maximize,maximize")
4766    ///     .with_class("csd-button-icon")
4767    /// ```
4768    #[inline]
4769    #[must_use]
4770    pub fn create_icon_view<S: Into<AzString>>(icon_name: S) -> Self {
4771        crate::icon::icon_view(icon_name)
4772    }
4773
4774    #[inline]
4775    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
4776        Self::create_from_data(NodeData::create_virtual_view(data, callback))
4777    }
4778
4779    /// Creates an invisible `NodeType::GeolocationProbe` node that
4780    /// signals "this subtree needs the user's location". Lays out as
4781    /// zero-size and is skipped in the display list - the framework
4782    /// scans for it at end-of-layout and starts / stops the native
4783    /// `CLLocationManager` / `LocationManager` / `geoclue`
4784    /// subscription. See `SUPER_PLAN_2.md` section 1.5.
4785    #[inline]
4786    #[must_use]
4787    pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
4788        Self::create_node(NodeType::GeolocationProbe(config))
4789    }
4790
4791    // Semantic HTML Elements with Accessibility Guidance
4792
4793    /// Creates a paragraph element.
4794    ///
4795    /// **Accessibility**: Paragraphs provide semantic structure for screen readers.
4796    #[inline]
4797    #[must_use]
4798    pub const fn create_p() -> Self {
4799        Self {
4800            root: NodeData::create_node(NodeType::P),
4801            children: DomVec::from_const_slice(&[]),
4802            css: azul_css::css::CssVec::from_const_slice(&[]),
4803            estimated_total_children: 0,
4804        }
4805    }
4806
4807    /// Creates an empty heading level 1 element.
4808    ///
4809    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
4810    /// per page.
4811    #[inline]
4812    #[must_use]
4813    pub const fn create_h1() -> Self {
4814        Self {
4815            root: NodeData::create_node(NodeType::H1),
4816            children: DomVec::from_const_slice(&[]),
4817            css: azul_css::css::CssVec::from_const_slice(&[]),
4818            estimated_total_children: 0,
4819        }
4820    }
4821
4822    /// Creates a heading level 1 element with text.
4823    ///
4824    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
4825    /// per page.
4826    ///
4827    /// **Parameters:**
4828    /// - `text`: Heading text
4829    #[inline]
4830    pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
4831        Self::create_h1().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4832            text,
4833        ))
4834    }
4835
4836    /// Creates an empty heading level 2 element.
4837    ///
4838    /// **Accessibility**: Use `h2` for major section headings under `h1`.
4839    #[inline]
4840    #[must_use]
4841    pub const fn create_h2() -> Self {
4842        Self {
4843            root: NodeData::create_node(NodeType::H2),
4844            children: DomVec::from_const_slice(&[]),
4845            css: azul_css::css::CssVec::from_const_slice(&[]),
4846            estimated_total_children: 0,
4847        }
4848    }
4849
4850    /// Creates a heading level 2 element with text.
4851    ///
4852    /// **Accessibility**: Use `h2` for major section headings under `h1`.
4853    ///
4854    /// **Parameters:**
4855    /// - `text`: Heading text
4856    #[inline]
4857    pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
4858        Self::create_h2().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4859            text,
4860        ))
4861    }
4862
4863    /// Creates an empty heading level 3 element.
4864    ///
4865    /// **Accessibility**: Use `h3` for subsections under `h2`.
4866    #[inline]
4867    #[must_use]
4868    pub const fn create_h3() -> Self {
4869        Self {
4870            root: NodeData::create_node(NodeType::H3),
4871            children: DomVec::from_const_slice(&[]),
4872            css: azul_css::css::CssVec::from_const_slice(&[]),
4873            estimated_total_children: 0,
4874        }
4875    }
4876
4877    /// Creates a heading level 3 element with text.
4878    ///
4879    /// **Accessibility**: Use `h3` for subsections under `h2`.
4880    ///
4881    /// **Parameters:**
4882    /// - `text`: Heading text
4883    #[inline]
4884    pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
4885        Self::create_h3().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4886            text,
4887        ))
4888    }
4889
4890    /// Creates an empty heading level 4 element.
4891    #[inline]
4892    #[must_use]
4893    pub const fn create_h4() -> Self {
4894        Self {
4895            root: NodeData::create_node(NodeType::H4),
4896            children: DomVec::from_const_slice(&[]),
4897            css: azul_css::css::CssVec::from_const_slice(&[]),
4898            estimated_total_children: 0,
4899        }
4900    }
4901
4902    /// Creates a heading level 4 element with text.
4903    ///
4904    /// **Parameters:**
4905    /// - `text`: Heading text
4906    #[inline]
4907    pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
4908        Self::create_h4().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4909            text,
4910        ))
4911    }
4912
4913    /// Creates an empty heading level 5 element.
4914    #[inline]
4915    #[must_use]
4916    pub const fn create_h5() -> Self {
4917        Self {
4918            root: NodeData::create_node(NodeType::H5),
4919            children: DomVec::from_const_slice(&[]),
4920            css: azul_css::css::CssVec::from_const_slice(&[]),
4921            estimated_total_children: 0,
4922        }
4923    }
4924
4925    /// Creates a heading level 5 element with text.
4926    ///
4927    /// **Parameters:**
4928    /// - `text`: Heading text
4929    #[inline]
4930    pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
4931        Self::create_h5().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4932            text,
4933        ))
4934    }
4935
4936    /// Creates an empty heading level 6 element.
4937    #[inline]
4938    #[must_use]
4939    pub const fn create_h6() -> Self {
4940        Self {
4941            root: NodeData::create_node(NodeType::H6),
4942            children: DomVec::from_const_slice(&[]),
4943            css: azul_css::css::CssVec::from_const_slice(&[]),
4944            estimated_total_children: 0,
4945        }
4946    }
4947
4948    /// Creates a heading level 6 element with text.
4949    ///
4950    /// **Parameters:**
4951    /// - `text`: Heading text
4952    #[inline]
4953    pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
4954        Self::create_h6().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4955            text,
4956        ))
4957    }
4958
4959    /// Creates an empty generic inline container (span).
4960    ///
4961    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
4962    /// applicable.
4963    #[inline]
4964    #[must_use]
4965    pub const fn create_span() -> Self {
4966        Self {
4967            root: NodeData::create_node(NodeType::Span),
4968            children: DomVec::from_const_slice(&[]),
4969            css: azul_css::css::CssVec::from_const_slice(&[]),
4970            estimated_total_children: 0,
4971        }
4972    }
4973
4974    /// Creates a generic inline container (span) with text.
4975    ///
4976    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
4977    /// applicable.
4978    ///
4979    /// **Parameters:**
4980    /// - `text`: Span content
4981    #[inline]
4982    pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
4983        Self::create_span().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
4984            text,
4985        ))
4986    }
4987
4988    /// Creates an empty strong importance element.
4989    ///
4990    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning.
4991    #[inline]
4992    #[must_use]
4993    pub const fn create_strong() -> Self {
4994        Self {
4995            root: NodeData::create_node(NodeType::Strong),
4996            children: DomVec::from_const_slice(&[]),
4997            css: azul_css::css::CssVec::from_const_slice(&[]),
4998            estimated_total_children: 0,
4999        }
5000    }
5001
5002    /// Creates a strongly emphasized text element with text (strong importance).
5003    ///
5004    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning. Screen readers can
5005    /// convey the importance. Use for text that has strong importance, seriousness, or urgency.
5006    ///
5007    /// **Parameters:**
5008    /// - `text`: Text to emphasize
5009    #[inline]
5010    pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
5011        Self::create_strong().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5012            text,
5013        ))
5014    }
5015
5016    /// Creates an empty emphasis element (stress emphasis).
5017    ///
5018    /// **Accessibility**: Use `em` instead of `i` for semantic meaning.
5019    #[inline]
5020    #[must_use]
5021    pub const fn create_em() -> Self {
5022        Self {
5023            root: NodeData::create_node(NodeType::Em),
5024            children: DomVec::from_const_slice(&[]),
5025            css: azul_css::css::CssVec::from_const_slice(&[]),
5026            estimated_total_children: 0,
5027        }
5028    }
5029
5030    /// Creates an emphasized text element with text (stress emphasis).
5031    ///
5032    /// **Accessibility**: Use `em` instead of `i` for semantic meaning. Screen readers can
5033    /// convey the emphasis. Use for text that has stress emphasis.
5034    ///
5035    /// **Parameters:**
5036    /// - `text`: Text to emphasize
5037    #[inline]
5038    pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
5039        Self::create_em().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5040            text,
5041        ))
5042    }
5043
5044    /// Creates an empty code element.
5045    ///
5046    /// **Accessibility**: Represents a fragment of computer code.
5047    #[inline]
5048    #[must_use]
5049    pub fn create_code() -> Self {
5050        Self::create_node(NodeType::Code)
5051    }
5052
5053    /// Creates a code/computer code element with text.
5054    ///
5055    /// **Accessibility**: Represents a fragment of computer code. Screen readers can identify
5056    /// this as code content.
5057    ///
5058    /// **Parameters:**
5059    /// - `code`: Code content
5060    #[inline]
5061    pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
5062        Self::create_code().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5063            code,
5064        ))
5065    }
5066
5067    /// Creates an empty preformatted text element.
5068    ///
5069    /// **Accessibility**: Preserves whitespace and line breaks.
5070    #[inline]
5071    #[must_use]
5072    pub fn create_pre() -> Self {
5073        Self::create_node(NodeType::Pre)
5074    }
5075
5076    /// Creates a preformatted text element with text.
5077    ///
5078    /// **Accessibility**: Preserves whitespace and line breaks. Useful for code blocks or
5079    /// ASCII art. Screen readers will read the content as-is.
5080    ///
5081    /// **Parameters:**
5082    /// - `text`: Preformatted content
5083    #[inline]
5084    pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
5085        Self::create_pre().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5086            text,
5087        ))
5088    }
5089
5090    /// Creates an empty blockquote element.
5091    ///
5092    /// **Accessibility**: Represents a section quoted from another source.
5093    #[inline]
5094    #[must_use]
5095    pub fn create_blockquote() -> Self {
5096        Self::create_node(NodeType::BlockQuote)
5097    }
5098
5099    /// Creates a blockquote element with text.
5100    ///
5101    /// **Accessibility**: Represents a section quoted from another source. Screen readers
5102    /// can identify quoted content. Consider adding a `cite` attribute.
5103    ///
5104    /// **Parameters:**
5105    /// - `text`: Quote content
5106    #[inline]
5107    pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
5108        Self::create_blockquote().with_child(
5109            Self::create_text_do_not_use_without_block_level_wrapper(text),
5110        )
5111    }
5112
5113    /// Creates an empty citation element.
5114    ///
5115    /// **Accessibility**: Represents a reference to a creative work.
5116    #[inline]
5117    #[must_use]
5118    pub fn create_cite() -> Self {
5119        Self::create_node(NodeType::Cite)
5120    }
5121
5122    /// Creates a citation element with text.
5123    ///
5124    /// **Accessibility**: Represents a reference to a creative work. Screen readers can
5125    /// identify citations.
5126    ///
5127    /// **Parameters:**
5128    /// - `text`: Citation text
5129    #[inline]
5130    pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
5131        Self::create_cite().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5132            text,
5133        ))
5134    }
5135
5136    /// Creates an empty abbreviation element.
5137    ///
5138    /// **Accessibility**: Represents an abbreviation or acronym. Use with a `title` attribute
5139    /// to provide the full expansion for screen readers.
5140    #[inline]
5141    #[must_use]
5142    pub fn create_abbr() -> Self {
5143        Self::create_node(NodeType::Abbr)
5144    }
5145
5146    /// Creates an abbreviation element with abbreviated text and a `title` expansion.
5147    ///
5148    /// **Accessibility**: Represents an abbreviation or acronym. The `title` attribute
5149    /// provides the full expansion for screen readers.
5150    ///
5151    /// **Parameters:**
5152    /// - `abbr_text`: Abbreviated text
5153    /// - `title`: Full expansion
5154    #[inline]
5155    #[must_use]
5156    pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
5157        Self::create_node(NodeType::Abbr)
5158            .with_attribute(AttributeType::Title(title))
5159            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5160                abbr_text,
5161            ))
5162    }
5163
5164    /// Creates an empty keyboard input element.
5165    ///
5166    /// **Accessibility**: Represents keyboard input or key combinations.
5167    #[inline]
5168    #[must_use]
5169    pub fn create_kbd() -> Self {
5170        Self::create_node(NodeType::Kbd)
5171    }
5172
5173    /// Creates a keyboard input element with text.
5174    ///
5175    /// **Accessibility**: Represents keyboard input or key combinations. Screen readers can
5176    /// identify keyboard instructions.
5177    ///
5178    /// **Parameters:**
5179    /// - `text`: Keyboard instruction
5180    #[inline]
5181    pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
5182        Self::create_kbd().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5183            text,
5184        ))
5185    }
5186
5187    /// Creates an empty sample output element.
5188    ///
5189    /// **Accessibility**: Represents sample output from a program or computing system.
5190    #[inline]
5191    #[must_use]
5192    pub fn create_samp() -> Self {
5193        Self::create_node(NodeType::Samp)
5194    }
5195
5196    /// Creates a sample output element with text.
5197    ///
5198    /// **Accessibility**: Represents sample output from a program or computing system.
5199    ///
5200    /// **Parameters:**
5201    /// - `text`: Sample text
5202    #[inline]
5203    pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
5204        Self::create_samp().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5205            text,
5206        ))
5207    }
5208
5209    /// Creates an empty variable element.
5210    ///
5211    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
5212    #[inline]
5213    #[must_use]
5214    pub fn create_var() -> Self {
5215        Self::create_node(NodeType::Var)
5216    }
5217
5218    /// Creates a variable element with text.
5219    ///
5220    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
5221    ///
5222    /// **Parameters:**
5223    /// - `text`: Variable name
5224    #[inline]
5225    pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
5226        Self::create_var().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5227            text,
5228        ))
5229    }
5230
5231    /// Creates an empty subscript element.
5232    #[inline]
5233    #[must_use]
5234    pub fn create_sub() -> Self {
5235        Self::create_node(NodeType::Sub)
5236    }
5237
5238    /// Creates a subscript element with text.
5239    ///
5240    /// **Accessibility**: Screen readers may announce subscript formatting.
5241    ///
5242    /// **Parameters:**
5243    /// - `text`: Subscript content
5244    #[inline]
5245    pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
5246        Self::create_sub().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5247            text,
5248        ))
5249    }
5250
5251    /// Creates an empty superscript element.
5252    #[inline]
5253    #[must_use]
5254    pub fn create_sup() -> Self {
5255        Self::create_node(NodeType::Sup)
5256    }
5257
5258    /// Creates a superscript element with text.
5259    ///
5260    /// **Accessibility**: Screen readers may announce superscript formatting.
5261    ///
5262    /// **Parameters:**
5263    /// - `text`: Superscript content
5264    #[inline]
5265    pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
5266        Self::create_sup().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5267            text,
5268        ))
5269    }
5270
5271    /// Creates an empty underline element.
5272    #[inline]
5273    #[must_use]
5274    pub fn create_u() -> Self {
5275        Self::create_node(NodeType::U)
5276    }
5277
5278    /// Creates an underline text element with text.
5279    ///
5280    /// **Accessibility**: Screen readers typically don't announce underline formatting.
5281    /// Use semantic elements when possible (e.g., `<em>` for emphasis).
5282    #[inline]
5283    pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
5284        Self::create_u().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5285            text,
5286        ))
5287    }
5288
5289    /// Creates an empty strikethrough element.
5290    #[inline]
5291    #[must_use]
5292    pub fn create_s() -> Self {
5293        Self::create_node(NodeType::S)
5294    }
5295
5296    /// Creates a strikethrough text element with text.
5297    ///
5298    /// **Accessibility**: Represents text that is no longer accurate or relevant.
5299    /// Consider using `<del>` for deleted content with datetime attribute.
5300    #[inline]
5301    pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
5302        Self::create_s().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5303            text,
5304        ))
5305    }
5306
5307    /// Creates an empty mark element.
5308    #[inline]
5309    #[must_use]
5310    pub fn create_mark() -> Self {
5311        Self::create_node(NodeType::Mark)
5312    }
5313
5314    /// Creates a marked/highlighted text element with text.
5315    ///
5316    /// **Accessibility**: Represents text marked for reference or notation purposes.
5317    /// Screen readers may announce this as "highlighted".
5318    #[inline]
5319    pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
5320        Self::create_mark().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5321            text,
5322        ))
5323    }
5324
5325    /// Creates an empty deleted text element.
5326    #[inline]
5327    #[must_use]
5328    pub fn create_del() -> Self {
5329        Self::create_node(NodeType::Del)
5330    }
5331
5332    /// Creates a deleted text element with text.
5333    ///
5334    /// **Accessibility**: Represents deleted content in document edits.
5335    /// Use with `datetime` and `cite` attributes for edit tracking.
5336    #[inline]
5337    pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
5338        Self::create_del().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5339            text,
5340        ))
5341    }
5342
5343    /// Creates an empty inserted text element.
5344    #[inline]
5345    #[must_use]
5346    pub fn create_ins() -> Self {
5347        Self::create_node(NodeType::Ins)
5348    }
5349
5350    /// Creates an inserted text element with text.
5351    ///
5352    /// **Accessibility**: Represents inserted content in document edits.
5353    /// Use with `datetime` and `cite` attributes for edit tracking.
5354    #[inline]
5355    pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
5356        Self::create_ins().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5357            text,
5358        ))
5359    }
5360
5361    /// Creates an empty definition element.
5362    #[inline]
5363    #[must_use]
5364    pub fn create_dfn() -> Self {
5365        Self::create_node(NodeType::Dfn)
5366    }
5367
5368    /// Creates a definition element with text.
5369    ///
5370    /// **Accessibility**: Represents the defining instance of a term.
5371    /// Often used within a definition list or with `<abbr>`.
5372    #[inline]
5373    pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
5374        Self::create_dfn().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5375            text,
5376        ))
5377    }
5378
5379    /// Creates a time element.
5380    ///
5381    /// **Accessibility**: Represents a specific time or date.
5382    /// Use `datetime` attribute for machine-readable format.
5383    ///
5384    /// **Parameters:**
5385    /// - `text`: Human-readable time/date
5386    /// - `datetime`: Optional machine-readable datetime
5387    #[inline]
5388    #[must_use]
5389    pub fn create_time(text: AzString, datetime: OptionString) -> Self {
5390        let mut element = Self::create_node(NodeType::Time).with_child(
5391            Self::create_text_do_not_use_without_block_level_wrapper(text),
5392        );
5393        if let OptionString::Some(dt) = datetime {
5394            element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
5395                attr_name: "datetime".into(),
5396                value: dt,
5397            }));
5398        }
5399        element
5400    }
5401
5402    /// Creates an empty bi-directional override element.
5403    ///
5404    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
5405    #[inline]
5406    #[must_use]
5407    pub fn create_bdo() -> Self {
5408        Self::create_node(NodeType::Bdo)
5409    }
5410
5411    /// Creates a bi-directional override element with text.
5412    ///
5413    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
5414    #[inline]
5415    pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
5416        Self::create_bdo().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5417            text,
5418        ))
5419    }
5420
5421    // Additional inline / text-level elements
5422
5423    /// Creates an empty bold element.
5424    ///
5425    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
5426    #[inline]
5427    #[must_use]
5428    pub fn create_b() -> Self {
5429        Self::create_node(NodeType::B)
5430    }
5431
5432    /// Creates a bold element with text.
5433    ///
5434    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
5435    ///
5436    /// **Parameters:**
5437    /// - `text`: Bold text content
5438    #[inline]
5439    pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
5440        Self::create_b().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5441            text,
5442        ))
5443    }
5444
5445    /// Creates an empty italic element.
5446    ///
5447    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
5448    #[inline]
5449    #[must_use]
5450    pub fn create_i() -> Self {
5451        Self::create_node(NodeType::I)
5452    }
5453
5454    /// Creates an italic element with text.
5455    ///
5456    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
5457    ///
5458    /// **Parameters:**
5459    /// - `text`: Italic text content
5460    #[inline]
5461    pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
5462        Self::create_i().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5463            text,
5464        ))
5465    }
5466
5467    /// Creates an empty small text element.
5468    ///
5469    /// **Accessibility**: Represents side-comments and small print like copyright/legal text.
5470    #[inline]
5471    #[must_use]
5472    pub fn create_small() -> Self {
5473        Self::create_node(NodeType::Small)
5474    }
5475
5476    /// Creates a small text element with text.
5477    ///
5478    /// **Parameters:**
5479    /// - `text`: Small text content
5480    #[inline]
5481    pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
5482        Self::create_small().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5483            text,
5484        ))
5485    }
5486
5487    /// Creates an empty `<big>` element.
5488    ///
5489    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
5490    #[inline]
5491    #[must_use]
5492    pub fn create_big() -> Self {
5493        Self::create_node(NodeType::Big)
5494    }
5495
5496    /// Creates a `<big>` element with text.
5497    ///
5498    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
5499    #[inline]
5500    pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
5501        Self::create_big().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5502            text,
5503        ))
5504    }
5505
5506    /// Creates an empty bi-directional isolate element.
5507    ///
5508    /// **Accessibility**: Used to isolate text whose direction is unknown,
5509    /// keeping it from affecting surrounding bidi layout.
5510    #[inline]
5511    #[must_use]
5512    pub fn create_bdi() -> Self {
5513        Self::create_node(NodeType::Bdi)
5514    }
5515
5516    /// Creates a bi-directional isolate element with text.
5517    ///
5518    /// **Accessibility**: Used to isolate text whose direction is unknown,
5519    /// keeping it from affecting surrounding bidi layout.
5520    #[inline]
5521    pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
5522        Self::create_bdi().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5523            text,
5524        ))
5525    }
5526
5527    /// Creates an empty word break opportunity element.
5528    ///
5529    /// **Note**: `<wbr>` is a self-closing element that suggests a line-break opportunity.
5530    /// It does not take text content.
5531    #[inline]
5532    #[must_use]
5533    pub fn create_wbr() -> Self {
5534        Self::create_node(NodeType::Wbr)
5535    }
5536
5537    /// Creates an empty ruby annotation element.
5538    ///
5539    /// **Accessibility**: Used for East Asian typography to provide
5540    /// pronunciation/translation annotations. Wraps `<rt>`/`<rp>` children.
5541    #[inline]
5542    #[must_use]
5543    pub fn create_ruby() -> Self {
5544        Self::create_node(NodeType::Ruby)
5545    }
5546
5547    /// Creates an empty ruby text element.
5548    ///
5549    /// **Accessibility**: Pronunciation/translation annotation inside `<ruby>`.
5550    #[inline]
5551    #[must_use]
5552    pub fn create_rt() -> Self {
5553        Self::create_node(NodeType::Rt)
5554    }
5555
5556    /// Creates a ruby text element with text.
5557    ///
5558    /// **Parameters:**
5559    /// - `text`: Ruby annotation content
5560    #[inline]
5561    pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
5562        Self::create_rt().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5563            text,
5564        ))
5565    }
5566
5567    /// Creates an empty ruby text container element.
5568    ///
5569    /// **Accessibility**: Container for ruby text annotations.
5570    #[inline]
5571    #[must_use]
5572    pub fn create_rtc() -> Self {
5573        Self::create_node(NodeType::Rtc)
5574    }
5575
5576    /// Creates an empty ruby fallback parenthesis element.
5577    ///
5578    /// **Accessibility**: Provides parentheses around `<rt>` for browsers without ruby support.
5579    #[inline]
5580    #[must_use]
5581    pub fn create_rp() -> Self {
5582        Self::create_node(NodeType::Rp)
5583    }
5584
5585    /// Creates a ruby fallback parenthesis element with text.
5586    ///
5587    /// **Parameters:**
5588    /// - `text`: Parenthesis text (typically "(" or ")")
5589    #[inline]
5590    pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
5591        Self::create_rp().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5592            text,
5593        ))
5594    }
5595
5596    /// Creates a `<data>` element binding a machine-readable value to its content.
5597    ///
5598    /// **Parameters:**
5599    /// - `value`: Machine-readable value for the `value` attribute.
5600    #[inline]
5601    #[must_use]
5602    pub fn create_data(value: AzString) -> Self {
5603        Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
5604    }
5605
5606    /// Creates a `<data>` element with both a machine-readable value and visible text.
5607    ///
5608    /// **Parameters:**
5609    /// - `value`: Machine-readable value for the `value` attribute.
5610    /// - `text`: Human-readable text content.
5611    #[inline]
5612    #[must_use]
5613    pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
5614        Self::create_data(value).with_child(
5615            Self::create_text_do_not_use_without_block_level_wrapper(text),
5616        )
5617    }
5618
5619    /// Creates an empty directory list element.
5620    ///
5621    /// **Note**: Deprecated in HTML5. Use `<ul>` instead.
5622    #[inline]
5623    #[must_use]
5624    pub fn create_dir() -> Self {
5625        Self::create_node(NodeType::Dir)
5626    }
5627
5628    /// Creates an empty SVG container element.
5629    ///
5630    /// **Accessibility**: Provide `aria-label` or `<title>` child for assistive tech.
5631    #[inline]
5632    #[must_use]
5633    pub fn create_svg() -> Self {
5634        Self::create_node(NodeType::Svg)
5635    }
5636
5637    /// Creates an anchor/hyperlink element without accessibility information.
5638    ///
5639    /// Prefer [`Dom::create_a`] so that screen readers get a meaningful label.
5640    ///
5641    /// **Parameters:**
5642    /// - `href`: Link destination URL
5643    /// - `label`: Link text (pass `None` for image-only links with alt text)
5644    #[inline]
5645    #[must_use]
5646    pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
5647        let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
5648        if let OptionString::Some(text) = label {
5649            link = link.with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5650                text,
5651            ));
5652        }
5653        link
5654    }
5655
5656    /// Creates a button element without accessibility information.
5657    ///
5658    /// Prefer [`Dom::create_button`] so that the element has a meaningful accessible
5659    /// name for screen readers.
5660    ///
5661    /// **Parameters:**
5662    /// - `text`: Button label text
5663    #[inline]
5664    #[must_use]
5665    pub fn create_button_no_a11y(text: AzString) -> Self {
5666        Self::create_node(NodeType::Button).with_child(
5667            Self::create_text_do_not_use_without_block_level_wrapper(text),
5668        )
5669    }
5670
5671    /// Creates a label element for form controls without accessibility information.
5672    ///
5673    /// Prefer [`Dom::create_label`] so that screen readers get a descriptive label.
5674    ///
5675    /// **Parameters:**
5676    /// - `for_id`: ID of the associated form control
5677    /// - `text`: Label text
5678    #[inline]
5679    #[must_use]
5680    pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
5681        Self::create_node(NodeType::Label)
5682            .with_attribute(AttributeType::Custom(AttributeNameValue {
5683                attr_name: "for".into(),
5684                value: for_id,
5685            }))
5686            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5687                text,
5688            ))
5689    }
5690
5691    /// Creates an input element without accessibility information.
5692    ///
5693    /// Prefer [`Dom::create_input`] so that screen readers get a descriptive label
5694    /// beyond the HTML `aria-label` attribute.
5695    ///
5696    /// **Parameters:**
5697    /// - `input_type`: Input type (text, password, email, etc.)
5698    /// - `name`: Form field name
5699    /// - `label`: Accessibility label (required)
5700    #[inline]
5701    #[must_use]
5702    pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
5703        Self::create_node(NodeType::Input)
5704            .with_attribute(AttributeType::InputType(input_type))
5705            .with_attribute(AttributeType::Name(name))
5706            .with_attribute(AttributeType::AriaLabel(label))
5707    }
5708
5709    /// Creates a textarea element without accessibility information.
5710    ///
5711    /// Prefer [`Dom::create_textarea`] so that screen readers get an accurate
5712    /// description of the control.
5713    ///
5714    /// **Parameters:**
5715    /// - `name`: Form field name
5716    /// - `label`: Accessibility label (required)
5717    #[inline]
5718    #[must_use]
5719    pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
5720        Self::create_node(NodeType::TextArea)
5721            .with_attribute(AttributeType::Name(name))
5722            .with_attribute(AttributeType::AriaLabel(label))
5723    }
5724
5725    /// Creates a select dropdown element without accessibility information.
5726    ///
5727    /// Prefer [`Dom::create_select`] so that screen readers announce the control
5728    /// appropriately.
5729    ///
5730    /// **Parameters:**
5731    /// - `name`: Form field name
5732    /// - `label`: Accessibility label (required)
5733    #[inline]
5734    #[must_use]
5735    pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
5736        Self::create_node(NodeType::Select)
5737            .with_attribute(AttributeType::Name(name))
5738            .with_attribute(AttributeType::AriaLabel(label))
5739    }
5740
5741    /// Creates an option element for select dropdowns.
5742    ///
5743    /// **Parameters:**
5744    /// - `value`: Option value
5745    /// - `text`: Display text
5746    #[inline]
5747    #[must_use]
5748    pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
5749        Self::create_node(NodeType::SelectOption)
5750            .with_attribute(AttributeType::Value(value))
5751            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(
5752                text,
5753            ))
5754    }
5755
5756    /// Creates an option element for select dropdowns with accessibility information.
5757    ///
5758    /// **Parameters:**
5759    /// - `value`: Option value
5760    /// - `text`: Display text
5761    /// - `aria`: Accessibility information (description, etc.)
5762    ///
5763    /// Use [`Dom::create_option_no_a11y`] only as a deliberate escape hatch.
5764    #[inline]
5765    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
5766    #[must_use]
5767    pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
5768        Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
5769    }
5770
5771    /// Creates an unordered list element.
5772    ///
5773    /// **Accessibility**: Screen readers announce lists and item counts, helping users
5774    /// understand content structure.
5775    #[inline]
5776    #[must_use]
5777    pub fn create_ul() -> Self {
5778        Self::create_node(NodeType::Ul)
5779    }
5780
5781    /// Creates an ordered list element.
5782    ///
5783    /// **Accessibility**: Screen readers announce lists and item counts, helping users
5784    /// understand content structure and numbering.
5785    #[inline]
5786    #[must_use]
5787    pub fn create_ol() -> Self {
5788        Self::create_node(NodeType::Ol)
5789    }
5790
5791    /// Creates a list item element.
5792    ///
5793    /// **Accessibility**: Must be a child of `ul`, `ol`, or `menu`. Screen readers announce
5794    /// list item position (e.g., "2 of 5").
5795    #[inline]
5796    #[must_use]
5797    pub fn create_li() -> Self {
5798        Self::create_node(NodeType::Li)
5799    }
5800
5801    /// Creates a table element without accessibility information.
5802    ///
5803    /// Prefer [`Dom::create_table`] so that screen readers can announce the table's
5804    /// purpose alongside its caption.
5805    #[inline]
5806    #[must_use]
5807    pub fn create_table_no_a11y() -> Self {
5808        Self::create_node(NodeType::Table)
5809    }
5810
5811    /// Creates a table caption element.
5812    ///
5813    /// **Accessibility**: Describes the purpose of the table. Screen readers announce this first.
5814    #[inline]
5815    #[must_use]
5816    pub fn create_caption() -> Self {
5817        Self::create_node(NodeType::Caption)
5818    }
5819
5820    /// Creates a table header element.
5821    ///
5822    /// **Accessibility**: Groups header rows. Screen readers can navigate table structure.
5823    #[inline]
5824    #[must_use]
5825    pub fn create_thead() -> Self {
5826        Self::create_node(NodeType::THead)
5827    }
5828
5829    /// Creates a table body element.
5830    ///
5831    /// **Accessibility**: Groups body rows. Screen readers can navigate table structure.
5832    #[inline]
5833    #[must_use]
5834    pub fn create_tbody() -> Self {
5835        Self::create_node(NodeType::TBody)
5836    }
5837
5838    /// Creates a table footer element.
5839    ///
5840    /// **Accessibility**: Groups footer rows. Screen readers can navigate table structure.
5841    #[inline]
5842    #[must_use]
5843    pub fn create_tfoot() -> Self {
5844        Self::create_node(NodeType::TFoot)
5845    }
5846
5847    /// Creates a table row element.
5848    #[inline]
5849    #[must_use]
5850    pub fn create_tr() -> Self {
5851        Self::create_node(NodeType::Tr)
5852    }
5853
5854    /// Creates a table header cell element.
5855    ///
5856    /// **Accessibility**: Use `scope` attribute ("col" or "row") to associate headers with
5857    /// data cells. Screen readers use this to announce cell context.
5858    #[inline]
5859    #[must_use]
5860    pub fn create_th() -> Self {
5861        Self::create_node(NodeType::Th)
5862    }
5863
5864    /// Creates a table data cell element.
5865    #[inline]
5866    #[must_use]
5867    pub fn create_td() -> Self {
5868        Self::create_node(NodeType::Td)
5869    }
5870
5871    /// Creates a form element without accessibility information.
5872    ///
5873    /// Prefer [`Dom::create_form`] so that screen readers can announce the form's purpose.
5874    #[inline]
5875    #[must_use]
5876    pub fn create_form_no_a11y() -> Self {
5877        Self::create_node(NodeType::Form)
5878    }
5879
5880    /// Creates a form element with accessibility information.
5881    ///
5882    /// **Accessibility**: Group related form controls with `fieldset` and `legend`.
5883    /// Provide clear labels for all inputs. Consider `aria-describedby` for instructions.
5884    ///
5885    /// Use [`Dom::create_form_no_a11y`] only as a deliberate escape hatch.
5886    #[inline]
5887    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
5888    #[must_use]
5889    pub fn create_form(aria: SmallAriaInfo) -> Self {
5890        Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
5891    }
5892
5893    /// Creates a fieldset element for grouping form controls without accessibility info.
5894    ///
5895    /// Prefer [`Dom::create_fieldset`] so that screen readers can announce the group's purpose.
5896    #[inline]
5897    #[must_use]
5898    pub fn create_fieldset_no_a11y() -> Self {
5899        Self::create_node(NodeType::FieldSet)
5900    }
5901
5902    /// Creates a fieldset element with accessibility information.
5903    ///
5904    /// **Accessibility**: Groups related form controls. Always include a `legend` as the
5905    /// first child to describe the group. Screen readers announce the legend when entering
5906    /// the fieldset.
5907    ///
5908    /// Use [`Dom::create_fieldset_no_a11y`] only as a deliberate escape hatch.
5909    #[inline]
5910    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
5911    #[must_use]
5912    pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
5913        Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
5914    }
5915
5916    /// Creates a legend element without accessibility information.
5917    ///
5918    /// Prefer [`Dom::create_legend`] so that the legend's accessible name is explicit.
5919    #[inline]
5920    #[must_use]
5921    pub fn create_legend_no_a11y() -> Self {
5922        Self::create_node(NodeType::Legend)
5923    }
5924
5925    /// Creates a legend element with accessibility information.
5926    ///
5927    /// **Accessibility**: Describes the purpose of a fieldset. Must be the first child of
5928    /// a fieldset. Screen readers announce this when entering the fieldset.
5929    ///
5930    /// Use [`Dom::create_legend_no_a11y`] only as a deliberate escape hatch.
5931    #[inline]
5932    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
5933    #[must_use]
5934    pub fn create_legend(aria: SmallAriaInfo) -> Self {
5935        Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
5936    }
5937
5938    /// Creates a horizontal rule element.
5939    ///
5940    /// **Accessibility**: Represents a thematic break. Screen readers may announce this as
5941    /// a separator. Consider using CSS borders for purely decorative lines.
5942    #[inline]
5943    #[must_use]
5944    pub fn create_hr() -> Self {
5945        Self::create_node(NodeType::Hr)
5946    }
5947
5948    /// Creates THE canonical page-break element: a zero-size block with
5949    /// `break-before: page`, carrying the `__azul-native-pagebreak` class.
5950    ///
5951    /// This is the one break element the pagination estimator and a screen
5952    /// DOM treat identically (see `pagination_to_dom_breaks` in
5953    /// azul-layout): an application materializes an estimated break by
5954    /// inserting this node at the returned child-index path, and the layout
5955    /// of the surrounding content does not move - the element is an empty
5956    /// block with no margins, borders or padding, so sibling margins keep
5957    /// collapsing through it exactly as they did without it.
5958    ///
5959    /// The XML pipeline's `<pagebreak/>` builtin renders the equivalent
5960    /// element.
5961    #[must_use]
5962    pub fn create_page_break() -> Self {
5963        Self::create_node(NodeType::PageBreak)
5964    }
5965
5966    // Additional Element Constructors
5967
5968    /// Creates an address element.
5969    ///
5970    /// **Accessibility**: Represents contact information. Screen readers identify this
5971    /// as address content.
5972    #[inline]
5973    #[must_use]
5974    pub const fn create_address() -> Self {
5975        Self {
5976            root: NodeData::create_node(NodeType::Address),
5977            children: DomVec::from_const_slice(&[]),
5978            css: azul_css::css::CssVec::from_const_slice(&[]),
5979            estimated_total_children: 0,
5980        }
5981    }
5982
5983    /// Creates a definition list element.
5984    ///
5985    /// **Accessibility**: Screen readers announce definition lists and their structure.
5986    #[inline]
5987    #[must_use]
5988    pub const fn create_dl() -> Self {
5989        Self {
5990            root: NodeData::create_node(NodeType::Dl),
5991            children: DomVec::from_const_slice(&[]),
5992            css: azul_css::css::CssVec::from_const_slice(&[]),
5993            estimated_total_children: 0,
5994        }
5995    }
5996
5997    /// Creates a definition term element.
5998    ///
5999    /// **Accessibility**: Must be a child of `dl`. Represents the term being defined.
6000    #[inline]
6001    #[must_use]
6002    pub const fn create_dt() -> Self {
6003        Self {
6004            root: NodeData::create_node(NodeType::Dt),
6005            children: DomVec::from_const_slice(&[]),
6006            css: azul_css::css::CssVec::from_const_slice(&[]),
6007            estimated_total_children: 0,
6008        }
6009    }
6010
6011    /// Creates a definition description element.
6012    ///
6013    /// **Accessibility**: Must be a child of `dl`. Provides the definition for the term.
6014    #[inline]
6015    #[must_use]
6016    pub const fn create_dd() -> Self {
6017        Self {
6018            root: NodeData::create_node(NodeType::Dd),
6019            children: DomVec::from_const_slice(&[]),
6020            css: azul_css::css::CssVec::from_const_slice(&[]),
6021            estimated_total_children: 0,
6022        }
6023    }
6024
6025    /// Creates a table column group element.
6026    #[inline]
6027    #[must_use]
6028    pub const fn create_colgroup() -> Self {
6029        Self {
6030            root: NodeData::create_node(NodeType::ColGroup),
6031            children: DomVec::from_const_slice(&[]),
6032            css: azul_css::css::CssVec::from_const_slice(&[]),
6033            estimated_total_children: 0,
6034        }
6035    }
6036
6037    /// Creates a table column element.
6038    #[inline]
6039    #[must_use]
6040    pub fn create_col(span: i32) -> Self {
6041        Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
6042    }
6043
6044    /// Creates an optgroup element for grouping select options without accessibility info.
6045    ///
6046    /// Prefer [`Dom::create_optgroup`] so that screen readers can announce the group's purpose.
6047    ///
6048    /// **Parameters:**
6049    /// - `label`: Label for the option group
6050    #[inline]
6051    #[must_use]
6052    pub fn create_optgroup_no_a11y(label: AzString) -> Self {
6053        Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
6054    }
6055
6056    /// Creates an optgroup element for grouping select options with accessibility information.
6057    ///
6058    /// **Parameters:**
6059    /// - `label`: Label for the option group (visible)
6060    /// - `aria`: Additional accessibility information (description, etc.)
6061    ///
6062    /// Use [`Dom::create_optgroup_no_a11y`] only as a deliberate escape hatch.
6063    #[inline]
6064    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6065    #[must_use]
6066    pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
6067        Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
6068    }
6069
6070    /// Creates a quotation element.
6071    ///
6072    /// **Accessibility**: Represents an inline quotation.
6073    #[inline]
6074    #[must_use]
6075    pub const fn create_q() -> Self {
6076        Self {
6077            root: NodeData::create_node(NodeType::Q),
6078            children: DomVec::from_const_slice(&[]),
6079            css: azul_css::css::CssVec::from_const_slice(&[]),
6080            estimated_total_children: 0,
6081        }
6082    }
6083
6084    /// Creates an empty acronym element.
6085    ///
6086    /// **Note**: Deprecated in HTML5. Consider using `create_abbr()` instead.
6087    #[inline]
6088    #[must_use]
6089    pub const fn create_acronym() -> Self {
6090        Self {
6091            root: NodeData::create_node(NodeType::Acronym),
6092            children: DomVec::from_const_slice(&[]),
6093            css: azul_css::css::CssVec::from_const_slice(&[]),
6094            estimated_total_children: 0,
6095        }
6096    }
6097
6098    /// Creates an acronym element with text.
6099    ///
6100    /// **Note**: Deprecated in HTML5. Consider using `create_abbr_with_title()` instead.
6101    #[inline]
6102    pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
6103        Self::create_acronym().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6104            text,
6105        ))
6106    }
6107
6108    /// Creates a menu element without accessibility information.
6109    ///
6110    /// Prefer [`Dom::create_menu`] so that the menu's purpose is announced.
6111    #[inline]
6112    #[must_use]
6113    pub const fn create_menu_no_a11y() -> Self {
6114        Self {
6115            root: NodeData::create_node(NodeType::Menu),
6116            children: DomVec::from_const_slice(&[]),
6117            css: azul_css::css::CssVec::from_const_slice(&[]),
6118            estimated_total_children: 0,
6119        }
6120    }
6121
6122    /// Creates a menu element with accessibility information.
6123    ///
6124    /// **Accessibility**: Represents a list of commands. Similar to `<ul>` but semantic for
6125    /// toolbars/menus.
6126    ///
6127    /// Use [`Dom::create_menu_no_a11y`] only as a deliberate escape hatch.
6128    #[inline]
6129    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6130    #[must_use]
6131    pub fn create_menu(aria: SmallAriaInfo) -> Self {
6132        Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
6133    }
6134
6135    /// Creates an empty menu item element without accessibility information.
6136    ///
6137    /// Prefer [`Dom::create_menuitem`] so that the menu item's purpose is announced.
6138    #[inline]
6139    #[must_use]
6140    pub const fn create_menuitem_no_a11y() -> Self {
6141        Self {
6142            root: NodeData::create_node(NodeType::MenuItem),
6143            children: DomVec::from_const_slice(&[]),
6144            css: azul_css::css::CssVec::from_const_slice(&[]),
6145            estimated_total_children: 0,
6146        }
6147    }
6148
6149    /// Creates an empty menu item element with accessibility information.
6150    ///
6151    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
6152    /// attributes.
6153    ///
6154    /// Use [`Dom::create_menuitem_no_a11y`] only as a deliberate escape hatch.
6155    #[inline]
6156    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6157    #[must_use]
6158    pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
6159        Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
6160    }
6161
6162    /// Creates a menu item element with text but without accessibility information.
6163    ///
6164    /// Prefer [`Dom::create_menuitem_with_text`] so that screen readers get a
6165    /// distinct accessible name in addition to the visible text.
6166    #[inline]
6167    pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
6168        Self::create_menuitem_no_a11y().with_child(
6169            Self::create_text_do_not_use_without_block_level_wrapper(text),
6170        )
6171    }
6172
6173    /// Creates a menu item element with text and accessibility information.
6174    ///
6175    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
6176    /// attributes.
6177    ///
6178    /// Use [`Dom::create_menuitem_with_text_no_a11y`] only as a deliberate escape hatch.
6179    #[inline]
6180    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6181    pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
6182        Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
6183    }
6184
6185    /// Creates an output element without accessibility information.
6186    ///
6187    /// Prefer [`Dom::create_output`] so that screen readers can announce the
6188    /// computed value's purpose.
6189    #[inline]
6190    #[must_use]
6191    pub const fn create_output_no_a11y() -> Self {
6192        Self {
6193            root: NodeData::create_node(NodeType::Output),
6194            children: DomVec::from_const_slice(&[]),
6195            css: azul_css::css::CssVec::from_const_slice(&[]),
6196            estimated_total_children: 0,
6197        }
6198    }
6199
6200    /// Creates an output element with accessibility information.
6201    ///
6202    /// **Accessibility**: Represents the result of a calculation or user action.
6203    /// Use `for` attribute to associate with input elements. Screen readers announce updates.
6204    ///
6205    /// Use [`Dom::create_output_no_a11y`] only as a deliberate escape hatch.
6206    #[inline]
6207    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6208    #[must_use]
6209    pub fn create_output(aria: SmallAriaInfo) -> Self {
6210        Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
6211    }
6212
6213    /// Creates a progress indicator element without accessibility information.
6214    ///
6215    /// Prefer [`Dom::create_progress`] so that the task being measured is announced.
6216    ///
6217    /// **Parameters:**
6218    /// - `value`: Current progress value
6219    /// - `max`: Maximum value
6220    #[inline]
6221    #[must_use]
6222    pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
6223        Self::create_node(NodeType::Progress)
6224            .with_attribute(AttributeType::Custom(AttributeNameValue {
6225                attr_name: "value".into(),
6226                value: value.to_string().into(),
6227            }))
6228            .with_attribute(AttributeType::Custom(AttributeNameValue {
6229                attr_name: "max".into(),
6230                value: max.to_string().into(),
6231            }))
6232    }
6233
6234    /// Creates a progress indicator element with accessibility information.
6235    ///
6236    /// **Accessibility**: Represents task progress. Screen readers announce progress
6237    /// percentage. The `aria` value carries the label, current value, max, and an
6238    /// indeterminate flag for spinners with no known endpoint.
6239    ///
6240    /// Use [`Dom::create_progress_no_a11y`] only as a deliberate escape hatch.
6241    #[inline]
6242    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6243    #[must_use]
6244    pub fn create_progress(aria: ProgressAriaInfo) -> Self {
6245        let mut node = Self::create_node(NodeType::Progress);
6246        if !aria.indeterminate {
6247            if let azul_css::OptionF32::Some(v) = aria.current_value {
6248                node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
6249                    attr_name: "value".into(),
6250                    value: v.to_string().into(),
6251                }));
6252            }
6253        }
6254        if let azul_css::OptionF32::Some(m) = aria.max {
6255            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
6256                attr_name: "max".into(),
6257                value: m.to_string().into(),
6258            }));
6259        }
6260        node.with_accessibility_info(aria.to_full_info())
6261    }
6262
6263    /// Creates a meter gauge element without accessibility information.
6264    ///
6265    /// Prefer [`Dom::create_meter`] so that the measurement's purpose is announced.
6266    ///
6267    /// **Parameters:**
6268    /// - `value`: Current meter value
6269    /// - `min`: Minimum value
6270    /// - `max`: Maximum value
6271    #[inline]
6272    #[must_use]
6273    pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
6274        Self::create_node(NodeType::Meter)
6275            .with_attribute(AttributeType::Custom(AttributeNameValue {
6276                attr_name: "value".into(),
6277                value: value.to_string().into(),
6278            }))
6279            .with_attribute(AttributeType::Custom(AttributeNameValue {
6280                attr_name: "min".into(),
6281                value: min.to_string().into(),
6282            }))
6283            .with_attribute(AttributeType::Custom(AttributeNameValue {
6284                attr_name: "max".into(),
6285                value: max.to_string().into(),
6286            }))
6287    }
6288
6289    /// Creates a meter gauge element with accessibility information.
6290    ///
6291    /// **Accessibility**: Represents a scalar measurement within a known range.
6292    /// Screen readers announce the measurement. The `aria` value carries the
6293    /// label plus value/min/max/low/high/optimum metadata.
6294    ///
6295    /// Use [`Dom::create_meter_no_a11y`] only as a deliberate escape hatch.
6296    #[inline]
6297    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6298    #[must_use]
6299    pub fn create_meter(aria: MeterAriaInfo) -> Self {
6300        let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
6301        if let azul_css::OptionF32::Some(v) = aria.low {
6302            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
6303                attr_name: "low".into(),
6304                value: v.to_string().into(),
6305            }));
6306        }
6307        if let azul_css::OptionF32::Some(v) = aria.high {
6308            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
6309                attr_name: "high".into(),
6310                value: v.to_string().into(),
6311            }));
6312        }
6313        if let azul_css::OptionF32::Some(v) = aria.optimum {
6314            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
6315                attr_name: "optimum".into(),
6316                value: v.to_string().into(),
6317            }));
6318        }
6319        node.with_accessibility_info(aria.to_full_info())
6320    }
6321
6322    /// Creates a datalist element without accessibility information.
6323    ///
6324    /// Prefer [`Dom::create_datalist`] so that the suggestion list's purpose is announced.
6325    #[inline]
6326    #[must_use]
6327    pub const fn create_datalist_no_a11y() -> Self {
6328        Self {
6329            root: NodeData::create_node(NodeType::DataList),
6330            children: DomVec::from_const_slice(&[]),
6331            css: azul_css::css::CssVec::from_const_slice(&[]),
6332            estimated_total_children: 0,
6333        }
6334    }
6335
6336    /// Creates a datalist element with accessibility information.
6337    ///
6338    /// **Accessibility**: Provides autocomplete options for inputs.
6339    /// Associate with input using `list` attribute. Screen readers announce available options.
6340    ///
6341    /// Use [`Dom::create_datalist_no_a11y`] only as a deliberate escape hatch.
6342    #[inline]
6343    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6344    #[must_use]
6345    pub fn create_datalist(aria: SmallAriaInfo) -> Self {
6346        Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
6347    }
6348
6349    // Embedded Content Elements
6350
6351    /// Creates a canvas element for graphics without accessibility information.
6352    ///
6353    /// Prefer [`Dom::create_canvas`] so that the canvas's purpose is announced; canvas
6354    /// content is otherwise opaque to assistive technologies.
6355    #[inline]
6356    #[must_use]
6357    pub const fn create_canvas_no_a11y() -> Self {
6358        Self {
6359            root: NodeData::create_node(NodeType::Canvas),
6360            children: DomVec::from_const_slice(&[]),
6361            css: azul_css::css::CssVec::from_const_slice(&[]),
6362            estimated_total_children: 0,
6363        }
6364    }
6365
6366    /// Creates a canvas element for graphics with accessibility information.
6367    ///
6368    /// **Accessibility**: Canvas content is not accessible by default.
6369    /// Always provide fallback content as children and/or detailed aria-label.
6370    /// Consider using SVG for accessible graphics when possible.
6371    ///
6372    /// Use [`Dom::create_canvas_no_a11y`] only as a deliberate escape hatch.
6373    #[inline]
6374    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6375    #[must_use]
6376    pub fn create_canvas(aria: SmallAriaInfo) -> Self {
6377        Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
6378    }
6379
6380    /// Creates an object element for embedded content.
6381    ///
6382    /// **Accessibility**: Provide fallback content as children. Use aria-label to describe content.
6383    #[inline]
6384    #[must_use]
6385    pub const fn create_object() -> Self {
6386        Self {
6387            root: NodeData::create_node(NodeType::Object),
6388            children: DomVec::from_const_slice(&[]),
6389            css: azul_css::css::CssVec::from_const_slice(&[]),
6390            estimated_total_children: 0,
6391        }
6392    }
6393
6394    /// Creates a param element for object parameters.
6395    ///
6396    /// **Parameters:**
6397    /// - `name`: Parameter name
6398    /// - `value`: Parameter value
6399    #[inline]
6400    #[must_use]
6401    pub fn create_param(name: AzString, value: AzString) -> Self {
6402        Self::create_node(NodeType::Param)
6403            .with_attribute(AttributeType::Name(name))
6404            .with_attribute(AttributeType::Value(value))
6405    }
6406
6407    /// Creates an embed element.
6408    ///
6409    /// **Accessibility**: Provide alternative content or link. Use aria-label to describe embedded
6410    /// content.
6411    #[inline]
6412    #[must_use]
6413    pub const fn create_embed() -> Self {
6414        Self {
6415            root: NodeData::create_node(NodeType::Embed),
6416            children: DomVec::from_const_slice(&[]),
6417            css: azul_css::css::CssVec::from_const_slice(&[]),
6418            estimated_total_children: 0,
6419        }
6420    }
6421
6422    /// Creates an audio element without accessibility information.
6423    ///
6424    /// Prefer [`Dom::create_audio`] so that screen readers announce the audio's purpose.
6425    #[inline]
6426    #[must_use]
6427    pub const fn create_audio_no_a11y() -> Self {
6428        Self {
6429            root: NodeData::create_node(NodeType::Audio),
6430            children: DomVec::from_const_slice(&[]),
6431            css: azul_css::css::CssVec::from_const_slice(&[]),
6432            estimated_total_children: 0,
6433        }
6434    }
6435
6436    /// Creates an audio element with accessibility information.
6437    ///
6438    /// **Accessibility**: Always provide controls. Use `<track>` for captions/subtitles.
6439    /// Provide fallback text for unsupported browsers.
6440    ///
6441    /// Use [`Dom::create_audio_no_a11y`] only as a deliberate escape hatch.
6442    #[inline]
6443    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6444    #[must_use]
6445    pub fn create_audio(aria: SmallAriaInfo) -> Self {
6446        Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
6447    }
6448
6449    /// Creates a video element without accessibility information.
6450    ///
6451    /// Prefer [`Dom::create_video`] so that screen readers announce the video's purpose.
6452    #[inline]
6453    #[must_use]
6454    pub const fn create_video_no_a11y() -> Self {
6455        Self {
6456            root: NodeData::create_node(NodeType::Video),
6457            children: DomVec::from_const_slice(&[]),
6458            css: azul_css::css::CssVec::from_const_slice(&[]),
6459            estimated_total_children: 0,
6460        }
6461    }
6462
6463    /// Creates a video element with accessibility information.
6464    ///
6465    /// **Accessibility**: Always provide controls. Use `<track>` for
6466    /// captions/subtitles/descriptions. Provide fallback text. Consider providing transcript.
6467    ///
6468    /// Use [`Dom::create_video_no_a11y`] only as a deliberate escape hatch.
6469    #[inline]
6470    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6471    #[must_use]
6472    pub fn create_video(aria: SmallAriaInfo) -> Self {
6473        Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
6474    }
6475
6476    /// Creates a source element for media.
6477    ///
6478    /// **Parameters:**
6479    /// - `src`: Media source URL
6480    /// - `media_type`: MIME type (e.g., "video/mp4", "audio/ogg")
6481    #[inline]
6482    #[must_use]
6483    pub fn create_source(src: AzString, media_type: AzString) -> Self {
6484        Self::create_node(NodeType::Source)
6485            .with_attribute(AttributeType::Src(src))
6486            .with_attribute(AttributeType::Custom(AttributeNameValue {
6487                attr_name: "type".into(),
6488                value: media_type,
6489            }))
6490    }
6491
6492    /// Creates a track element for media captions/subtitles.
6493    ///
6494    /// **Accessibility**: Essential for deaf/hard-of-hearing users and non-native speakers.
6495    /// Use `kind` (subtitles/captions/descriptions), `srclang`, and `label` attributes.
6496    ///
6497    /// **Parameters:**
6498    /// - `src`: Track file URL (`WebVTT` format)
6499    /// - `kind`: Track kind ("subtitles", "captions", "descriptions", "chapters", "metadata")
6500    #[inline]
6501    #[must_use]
6502    pub fn create_track(src: AzString, kind: AzString) -> Self {
6503        Self::create_node(NodeType::Track)
6504            .with_attribute(AttributeType::Src(src))
6505            .with_attribute(AttributeType::Custom(AttributeNameValue {
6506                attr_name: "kind".into(),
6507                value: kind,
6508            }))
6509    }
6510
6511    /// Creates a map element for image maps.
6512    ///
6513    /// **Accessibility**: Provide text alternatives. Ensure all areas have alt text.
6514    #[inline]
6515    #[must_use]
6516    pub const fn create_map() -> Self {
6517        Self {
6518            root: NodeData::create_node(NodeType::Map),
6519            children: DomVec::from_const_slice(&[]),
6520            css: azul_css::css::CssVec::from_const_slice(&[]),
6521            estimated_total_children: 0,
6522        }
6523    }
6524
6525    /// Creates an area element for image map regions without accessibility information.
6526    ///
6527    /// Prefer [`Dom::create_area`] so that screen readers can announce the region's purpose.
6528    #[inline]
6529    #[must_use]
6530    pub const fn create_area_no_a11y() -> Self {
6531        Self {
6532            root: NodeData::create_node(NodeType::Area),
6533            children: DomVec::from_const_slice(&[]),
6534            css: azul_css::css::CssVec::from_const_slice(&[]),
6535            estimated_total_children: 0,
6536        }
6537    }
6538
6539    /// Creates an area element for image map regions with accessibility information.
6540    ///
6541    /// **Accessibility**: Always provide `alt` text describing the region/link purpose.
6542    /// Keyboard users should be able to navigate areas.
6543    ///
6544    /// Use [`Dom::create_area_no_a11y`] only as a deliberate escape hatch.
6545    #[inline]
6546    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6547    #[must_use]
6548    pub fn create_area(aria: SmallAriaInfo) -> Self {
6549        Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
6550    }
6551
6552    // Metadata Elements
6553
6554    /// Creates an empty title element for document title.
6555    ///
6556    /// **Accessibility**: Required for all pages. Screen readers announce this first.
6557    #[inline]
6558    #[must_use]
6559    pub fn create_title() -> Self {
6560        Self::create_node(NodeType::Title)
6561    }
6562
6563    /// Creates a title element for document title with text.
6564    ///
6565    /// **Accessibility**: Required for all pages. Screen readers announce this first.
6566    /// Should be unique and descriptive. Keep under 60 characters.
6567    #[inline]
6568    pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
6569        Self::create_title().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6570            text,
6571        ))
6572    }
6573
6574    /// Creates a meta element.
6575    ///
6576    /// **Accessibility**: Use for charset, viewport, description. Crucial for proper text display.
6577    #[inline]
6578    #[must_use]
6579    pub const fn create_meta() -> Self {
6580        Self {
6581            root: NodeData::create_node(NodeType::Meta),
6582            children: DomVec::from_const_slice(&[]),
6583            css: azul_css::css::CssVec::from_const_slice(&[]),
6584            estimated_total_children: 0,
6585        }
6586    }
6587
6588    /// Creates a link element for external resources.
6589    ///
6590    /// **Accessibility**: Use for stylesheets, icons, alternate versions.
6591    /// Provide meaningful `title` attribute for alternate stylesheets.
6592    #[inline]
6593    #[must_use]
6594    pub const fn create_link() -> Self {
6595        Self {
6596            root: NodeData::create_node(NodeType::Link),
6597            children: DomVec::from_const_slice(&[]),
6598            css: azul_css::css::CssVec::from_const_slice(&[]),
6599            estimated_total_children: 0,
6600        }
6601    }
6602
6603    /// Creates a script element.
6604    ///
6605    /// **Accessibility**: Ensure scripted content is accessible.
6606    /// Provide noscript fallbacks for critical functionality.
6607    #[inline]
6608    #[must_use]
6609    pub const fn create_script() -> Self {
6610        Self {
6611            root: NodeData::create_node(NodeType::Script),
6612            children: DomVec::from_const_slice(&[]),
6613            css: azul_css::css::CssVec::from_const_slice(&[]),
6614            estimated_total_children: 0,
6615        }
6616    }
6617
6618    /// Creates an empty style element for embedded CSS.
6619    ///
6620    /// **Note**: In Azul, use `.with_css()` instead for styling.
6621    /// This creates a `<style>` HTML element for embedded stylesheets.
6622    #[inline]
6623    #[must_use]
6624    pub const fn create_style() -> Self {
6625        Self {
6626            root: NodeData::create_node(NodeType::Style),
6627            children: DomVec::from_const_slice(&[]),
6628            css: azul_css::css::CssVec::from_const_slice(&[]),
6629            estimated_total_children: 0,
6630        }
6631    }
6632
6633    /// Creates a style element for embedded CSS with the given stylesheet text.
6634    ///
6635    /// **Note**: In Azul, use `.with_css()` instead for styling.
6636    /// This creates a `<style>` HTML element for embedded stylesheets.
6637    #[inline]
6638    pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
6639        Self::create_style().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6640            text,
6641        ))
6642    }
6643
6644    /// Creates a base element for document base URL.
6645    ///
6646    /// **Parameters:**
6647    /// - `href`: Base URL for relative URLs in the document
6648    #[inline]
6649    #[must_use]
6650    pub fn create_base(href: AzString) -> Self {
6651        Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
6652    }
6653
6654    // Advanced Constructors with Parameters
6655
6656    /// Creates a table header cell with scope.
6657    ///
6658    /// **Parameters:**
6659    /// - `scope`: "col", "row", "colgroup", or "rowgroup"
6660    /// - `text`: Header text
6661    ///
6662    /// **Accessibility**: The scope attribute is crucial for associating headers with data cells.
6663    #[inline]
6664    #[must_use]
6665    pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
6666        Self::create_node(NodeType::Th)
6667            .with_attribute(AttributeType::Scope(scope))
6668            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6669                text,
6670            ))
6671    }
6672
6673    /// Creates a table data cell with text.
6674    ///
6675    /// **Parameters:**
6676    /// - `text`: Cell content
6677    #[inline]
6678    pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
6679        Self::create_td().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6680            text,
6681        ))
6682    }
6683
6684    /// Creates a table header cell with text.
6685    ///
6686    /// **Parameters:**
6687    /// - `text`: Header text
6688    #[inline]
6689    pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
6690        Self::create_th().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6691            text,
6692        ))
6693    }
6694
6695    /// Creates a list item with text.
6696    ///
6697    /// **Parameters:**
6698    /// - `text`: List item content
6699    #[inline]
6700    pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
6701        Self::create_li().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6702            text,
6703        ))
6704    }
6705
6706    /// Creates a paragraph with text.
6707    ///
6708    /// **Parameters:**
6709    /// - `text`: Paragraph content
6710    #[inline]
6711    pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
6712        Self::create_p().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6713            text,
6714        ))
6715    }
6716
6717    /// Creates a generic block container (div) with text.
6718    ///
6719    /// The div is the box the text lives in: put your styling, callbacks and
6720    /// `tab_index` on the DIV, never on the text leaf (a text node has no box
6721    /// of its own - see
6722    /// [`Dom::create_text_do_not_use_without_block_level_wrapper`]).
6723    ///
6724    /// **Parameters:**
6725    /// - `text`: Div content
6726    #[inline]
6727    pub fn create_div_with_text<S: Into<AzString>>(text: S) -> Self {
6728        Self::create_div().with_child(Self::create_text_do_not_use_without_block_level_wrapper(
6729            text,
6730        ))
6731    }
6732
6733    // Accessibility-Aware Constructors
6734    // These constructors require explicit accessibility information.
6735    // Use the `*_no_a11y` variants only as a deliberate escape hatch.
6736
6737    /// Creates a button with text content and accessibility information.
6738    ///
6739    /// Use [`Dom::create_button_no_a11y`] to skip the accessibility information.
6740    ///
6741    /// **Parameters:**
6742    /// - `text`: The visible button text
6743    /// - `aria`: Accessibility information (role, description, etc.)
6744    #[inline]
6745    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6746    pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
6747        let mut btn = Self::create_button_no_a11y(text.into());
6748        btn.root.set_accessibility_info(aria.to_full_info());
6749        btn
6750    }
6751
6752    /// Creates a link (anchor) with href, text, and accessibility information.
6753    ///
6754    /// Use [`Dom::create_a_no_a11y`] to skip the accessibility information (e.g. for
6755    /// image-only links whose accessible name comes from an `<img alt>`).
6756    ///
6757    /// **Parameters:**
6758    /// - `href`: The link destination
6759    /// - `text`: The visible link text
6760    /// - `aria`: Accessibility information (expanded description, etc.)
6761    #[inline]
6762    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6763    pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
6764        href: S1,
6765        text: S2,
6766        aria: SmallAriaInfo,
6767    ) -> Self {
6768        let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
6769        link.root.set_accessibility_info(aria.to_full_info());
6770        link
6771    }
6772
6773    /// Creates an input element with type, name, and accessibility information.
6774    ///
6775    /// Use [`Dom::create_input_no_a11y`] to skip the accessibility information.
6776    ///
6777    /// **Parameters:**
6778    /// - `input_type`: The input type (text, password, email, etc.)
6779    /// - `name`: The form field name
6780    /// - `label`: Base accessibility label
6781    /// - `aria`: Additional accessibility information (description, etc.)
6782    #[inline]
6783    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6784    pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
6785        input_type: S1,
6786        name: S2,
6787        label: S3,
6788        aria: SmallAriaInfo,
6789    ) -> Self {
6790        let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
6791        input.root.set_accessibility_info(aria.to_full_info());
6792        input
6793    }
6794
6795    /// Creates a textarea with name and accessibility information.
6796    ///
6797    /// Use [`Dom::create_textarea_no_a11y`] to skip the accessibility information.
6798    ///
6799    /// **Parameters:**
6800    /// - `name`: The form field name
6801    /// - `label`: Base accessibility label
6802    /// - `aria`: Additional accessibility information (description, etc.)
6803    #[inline]
6804    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6805    pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
6806        name: S1,
6807        label: S2,
6808        aria: SmallAriaInfo,
6809    ) -> Self {
6810        let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
6811        textarea.root.set_accessibility_info(aria.to_full_info());
6812        textarea
6813    }
6814
6815    /// Creates a select dropdown with name and accessibility information.
6816    ///
6817    /// Use [`Dom::create_select_no_a11y`] to skip the accessibility information.
6818    ///
6819    /// **Parameters:**
6820    /// - `name`: The form field name
6821    /// - `label`: Base accessibility label
6822    /// - `aria`: Additional accessibility information (description, etc.)
6823    #[inline]
6824    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6825    pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
6826        name: S1,
6827        label: S2,
6828        aria: SmallAriaInfo,
6829    ) -> Self {
6830        let mut select = Self::create_select_no_a11y(name.into(), label.into());
6831        select.root.set_accessibility_info(aria.to_full_info());
6832        select
6833    }
6834
6835    /// Creates a table with caption and accessibility information.
6836    ///
6837    /// Use [`Dom::create_table_no_a11y`] to skip the caption and accessibility
6838    /// information.
6839    ///
6840    /// **Parameters:**
6841    /// - `caption`: Table caption (visible title)
6842    /// - `aria`: Accessibility information describing table purpose
6843    #[inline]
6844    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6845    pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
6846        let mut table = Self::create_table_no_a11y().with_child(Self::create_caption().with_child(
6847            Self::create_text_do_not_use_without_block_level_wrapper(caption),
6848        ));
6849        table.root.set_accessibility_info(aria.to_full_info());
6850        table
6851    }
6852
6853    /// Creates a label for a form control with additional accessibility information.
6854    ///
6855    /// Use [`Dom::create_label_no_a11y`] to skip the accessibility information.
6856    ///
6857    /// **Parameters:**
6858    /// - `for_id`: The ID of the associated form control
6859    /// - `text`: The visible label text
6860    /// - `aria`: Additional accessibility information (description, etc.)
6861    #[inline]
6862    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
6863    pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
6864        for_id: S1,
6865        text: S2,
6866        aria: SmallAriaInfo,
6867    ) -> Self {
6868        let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
6869        label.root.set_accessibility_info(aria.to_full_info());
6870        label
6871    }
6872
6873    /// Parse XML/XHTML string into a DOM
6874    ///
6875    /// This is a simple wrapper that parses XML and converts it to a DOM.
6876    /// For now, it just creates a text node with the content since full XML parsing
6877    /// requires the xml feature and more complex parsing logic.
6878    #[cfg(feature = "xml")]
6879    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
6880        // TODO: Implement full XML parsing
6881        // For now, just create a text node showing that XML was loaded
6882        Self::create_text_do_not_use_without_block_level_wrapper(format!(
6883            "XML content loaded ({} bytes)",
6884            xml_str.as_ref().len()
6885        ))
6886    }
6887
6888    /// Parse XML/XHTML string into a DOM (fallback without xml feature)
6889    #[cfg(not(feature = "xml"))]
6890    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
6891        Self::create_text_do_not_use_without_block_level_wrapper(format!(
6892            "XML parsing requires 'xml' feature ({} bytes)",
6893            xml_str.as_ref().len()
6894        ))
6895    }
6896
6897    // Swaps `self` with a default DOM, necessary for builder methods
6898    #[inline]
6899    #[must_use]
6900    pub const fn swap_with_default(&mut self) -> Self {
6901        let mut s = Self {
6902            root: NodeData::create_div(),
6903            children: DomVec::from_const_slice(&[]),
6904            css: azul_css::css::CssVec::from_const_slice(&[]),
6905            estimated_total_children: 0,
6906        };
6907        mem::swap(&mut s, self);
6908        s
6909    }
6910
6911    /// AUDIT: recompute the authoritative descendant count (1 per descendant)
6912    /// from `children`, without mutating anything. Used by the debug-only
6913    /// consistency assertions in the builder methods so a stale
6914    /// `estimated_total_children` (from direct `children` mutation) is caught in
6915    /// tests before it can under-allocate the arena in
6916    /// `convert_dom_into_compact_dom`.
6917    #[must_use]
6918    pub fn recompute_estimated_total_children(&self) -> usize {
6919        self.children
6920            .iter()
6921            .map(|c| c.recompute_estimated_total_children() + 1)
6922            .sum()
6923    }
6924
6925    #[inline]
6926    pub fn add_child(&mut self, child: Self) {
6927        // AUDIT: cheap ONE-LEVEL consistency check (O(child.children), not the
6928        // full O(subtree) recompute — `add_child` is a per-child hot path, so a
6929        // recursive assert would make debug DOM construction O(n^2)). Assuming
6930        // grandchildren are already consistent (they are when the tree is built
6931        // bottom-up), this catches a direct mutation of `child.children` that
6932        // skipped `fixup_children_estimated()`.
6933        debug_assert_eq!(
6934            child.estimated_total_children,
6935            child
6936                .children
6937                .iter()
6938                .map(|c| c.estimated_total_children + 1)
6939                .sum::<usize>(),
6940            "Dom.estimated_total_children desynced for added child; call \
6941             fixup_children_estimated() after mutating `children` directly",
6942        );
6943        let estimated = child.estimated_total_children;
6944        let mut v: DomVec = Vec::new().into();
6945        mem::swap(&mut v, &mut self.children);
6946        let mut v = v.into_library_owned_vec();
6947        v.push(child);
6948        self.children = v.into();
6949        self.estimated_total_children += estimated + 1;
6950    }
6951
6952    #[inline]
6953    pub fn set_children(&mut self, children: DomVec) {
6954        // AUDIT: one-level check per child (see `add_child`) — verifies each
6955        // child's own cached estimate is internally consistent before we trust
6956        // it, without an O(subtree) recompute.
6957        debug_assert!(
6958            children.iter().all(|c| c.estimated_total_children
6959                == c.children
6960                    .iter()
6961                    .map(|g| g.estimated_total_children + 1)
6962                    .sum::<usize>()),
6963            "Dom.estimated_total_children desynced in set_children; a child's own \
6964             estimate was stale — call fixup_children_estimated() first",
6965        );
6966        let children_estimated = children
6967            .iter()
6968            .map(|s| s.estimated_total_children + 1)
6969            .sum();
6970        self.children = children;
6971        self.estimated_total_children = children_estimated;
6972    }
6973
6974    #[must_use]
6975    pub fn copy_except_for_root(&mut self) -> Self {
6976        Self {
6977            root: self.root.copy_special(),
6978            children: self.children.clone(),
6979            css: self.css.clone(),
6980            estimated_total_children: self.estimated_total_children,
6981        }
6982    }
6983    #[must_use]
6984    pub const fn node_count(&self) -> usize {
6985        // `saturating_add`, not `+`. `estimated_total_children` is a PUBLIC
6986        // field, so a caller can put `usize::MAX` in it. With `+` that is a
6987        // panic in debug and a silent wrap to **0** in release — and 0 is the
6988        // worst available answer, because it reads as "this DOM is empty" and
6989        // every caller believes it. Saturating gives the same result for every
6990        // sane value and a defined, obviously-wrong-way-up one otherwise.
6991        self.estimated_total_children.saturating_add(1)
6992    }
6993
6994    /// Push a parsed `Css` onto this Dom subtree's `.css` list (the
6995    /// `@scope`-like mechanism that `with_css(&str)` also feeds — a string
6996    /// parses to a `Css` and lands here). The cascade selector-matches every
6997    /// entry against the subtree; later pushes win at equal specificity.
6998    /// This is the low-level Css-struct entry point; prefer `with_css(&str)`.
6999    pub fn add_component_css(&mut self, css: azul_css::css::Css) {
7000        let mut v = Vec::new().into();
7001        mem::swap(&mut v, &mut self.css);
7002        let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
7003        v.push(css);
7004        self.css = v.into();
7005    }
7006
7007    /// Builder form of [`Self::add_component_css`]: attach a parsed
7008    /// stylesheet and hand the `Dom` back, so a component builds in one
7009    /// expression. `with_css(&str)` is the same thing for a string that still
7010    /// has to be parsed.
7011    #[must_use]
7012    pub fn with_component_css(mut self, css: azul_css::css::Css) -> Self {
7013        self.add_component_css(css);
7014        self
7015    }
7016
7017    /// Replace the subtree's entire component-level CSS list with the
7018    /// provided one. Use `add_component_css` / `with_component_css` for
7019    /// stacking; this is the wholesale-replace form.
7020    pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
7021        self.css = css;
7022    }
7023    #[inline]
7024    #[must_use]
7025    pub fn with_children(mut self, children: DomVec) -> Self {
7026        self.set_children(children);
7027        self
7028    }
7029    #[inline]
7030    #[must_use]
7031    pub fn with_child(mut self, child: Self) -> Self {
7032        self.add_child(child);
7033        self
7034    }
7035    #[inline]
7036    #[must_use]
7037    pub fn with_node_type(mut self, node_type: NodeType) -> Self {
7038        self.root.set_node_type(node_type);
7039        self
7040    }
7041    #[inline]
7042    #[must_use]
7043    pub fn with_id(mut self, id: AzString) -> Self {
7044        self.root.add_id(id);
7045        self
7046    }
7047    #[inline]
7048    #[must_use]
7049    pub fn with_class(mut self, class: AzString) -> Self {
7050        self.root.add_class(class);
7051        self
7052    }
7053    #[inline]
7054    #[must_use]
7055    pub fn with_callback<C: Into<CoreCallback>>(
7056        mut self,
7057        event: EventFilter,
7058        data: RefAny,
7059        callback: C,
7060    ) -> Self {
7061        self.root.add_callback(event, data, callback);
7062        self
7063    }
7064    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
7065    #[inline]
7066    #[must_use]
7067    pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
7068        self.root.add_css_property(prop);
7069        self
7070    }
7071    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
7072    #[inline]
7073    pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
7074        self.root.add_css_property(prop);
7075    }
7076    #[inline]
7077    pub fn add_class(&mut self, class: AzString) {
7078        self.root.add_class(class);
7079    }
7080    #[inline]
7081    pub fn add_callback<C: Into<CoreCallback>>(
7082        &mut self,
7083        event: EventFilter,
7084        data: RefAny,
7085        callback: C,
7086    ) {
7087        self.root.add_callback(event, data, callback);
7088    }
7089    #[inline]
7090    pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
7091        self.root.set_tab_index(tab_index);
7092    }
7093    #[inline]
7094    pub const fn set_contenteditable(&mut self, contenteditable: bool) {
7095        self.root.set_contenteditable(contenteditable);
7096    }
7097    #[inline]
7098    #[must_use]
7099    pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
7100        self.root.set_tab_index(tab_index);
7101        self
7102    }
7103    #[inline]
7104    #[must_use]
7105    pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
7106        self.root.set_contenteditable(contenteditable);
7107        self
7108    }
7109    #[inline]
7110    #[must_use]
7111    pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
7112        self.root.set_dataset(data);
7113        self
7114    }
7115    /// Setter form of [`Self::with_dataset`].
7116    #[inline]
7117    pub fn set_dataset(&mut self, data: OptionRefAny) {
7118        self.root.set_dataset(data);
7119    }
7120    /// Stamp an app-chosen marker string on the root node, resolvable to a
7121    /// `(DomId, NodeId)` via `CallbackInfo::get_node_id_by_marker` — the
7122    /// clean way for one widget's callback to find another widget without
7123    /// knowing its internals. Invisible to CSS matching, unlike an id.
7124    #[inline]
7125    #[must_use]
7126    pub fn with_marker(mut self, marker: azul_css::OptionString) -> Self {
7127        self.root.set_marker(marker);
7128        self
7129    }
7130    /// Setter form of [`Self::with_marker`].
7131    #[inline]
7132    pub fn set_marker(&mut self, marker: azul_css::OptionString) {
7133        self.root.set_marker(marker);
7134    }
7135    /// Setter form of [`Self::with_key`].
7136    #[inline]
7137    pub fn set_key<K: Hash>(&mut self, key: K) {
7138        self.root.set_key(key);
7139    }
7140    /// Setter form of [`Self::with_merge_callback`].
7141    #[inline]
7142    pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
7143        self.root.set_merge_callback(callback);
7144    }
7145    /// Setter form of [`Self::with_svg_data`].
7146    #[inline]
7147    pub fn set_svg_data(&mut self, data: SvgNodeData) {
7148        self.root.set_svg_data(data);
7149    }
7150    /// Setter form of [`Self::with_attributes`].
7151    #[inline]
7152    pub fn set_attributes(&mut self, attributes: AttributeTypeVec) {
7153        self.root.set_attributes(attributes);
7154    }
7155
7156    /// Attach a presence-animation function to this node — see
7157    /// [`NodeData::add_animation_callback`].
7158    #[must_use]
7159    pub fn with_animation_callback(
7160        mut self,
7161        name: AzString,
7162        callback: crate::resources::ZombieAnimCallback,
7163        data: RefAny,
7164    ) -> Self {
7165        self.root.add_animation_callback(name, callback, data);
7166        self
7167    }
7168    #[inline]
7169    #[must_use]
7170    pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
7171        self.root.set_ids_and_classes(ids_and_classes);
7172        self
7173    }
7174
7175    /// Adds an attribute to this DOM element.
7176    #[inline]
7177    #[must_use]
7178    pub fn with_attribute(mut self, attr: AttributeType) -> Self {
7179        let mut attrs = self.root.attributes().clone();
7180        let mut v = attrs.into_library_owned_vec();
7181        v.push(attr);
7182        self.root.set_attributes(v.into());
7183        self
7184    }
7185
7186    /// Adds multiple attributes to this DOM element.
7187    #[inline]
7188    #[must_use]
7189    pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
7190        self.root.set_attributes(attributes);
7191        self
7192    }
7193
7194    #[inline]
7195    #[must_use]
7196    pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
7197        self.root.callbacks = callbacks;
7198        self
7199    }
7200    /// Legacy: builder-form for the flat property+conditions list. Each entry
7201    /// becomes a single-declaration rule at `rule_priority::INLINE`.
7202    #[inline]
7203    #[must_use]
7204    pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
7205        self.root.style = css_props.into();
7206        self
7207    }
7208    /// Builder-form for setting the inline `Css` directly.
7209    #[inline]
7210    #[must_use]
7211    pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
7212        self.root.style = style;
7213        self
7214    }
7215
7216    /// Assigns a stable key to the root node of this DOM for reconciliation.
7217    ///
7218    /// This is crucial for performance and correct state preservation when
7219    /// lists of items change order or items are inserted/removed.
7220    ///
7221    /// # Example
7222    /// ```rust
7223    /// # use azul_core::dom::Dom;
7224    /// Dom::create_div()
7225    ///     .with_key("user-avatar-123");
7226    /// ```
7227    #[inline]
7228    #[must_use]
7229    pub fn with_key<K: Hash>(mut self, key: K) -> Self {
7230        self.root.set_key(key);
7231        self
7232    }
7233
7234    /// Registers a callback to merge dataset state from the previous frame.
7235    ///
7236    /// This is used for components that maintain heavy internal state (video players,
7237    /// WebGL contexts, network connections) that should not be destroyed and recreated
7238    /// on every render frame.
7239    ///
7240    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
7241    /// returns the `RefAny` that should be used for the new node.
7242    #[inline]
7243    #[must_use]
7244    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
7245        self.root.set_merge_callback(callback);
7246        self
7247    }
7248
7249    /// Parse and set CSS styles with full selector support.
7250    ///
7251    /// This is the unified API for setting inline CSS on a DOM node. It supports:
7252    /// - Simple properties: `color: red; font-size: 14px;`
7253    /// - Pseudo-selectors: `:hover { background: blue; }`
7254    /// - @-rules: `@os linux { font-size: 14px; }`
7255    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
7256    ///
7257    /// # Examples
7258    /// ```rust
7259    /// # use azul_core::dom::Dom;
7260    /// // Simple inline styles
7261    /// Dom::create_div().with_css("color: red; font-size: 14px;");
7262    ///
7263    /// // With hover and active states
7264    /// Dom::create_div().with_css("
7265    ///     color: blue;
7266    ///     :hover { color: red; }
7267    ///     :active { color: green; }
7268    /// ");
7269    ///
7270    /// // OS-specific with nested hover
7271    /// Dom::create_div().with_css("
7272    ///     font-size: 12px;
7273    ///     @os linux { font-size: 14px; :hover { color: red; }}
7274    ///     @os windows { font-size: 13px; }
7275    /// ");
7276    /// ```
7277    pub fn set_css(&mut self, style: &str) {
7278        // Unified, `@scope`-like model: a CSS string parses into a `Css` struct that is
7279        // pushed onto THIS Dom subtree's `.css` vec, where the cascade selector-matches
7280        // it against the subtree (`collect_css_from_dom` → `CssPropertyCache::restyle`).
7281        // `with_css` is the single CSS entry point — there is no separate node-only inline
7282        // path, and the old `with_component_css` is folded into this. A bare-declaration
7283        // string (`color: red`) parses to `* { color: red }` and so applies to the whole
7284        // subtree, exactly like attaching a `@scope { :scope { ... } }` block.
7285        self.add_component_css(azul_css::css::Css::parse_inline(style));
7286    }
7287
7288    /// Builder method for `set_css`
7289    #[must_use]
7290    pub fn with_css(mut self, style: &str) -> Self {
7291        self.set_css(style);
7292        self
7293    }
7294
7295    /// Sets the context menu for the root node
7296    #[inline]
7297    pub fn set_context_menu(&mut self, context_menu: Menu) {
7298        self.root.set_context_menu(context_menu);
7299    }
7300
7301    #[inline]
7302    #[must_use]
7303    pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
7304        self.set_context_menu(context_menu);
7305        self
7306    }
7307
7308    /// Sets the menu bar for the root node
7309    #[inline]
7310    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
7311        self.root.set_menu_bar(menu_bar);
7312    }
7313
7314    #[inline]
7315    #[must_use]
7316    pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
7317        self.set_menu_bar(menu_bar);
7318        self
7319    }
7320
7321    #[inline]
7322    #[must_use]
7323    pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
7324        self.root.set_clip_mask(clip_mask);
7325        self
7326    }
7327
7328    #[inline]
7329    #[must_use]
7330    pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
7331        self.root.set_svg_data(SvgNodeData::Path(clip));
7332        self
7333    }
7334
7335    #[inline]
7336    #[must_use]
7337    pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
7338        self.root.set_svg_data(data);
7339        self
7340    }
7341
7342    #[inline]
7343    /// Give this node an accessible NAME, keeping whatever else it already
7344    /// declares.
7345    ///
7346    /// This is the override an application needs and `with_accessibility_info`
7347    /// cannot give it. A widget fills in what it knows — a slider's role and
7348    /// live value, a checkbox's checked state — and it CANNOT know what the
7349    /// control is called: only the app does. Replacing the whole struct to add
7350    /// a name would discard the role and the value with it, so the control
7351    /// would gain a name and stop reporting its position.
7352    ///
7353    /// ```ignore
7354    /// Slider::new(volume).dom().with_accessibility_name("Volume")
7355    /// ```
7356    #[must_use]
7357    pub fn with_accessibility_name<S: Into<AzString>>(self, name: S) -> Self {
7358        self.with_accessibility_assign(AccessibilityInfo {
7359            accessibility_name: Some(name.into()).into(),
7360            ..AccessibilityInfo::default()
7361        })
7362    }
7363
7364    /// Overlay a PARTIAL accessibility declaration, keeping every field the
7365    /// patch does not set.
7366    ///
7367    /// The general form of the two helpers around it, and the one to reach for
7368    /// when setting more than one thing at once:
7369    ///
7370    /// ```ignore
7371    /// slider.dom().with_accessibility_assign(&AccessibilityInfo {
7372    ///     accessibility_name: Some("Volume".into()).into(),
7373    ///     description: Some("0 to 100".into()).into(),
7374    ///     ..Default::default()
7375    /// })
7376    /// // the widget's role, live value and states all survive
7377    /// ```
7378    ///
7379    /// See [`AccessibilityInfo::assign`] for exactly which fields count as
7380    /// "set" — `None`, an empty vec and `Unknown` all mean "not specified".
7381    #[must_use]
7382    pub fn with_accessibility_assign(mut self, patch: AccessibilityInfo) -> Self {
7383        let info = self
7384            .root
7385            .accessibility
7386            .as_ref()
7387            .map_or_else(AccessibilityInfo::default, |b| (**b).clone());
7388        self.root.set_accessibility_info(info.assigned(patch));
7389        self
7390    }
7391
7392    /// Point this node at the node that already NAMES it, keeping the rest of
7393    /// its declaration.
7394    ///
7395    /// Preferred over copying the label text: a duplicated name drifts the
7396    /// moment someone edits the visible label and forgets the spoken one.
7397    #[must_use]
7398    pub fn with_accessibility_labelled_by(self, label: DomNodeId) -> Self {
7399        self.with_accessibility_assign(AccessibilityInfo {
7400            labelled_by: Some(label).into(),
7401            ..AccessibilityInfo::default()
7402        })
7403    }
7404
7405    #[must_use]
7406    pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
7407        self.root.set_accessibility_info(accessibility_info);
7408        self
7409    }
7410
7411    pub fn fixup_children_estimated(&mut self) -> usize {
7412        if self.children.is_empty() {
7413            self.estimated_total_children = 0;
7414        } else {
7415            self.estimated_total_children = self
7416                .children
7417                .iter_mut()
7418                .map(|s| s.fixup_children_estimated() + 1)
7419                .sum();
7420        }
7421        self.estimated_total_children
7422    }
7423}
7424
7425impl core::iter::FromIterator<Self> for Dom {
7426    fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
7427        let mut estimated_total_children = 0;
7428        let children = iter
7429            .into_iter()
7430            .inspect(|c| {
7431                estimated_total_children += c.estimated_total_children + 1;
7432            })
7433            .collect::<Vec<Self>>();
7434
7435        Self {
7436            root: NodeData::create_div(),
7437            children: children.into(),
7438            css: azul_css::css::CssVec::from_const_slice(&[]),
7439            estimated_total_children,
7440        }
7441    }
7442}
7443
7444impl fmt::Debug for Dom {
7445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7446        fn print_dom(d: &Dom, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7447            write!(f, "Dom {{\r\n")?;
7448            write!(f, "\troot: {:#?}\r\n", d.root)?;
7449            write!(
7450                f,
7451                "\testimated_total_children: {:#?}\r\n",
7452                d.estimated_total_children
7453            )?;
7454            write!(f, "\tchildren: [\r\n")?;
7455            for c in &d.children {
7456                print_dom(c, f)?;
7457            }
7458            write!(f, "\t]\r\n")?;
7459            write!(f, "}}\r\n")?;
7460            Ok(())
7461        }
7462
7463        print_dom(self, f)
7464    }
7465}
7466
7467#[cfg(test)]
7468#[path = "dom_test.rs"]
7469mod dom_test;