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"))]
8
use alloc::string::ToString;
9
use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
10
use core::{
11
    fmt,
12
    hash::{Hash, Hasher},
13
    iter::FromIterator,
14
    mem,
15
    sync::atomic::{AtomicUsize, Ordering},
16
};
17

            
18
use azul_css::{
19
    css::{BoxOrStatic, Css, NodeTypeTag},
20
    codegen::format::GetHash,
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
30
pub use crate::a11y::*;
31
pub use crate::events::{
32
    ApplicationEventFilter, ComponentEventFilter, EventFilter, FocusEventFilter, HoverEventFilter,
33
    WindowEventFilter,
34
};
35
pub use crate::id::{Node, NodeHierarchy, NodeId};
36
use 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
};
55
pub use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
56

            
57
static 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)]
62
pub 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

            
111
impl InputType {
112
    /// Returns the HTML attribute value for this input type
113
25
    #[must_use] pub const fn as_str(&self) -> &'static str {
114
25
        match self {
115
2
            Self::Text => "text",
116
1
            Self::Button => "button",
117
1
            Self::Checkbox => "checkbox",
118
1
            Self::Color => "color",
119
1
            Self::Date => "date",
120
1
            Self::Datetime => "datetime",
121
2
            Self::DatetimeLocal => "datetime-local",
122
1
            Self::Email => "email",
123
1
            Self::File => "file",
124
1
            Self::Hidden => "hidden",
125
1
            Self::Image => "image",
126
1
            Self::Month => "month",
127
1
            Self::Number => "number",
128
1
            Self::Password => "password",
129
1
            Self::Radio => "radio",
130
1
            Self::Range => "range",
131
1
            Self::Reset => "reset",
132
1
            Self::Search => "search",
133
1
            Self::Submit => "submit",
134
1
            Self::Tel => "tel",
135
1
            Self::Time => "time",
136
1
            Self::Url => "url",
137
1
            Self::Week => "week",
138
        }
139
25
    }
140
}
141

            
142
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
143
#[repr(C)]
144
pub struct TagId {
145
    pub inner: u64,
146
}
147

            
148
impl ::core::fmt::Display for TagId {
149
3
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150
3
        f.debug_struct("TagId").field("inner", &self.inner).finish()
151
3
    }
152
}
153

            
154
impl_option!(
155
    TagId,
156
    OptionTagId,
157
    [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
158
);
159

            
160
impl TagId {
161
8
    #[must_use] pub const fn into_crate_internal(&self) -> Self {
162
8
        Self { inner: self.inner }
163
8
    }
164
642610
    #[must_use] pub const fn from_crate_internal(t: Self) -> Self {
165
642610
        t
166
642610
    }
167

            
168
    /// Creates a new, unique hit-testing tag ID.
169
    /// Wraps around to 1 on overflow (0 is reserved for "no tag").
170
    ///
171
    /// AUDIT: the wrap is only reachable after 2^64 - 1 allocations (a process
172
    /// running long enough to exhaust the counter is not realistic), but note
173
    /// that on wrap the freshly-issued id could theoretically collide with a
174
    /// still-live tag from very early in the process. This is left as a
175
    /// documented, non-triggerable limitation rather than adding a live-tag
176
    /// registry to detect collisions on every allocation. AUDIT-TODO: revisit
177
    /// if `TagId` is ever narrowed below 64 bits.
178
514
    pub fn unique() -> Self {
179
        loop {
180
514
            let current = TAG_ID.load(Ordering::SeqCst);
181
514
            let next = if current == usize::MAX { 1 } else { current + 1 };
182
514
            if TAG_ID.compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
183
514
                return Self { inner: current as u64 };
184
            }
185
        }
186
514
    }
187
}
188

            
189
/// Same as the `TagId`, but only for scrollable nodes.
190
/// This provides a typed distinction for tags associated with scrolling containers.
191
#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
192
#[repr(C)]
193
pub struct ScrollTagId {
194
    pub inner: TagId,
195
}
196

            
197
impl ::core::fmt::Display for ScrollTagId {
198
3
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199
3
        f.debug_struct("ScrollTagId")
200
3
            .field("inner", &self.inner)
201
3
            .finish()
202
3
    }
203
}
204

            
205
impl ::core::fmt::Debug for ScrollTagId {
206
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207
1
        write!(f, "{self}")
208
1
    }
209
}
210

            
211
impl ScrollTagId {
212
    /// Creates a new, unique scroll tag ID. Note that this should not
213
    /// be used for identifying nodes, use the `DomNodeHash` instead.
214
2
    #[must_use] pub fn unique() -> Self {
215
2
        Self {
216
2
            inner: TagId::unique(),
217
2
        }
218
2
    }
219
}
220

            
221
/// Orientation of a scrollbar.
222
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
223
#[repr(C)]
224
pub enum ScrollbarOrientation {
225
    Horizontal,
226
    Vertical,
227
}
228

            
229
/// Calculated hash of a DOM node, used for identifying identical DOM
230
/// nodes across frames for efficient diffing and state preservation.
231
#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
232
#[repr(C)]
233
pub struct DomNodeHash {
234
    pub inner: u64,
235
}
236

            
237
impl ::core::fmt::Debug for DomNodeHash {
238
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239
        write!(f, "DomNodeHash({})", self.inner)
240
    }
241
}
242

            
243
/// List of core DOM node types built into `azul`.
244
/// This enum defines the building blocks of the UI, similar to HTML tags.
245
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
246
#[repr(C, u8)]
247
pub enum NodeType {
248
    // Root and container elements
249
    /// Root HTML element.
250
    Html,
251
    /// Document head (metadata container).
252
    Head,
253
    /// Root element of the document body.
254
    Body,
255
    /// Generic block-level container.
256
    Div,
257
    /// Paragraph.
258
    P,
259
    /// Article content.
260
    Article,
261
    /// Section of a document.
262
    Section,
263
    /// Navigation links.
264
    Nav,
265
    /// Sidebar/tangential content.
266
    Aside,
267
    /// Header section.
268
    Header,
269
    /// Footer section.
270
    Footer,
271
    /// Main content.
272
    Main,
273
    /// Figure with optional caption.
274
    Figure,
275
    /// Caption for figure element.
276
    FigCaption,
277
    /// Headings.
278
    H1,
279
    H2,
280
    H3,
281
    H4,
282
    H5,
283
    H6,
284
    /// Line break.
285
    Br,
286
    /// Horizontal rule.
287
    Hr,
288
    /// Preformatted text.
289
    Pre,
290
    /// Block quote.
291
    BlockQuote,
292
    /// Address.
293
    Address,
294
    /// Details disclosure widget.
295
    Details,
296
    /// Summary for details element.
297
    Summary,
298
    /// Dialog box or window.
299
    Dialog,
300

            
301
    // List elements
302
    /// Unordered list.
303
    Ul,
304
    /// Ordered list.
305
    Ol,
306
    /// List item.
307
    Li,
308
    /// Definition list.
309
    Dl,
310
    /// Definition term.
311
    Dt,
312
    /// Definition description.
313
    Dd,
314
    /// Menu list.
315
    Menu,
316
    /// Menu item.
317
    MenuItem,
318
    /// Directory list (deprecated).
319
    Dir,
320

            
321
    // Table elements
322
    /// Table container.
323
    Table,
324
    /// Table caption.
325
    Caption,
326
    /// Table header.
327
    THead,
328
    /// Table body.
329
    TBody,
330
    /// Table footer.
331
    TFoot,
332
    /// Table row.
333
    Tr,
334
    /// Table header cell.
335
    Th,
336
    /// Table data cell.
337
    Td,
338
    /// Table column group.
339
    ColGroup,
340
    /// Table column.
341
    Col,
342

            
343
    // Form elements
344
    /// Form container.
345
    Form,
346
    /// Form fieldset.
347
    FieldSet,
348
    /// Fieldset legend.
349
    Legend,
350
    /// Label for form controls.
351
    Label,
352
    /// Input control.
353
    Input,
354
    /// Button control.
355
    Button,
356
    /// Select dropdown.
357
    Select,
358
    /// Option group.
359
    OptGroup,
360
    /// Select option.
361
    SelectOption,
362
    /// Multiline text input.
363
    TextArea,
364
    /// Form output element.
365
    Output,
366
    /// Progress indicator.
367
    Progress,
368
    /// Scalar measurement within a known range.
369
    Meter,
370
    /// List of predefined options for input.
371
    DataList,
372

            
373
    // Inline elements
374
    /// Generic inline container.
375
    Span,
376
    /// Anchor/hyperlink.
377
    A,
378
    /// Emphasized text.
379
    Em,
380
    /// Strongly emphasized text.
381
    Strong,
382
    /// Bold text (deprecated - use `Dom::create_strong()` for semantic importance).
383
    B,
384
    /// Italic text (deprecated - use `Dom::create_em()` for emphasis or `Dom::create_cite()` for citations).
385
    I,
386
    /// Underline text.
387
    U,
388
    /// Strikethrough text.
389
    S,
390
    /// Marked/highlighted text.
391
    Mark,
392
    /// Deleted text.
393
    Del,
394
    /// Inserted text.
395
    Ins,
396
    /// Code.
397
    Code,
398
    /// Sample output.
399
    Samp,
400
    /// Keyboard input.
401
    Kbd,
402
    /// Variable.
403
    Var,
404
    /// Citation.
405
    Cite,
406
    /// Defining instance of a term.
407
    Dfn,
408
    /// Abbreviation.
409
    Abbr,
410
    /// Acronym.
411
    Acronym,
412
    /// Inline quotation.
413
    Q,
414
    /// Date/time.
415
    Time,
416
    /// Subscript.
417
    Sub,
418
    /// Superscript.
419
    Sup,
420
    /// Small text (deprecated - use CSS `font-size` instead).
421
    Small,
422
    /// Big text (deprecated - use CSS `font-size` instead).
423
    Big,
424
    /// Bi-directional override.
425
    Bdo,
426
    /// Bi-directional isolate.
427
    Bdi,
428
    /// Word break opportunity.
429
    Wbr,
430
    /// Ruby annotation.
431
    Ruby,
432
    /// Ruby text.
433
    Rt,
434
    /// Ruby text container.
435
    Rtc,
436
    /// Ruby parenthesis.
437
    Rp,
438
    /// Machine-readable data.
439
    Data,
440

            
441
    // Embedded content
442
    /// Canvas for graphics.
443
    Canvas,
444
    /// Embedded object.
445
    Object,
446
    /// Embedded object parameter.
447
    Param,
448
    /// External resource embed.
449
    Embed,
450
    /// Audio content.
451
    Audio,
452
    /// Video content.
453
    Video,
454
    /// Media source.
455
    Source,
456
    /// Text track for media.
457
    Track,
458
    /// Image map.
459
    Map,
460
    /// Image map area.
461
    Area,
462
    // SVG elements — container
463
    /// SVG `<svg>` root graphics container.
464
    Svg,
465
    /// SVG `<g>` group element.
466
    SvgG,
467
    /// SVG `<defs>` — reusable definitions (not rendered directly).
468
    SvgDefs,
469
    /// SVG `<symbol>` — like defs but with its own viewBox.
470
    SvgSymbol,
471
    /// SVG `<use>` — references and instantiates a defs element.
472
    SvgUse,
473
    /// SVG `<switch>` — conditional processing.
474
    SvgSwitch,
475

            
476
    // SVG elements — shape
477
    /// SVG `<path>` element.
478
    SvgPath,
479
    /// SVG `<circle>` element.
480
    SvgCircle,
481
    /// SVG `<rect>` element.
482
    SvgRect,
483
    /// SVG `<ellipse>` element.
484
    SvgEllipse,
485
    /// SVG `<line>` element.
486
    SvgLine,
487
    /// SVG `<polygon>` element.
488
    SvgPolygon,
489
    /// SVG `<polyline>` element.
490
    SvgPolyline,
491

            
492
    // SVG elements — text
493
    /// SVG `<text>` element.
494
    SvgText(AzString),
495
    /// SVG `<tspan>` element.
496
    SvgTspan,
497
    /// SVG `<textPath>` element.
498
    SvgTextPath,
499

            
500
    // SVG elements — paint servers
501
    /// SVG `<linearGradient>` element.
502
    SvgLinearGradient,
503
    /// SVG `<radialGradient>` element.
504
    SvgRadialGradient,
505
    /// SVG `<stop>` gradient stop element.
506
    SvgStop,
507
    /// SVG `<pattern>` element.
508
    SvgPattern,
509

            
510
    // SVG elements — clipping / masking
511
    /// SVG `<clipPath>` element.
512
    SvgClipPathElement,
513
    /// SVG `<mask>` element.
514
    SvgMask,
515

            
516
    // SVG elements — filter
517
    /// SVG `<filter>` container element.
518
    SvgFilter,
519
    /// SVG `<feBlend>`.
520
    SvgFeBlend,
521
    /// SVG `<feColorMatrix>`.
522
    SvgFeColorMatrix,
523
    /// SVG `<feComponentTransfer>`.
524
    SvgFeComponentTransfer,
525
    /// SVG `<feComposite>`.
526
    SvgFeComposite,
527
    /// SVG `<feConvolveMatrix>`.
528
    SvgFeConvolveMatrix,
529
    /// SVG `<feDiffuseLighting>`.
530
    SvgFeDiffuseLighting,
531
    /// SVG `<feDisplacementMap>`.
532
    SvgFeDisplacementMap,
533
    /// SVG `<feDistantLight>`.
534
    SvgFeDistantLight,
535
    /// SVG `<feDropShadow>`.
536
    SvgFeDropShadow,
537
    /// SVG `<feFlood>`.
538
    SvgFeFlood,
539
    /// SVG `<feFuncR>`.
540
    SvgFeFuncR,
541
    /// SVG `<feFuncG>`.
542
    SvgFeFuncG,
543
    /// SVG `<feFuncB>`.
544
    SvgFeFuncB,
545
    /// SVG `<feFuncA>`.
546
    SvgFeFuncA,
547
    /// SVG `<feGaussianBlur>`.
548
    SvgFeGaussianBlur,
549
    /// SVG `<feImage>`.
550
    SvgFeImage,
551
    /// SVG `<feMerge>`.
552
    SvgFeMerge,
553
    /// SVG `<feMergeNode>`.
554
    SvgFeMergeNode,
555
    /// SVG `<feMorphology>`.
556
    SvgFeMorphology,
557
    /// SVG `<feOffset>`.
558
    SvgFeOffset,
559
    /// SVG `<fePointLight>`.
560
    SvgFePointLight,
561
    /// SVG `<feSpecularLighting>`.
562
    SvgFeSpecularLighting,
563
    /// SVG `<feSpotLight>`.
564
    SvgFeSpotLight,
565
    /// SVG `<feTile>`.
566
    SvgFeTile,
567
    /// SVG `<feTurbulence>`.
568
    SvgFeTurbulence,
569

            
570
    // SVG elements — marker / image / foreign
571
    /// SVG `<marker>` element (not the CSS `::marker` pseudo-element).
572
    SvgMarker,
573
    /// SVG `<image>` element (embedded raster image in SVG).
574
    SvgImage(ImageRef),
575
    /// SVG `<foreignObject>` element.
576
    SvgForeignObject,
577

            
578
    // SVG elements — descriptive / structural
579
    /// SVG `<title>` element (distinct from HTML `<title>`).
580
    SvgTitle,
581
    /// SVG `<desc>` element.
582
    SvgDesc,
583
    /// SVG `<metadata>` element.
584
    SvgMetadata,
585
    /// SVG `<a>` hyperlink element (distinct from HTML `<a>`).
586
    SvgA,
587
    /// SVG `<view>` element.
588
    SvgView,
589
    /// SVG `<style>` element (distinct from HTML `<style>`).
590
    SvgStyle,
591
    /// SVG `<script>` element (distinct from HTML `<script>`).
592
    SvgScript,
593

            
594
    // SVG elements — animation
595
    /// SVG `<animate>` element.
596
    SvgAnimate,
597
    /// SVG `<animateMotion>` element.
598
    SvgAnimateMotion,
599
    /// SVG `<animateTransform>` element.
600
    SvgAnimateTransform,
601
    /// SVG `<set>` element.
602
    SvgSet,
603
    /// SVG `<mpath>` element.
604
    SvgMpath,
605

            
606
    // Metadata elements
607
    /// Document title.
608
    Title,
609
    /// Metadata.
610
    Meta,
611
    /// External resource link.
612
    Link,
613
    /// Embedded or referenced script.
614
    Script,
615
    /// Style information.
616
    Style,
617
    /// Base URL for relative URLs.
618
    Base,
619

            
620
    // Pseudo-elements (transformed into real elements)
621
    /// `::before` pseudo-element.
622
    Before,
623
    /// `::after` pseudo-element.
624
    After,
625
    /// `::marker` pseudo-element.
626
    Marker,
627
    /// `::placeholder` pseudo-element.
628
    Placeholder,
629

            
630
    // Special content types
631
    /// Text content, `::text`.
632
    /// Uses `BoxOrStatic` to keep `NodeType` small (~16B vs ~72B with inline `AzString`)
633
    /// and to allow static text references in the future.
634
    Text(BoxOrStatic<AzString>),
635
    /// Image element, `::image`.
636
    /// Uses `BoxOrStatic` to keep `NodeType` small.
637
    Image(BoxOrStatic<ImageRef>),
638
    /// `VirtualView` (embedded content) - payload stored in `NodeDataExt.virtual_view`
639
    VirtualView,
640
    /// Icon element - resolved to actual content by `IconProvider`.
641
    /// The string is the icon name (e.g., "home", "settings", "search").
642
    /// Uses `BoxOrStatic` to keep `NodeType` small.
643
    Icon(BoxOrStatic<AzString>),
644
    /// Invisible probe node that signals "this subtree needs the user's
645
    /// GPS / network location". Zero-size in layout, skipped in the
646
    /// display list. The `GeolocationManager` walks the styled DOM for
647
    /// these at end-of-layout and starts / stops the matching native
648
    /// subscription. See `SUPER_PLAN_2.md` §1.5 + research/08.
649
    GeolocationProbe(crate::geolocation::GeolocationProbeConfig),
650
    /// THE canonical page-break element: an empty block the UA styles with
651
    /// `break-before: page`. The pagination estimator and a screen DOM
652
    /// treat it identically; sibling margins collapse through it, so
653
    /// materializing an estimated break does not move content. XML tag:
654
    /// `<pagebreak/>`; constructor: [`Dom::create_page_break`].
655
    PageBreak,
656
}
657

            
658
/// Type alias: `BoxOrStatic<ImageRef>` — used by `NodeType::Image` for FFI monomorphization.
659
pub type BoxOrStaticImageRef = BoxOrStatic<ImageRef>;
660

            
661
impl_option!(NodeType, OptionNodeType, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
662

            
663
impl NodeType {
664
    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
665
1348037
    fn to_library_owned_nodetype(&self) -> Self {
666
        use self::NodeType::{Html, Head, Body, Div, P, Article, Section, Nav, Aside, Header, Footer, Main, Figure, FigCaption, H1, H2, H3, H4, H5, H6, Br, Hr, Pre, BlockQuote, Address, Details, Summary, Dialog, Ul, Ol, Li, Dl, Dt, Dd, Menu, MenuItem, Dir, Table, Caption, THead, TBody, TFoot, Tr, Th, Td, ColGroup, Col, Form, FieldSet, Legend, Label, Input, Button, Select, OptGroup, SelectOption, TextArea, Output, Progress, Meter, DataList, Span, A, Em, Strong, B, I, U, S, Mark, Del, Ins, Code, Samp, Kbd, Var, Cite, Dfn, Abbr, Acronym, Q, Time, Sub, Sup, Small, Big, Bdo, Bdi, Wbr, Ruby, Rt, Rtc, Rp, Data, Canvas, Object, Param, Embed, Audio, Video, Source, Track, Map, Area, Svg, SvgG, SvgDefs, SvgSymbol, SvgUse, SvgSwitch, SvgPath, SvgCircle, SvgRect, SvgEllipse, SvgLine, SvgPolygon, SvgPolyline, SvgText, SvgTspan, SvgTextPath, SvgLinearGradient, SvgRadialGradient, SvgStop, SvgPattern, SvgClipPathElement, SvgMask, SvgFilter, SvgFeBlend, SvgFeColorMatrix, SvgFeComponentTransfer, SvgFeComposite, SvgFeConvolveMatrix, SvgFeDiffuseLighting, SvgFeDisplacementMap, SvgFeDistantLight, SvgFeDropShadow, SvgFeFlood, SvgFeFuncR, SvgFeFuncG, SvgFeFuncB, SvgFeFuncA, SvgFeGaussianBlur, SvgFeImage, SvgFeMerge, SvgFeMergeNode, SvgFeMorphology, SvgFeOffset, SvgFePointLight, SvgFeSpecularLighting, SvgFeSpotLight, SvgFeTile, SvgFeTurbulence, SvgMarker, SvgImage, SvgForeignObject, SvgTitle, SvgDesc, SvgMetadata, SvgA, SvgView, SvgStyle, SvgScript, SvgAnimate, SvgAnimateMotion, SvgAnimateTransform, SvgSet, SvgMpath, Title, Meta, Link, Script, Style, Base, Before, After, Marker, Placeholder, Text, Image, VirtualView, Icon, GeolocationProbe};
667
1348037
        match self {
668
154
            Html => Html,
669
            Head => Head,
670
289
            Body => Body,
671
1205013
            Div => Div,
672
14933
            P => P,
673
            Article => Article,
674
22
            Section => Section,
675
            Nav => Nav,
676
            Aside => Aside,
677
            Header => Header,
678
            Footer => Footer,
679
            Main => Main,
680
            Figure => Figure,
681
            FigCaption => FigCaption,
682
45
            H1 => H1,
683
88
            H2 => H2,
684
22
            H3 => H3,
685
            H4 => H4,
686
            H5 => H5,
687
1
            H6 => H6,
688
1
            Br => Br,
689
            Hr => Hr,
690
            Pre => Pre,
691
33
            BlockQuote => BlockQuote,
692
            Address => Address,
693
            Details => Details,
694
            Summary => Summary,
695
            Dialog => Dialog,
696
154
            Ul => Ul,
697
            Ol => Ol,
698
297
            Li => Li,
699
            Dl => Dl,
700
            Dt => Dt,
701
            Dd => Dd,
702
            Menu => Menu,
703
            MenuItem => MenuItem,
704
            Dir => Dir,
705
1
            Table => Table,
706
            Caption => Caption,
707
            THead => THead,
708
            TBody => TBody,
709
            TFoot => TFoot,
710
            Tr => Tr,
711
            Th => Th,
712
1
            Td => Td,
713
            ColGroup => ColGroup,
714
            Col => Col,
715
            Form => Form,
716
            FieldSet => FieldSet,
717
            Legend => Legend,
718
            Label => Label,
719
1
            Input => Input,
720
1343
            Button => Button,
721
1
            Select => Select,
722
            OptGroup => OptGroup,
723
            SelectOption => SelectOption,
724
1
            TextArea => TextArea,
725
            Output => Output,
726
            Progress => Progress,
727
            Meter => Meter,
728
            DataList => DataList,
729
            Span => Span,
730
1
            A => A,
731
22
            Em => Em,
732
22
            Strong => Strong,
733
33
            B => B,
734
            I => I,
735
            U => U,
736
            S => S,
737
            Mark => Mark,
738
            Del => Del,
739
            Ins => Ins,
740
22
            Code => Code,
741
            Samp => Samp,
742
            Kbd => Kbd,
743
            Var => Var,
744
            Cite => Cite,
745
            Dfn => Dfn,
746
            Abbr => Abbr,
747
            Acronym => Acronym,
748
            Q => Q,
749
            Time => Time,
750
            Sub => Sub,
751
            Sup => Sup,
752
            Small => Small,
753
            Big => Big,
754
            Bdo => Bdo,
755
            Bdi => Bdi,
756
            Wbr => Wbr,
757
            Ruby => Ruby,
758
            Rt => Rt,
759
            Rtc => Rtc,
760
            Rp => Rp,
761
            Data => Data,
762
            Canvas => Canvas,
763
            Object => Object,
764
            Param => Param,
765
            Embed => Embed,
766
            Audio => Audio,
767
            Video => Video,
768
            Source => Source,
769
            Track => Track,
770
            Map => Map,
771
            Area => Area,
772
            // SVG container
773
1
            Svg => Svg, SvgG => SvgG, SvgDefs => SvgDefs, SvgSymbol => SvgSymbol,
774
            SvgUse => SvgUse, SvgSwitch => SvgSwitch,
775
            // SVG shape
776
1
            SvgPath => SvgPath, SvgCircle => SvgCircle, SvgRect => SvgRect,
777
            SvgEllipse => SvgEllipse, SvgLine => SvgLine,
778
            SvgPolygon => SvgPolygon, SvgPolyline => SvgPolyline,
779
            // SVG text
780
1
            SvgText(s) => SvgText(s.clone_self()),
781
            SvgTspan => SvgTspan, SvgTextPath => SvgTextPath,
782
            // SVG paint
783
            SvgLinearGradient => SvgLinearGradient, SvgRadialGradient => SvgRadialGradient,
784
            SvgStop => SvgStop, SvgPattern => SvgPattern,
785
            // SVG clip/mask
786
            SvgClipPathElement => SvgClipPathElement, SvgMask => SvgMask,
787
            // SVG filter
788
            SvgFilter => SvgFilter, SvgFeBlend => SvgFeBlend,
789
            SvgFeColorMatrix => SvgFeColorMatrix,
790
            SvgFeComponentTransfer => SvgFeComponentTransfer,
791
            SvgFeComposite => SvgFeComposite, SvgFeConvolveMatrix => SvgFeConvolveMatrix,
792
            SvgFeDiffuseLighting => SvgFeDiffuseLighting,
793
            SvgFeDisplacementMap => SvgFeDisplacementMap,
794
            SvgFeDistantLight => SvgFeDistantLight, SvgFeDropShadow => SvgFeDropShadow,
795
            SvgFeFlood => SvgFeFlood,
796
            SvgFeFuncR => SvgFeFuncR, SvgFeFuncG => SvgFeFuncG,
797
            SvgFeFuncB => SvgFeFuncB, SvgFeFuncA => SvgFeFuncA,
798
            SvgFeGaussianBlur => SvgFeGaussianBlur, SvgFeImage => SvgFeImage,
799
            SvgFeMerge => SvgFeMerge, SvgFeMergeNode => SvgFeMergeNode,
800
            SvgFeMorphology => SvgFeMorphology, SvgFeOffset => SvgFeOffset,
801
            SvgFePointLight => SvgFePointLight,
802
            SvgFeSpecularLighting => SvgFeSpecularLighting,
803
            SvgFeSpotLight => SvgFeSpotLight,
804
            SvgFeTile => SvgFeTile, SvgFeTurbulence => SvgFeTurbulence,
805
            // SVG marker/image/foreign
806
            SvgMarker => SvgMarker,
807
1
            SvgImage(i) => SvgImage(i.clone()),
808
            SvgForeignObject => SvgForeignObject,
809
            // SVG descriptive/structural
810
            SvgTitle => SvgTitle, SvgDesc => SvgDesc, SvgMetadata => SvgMetadata,
811
            SvgA => SvgA, SvgView => SvgView,
812
            SvgStyle => SvgStyle, SvgScript => SvgScript,
813
            // SVG animation
814
            SvgAnimate => SvgAnimate, SvgAnimateMotion => SvgAnimateMotion,
815
            SvgAnimateTransform => SvgAnimateTransform,
816
            SvgSet => SvgSet, SvgMpath => SvgMpath,
817
            // HTML metadata
818
            Title => Title,
819
            Meta => Meta,
820
            Link => Link,
821
            Script => Script,
822
            Style => Style,
823
            Base => Base,
824
1
            Before => Before,
825
1
            After => After,
826
1
            Marker => Marker,
827
1
            Placeholder => Placeholder,
828

            
829
121457
            Text(s) => Text(BoxOrStatic::heap(s.clone_self())),
830
2
            Image(i) => Image(i.clone()),
831
1
            VirtualView => VirtualView,
832
4068
            Icon(s) => Icon(BoxOrStatic::heap(s.clone_self())),
833
1
            GeolocationProbe(cfg) => GeolocationProbe(*cfg),
834
            Self::PageBreak => Self::PageBreak,
835
        }
836
1348037
    }
837

            
838
35967
    #[must_use] pub fn format(&self) -> Option<String> {
839
        use self::NodeType::{Text, Image, VirtualView, Icon, GeolocationProbe};
840
35967
        match self {
841
27184
            Text(s) => Some(format!("{s}")),
842
1
            Image(id) => Some(format!("image({id:?})")),
843
2
            VirtualView => Some("virtualized-view".to_string()),
844
2
            Icon(s) => Some(format!("icon({s})")),
845
6
            GeolocationProbe(cfg) => Some(format!(
846
6
                "geolocation-probe(hi={}, bg={}, max={}m, every={}ms)",
847
6
                cfg.high_accuracy, cfg.background, cfg.max_accuracy_m, cfg.min_interval_ms
848
6
            )),
849
8772
            _ => None,
850
        }
851
35967
    }
852

            
853
    /// Returns the `NodeTypeTag` for CSS selector matching.
854
    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
855
570400
    #[must_use] pub const fn get_path(&self) -> NodeTypeTag {
856
570400
        match self {
857
4526
            Self::Html => NodeTypeTag::Html,
858
            Self::Head => NodeTypeTag::Head,
859
11686
            Self::Body => NodeTypeTag::Body,
860
89308
            Self::Div => NodeTypeTag::Div,
861
132637
            Self::P => NodeTypeTag::P,
862
            Self::Article => NodeTypeTag::Article,
863
            Self::Section => NodeTypeTag::Section,
864
            Self::Nav => NodeTypeTag::Nav,
865
            Self::Aside => NodeTypeTag::Aside,
866
            Self::Header => NodeTypeTag::Header,
867
            Self::Footer => NodeTypeTag::Footer,
868
            Self::Main => NodeTypeTag::Main,
869
            Self::Figure => NodeTypeTag::Figure,
870
            Self::FigCaption => NodeTypeTag::FigCaption,
871
3325
            Self::H1 => NodeTypeTag::H1,
872
1903
            Self::H2 => NodeTypeTag::H2,
873
319
            Self::H3 => NodeTypeTag::H3,
874
11
            Self::H4 => NodeTypeTag::H4,
875
11
            Self::H5 => NodeTypeTag::H5,
876
14
            Self::H6 => NodeTypeTag::H6,
877
14
            Self::Br => NodeTypeTag::Br,
878
            Self::Hr => NodeTypeTag::Hr,
879
44
            Self::Pre => NodeTypeTag::Pre,
880
308
            Self::BlockQuote => NodeTypeTag::BlockQuote,
881
            Self::Address => NodeTypeTag::Address,
882
            Self::Details => NodeTypeTag::Details,
883
            Self::Summary => NodeTypeTag::Summary,
884
            Self::Dialog => NodeTypeTag::Dialog,
885
3410
            Self::Ul => NodeTypeTag::Ul,
886
            Self::Ol => NodeTypeTag::Ol,
887
10472
            Self::Li => NodeTypeTag::Li,
888
            Self::Dl => NodeTypeTag::Dl,
889
            Self::Dt => NodeTypeTag::Dt,
890
            Self::Dd => NodeTypeTag::Dd,
891
            Self::Menu => NodeTypeTag::Menu,
892
            Self::MenuItem => NodeTypeTag::MenuItem,
893
            Self::Dir => NodeTypeTag::Dir,
894
443
            Self::Table => NodeTypeTag::Table,
895
            Self::Caption => NodeTypeTag::Caption,
896
44
            Self::THead => NodeTypeTag::THead,
897
            Self::TBody => NodeTypeTag::TBody,
898
            Self::TFoot => NodeTypeTag::TFoot,
899
462
            Self::Tr => NodeTypeTag::Tr,
900
88
            Self::Th => NodeTypeTag::Th,
901
1015
            Self::Td => NodeTypeTag::Td,
902
            Self::ColGroup => NodeTypeTag::ColGroup,
903
            Self::Col => NodeTypeTag::Col,
904
            Self::Form => NodeTypeTag::Form,
905
            Self::FieldSet => NodeTypeTag::FieldSet,
906
            Self::Legend => NodeTypeTag::Legend,
907
            Self::Label => NodeTypeTag::Label,
908
14
            Self::Input => NodeTypeTag::Input,
909
17900
            Self::Button => NodeTypeTag::Button,
910
3
            Self::Select => NodeTypeTag::Select,
911
            Self::OptGroup => NodeTypeTag::OptGroup,
912
            Self::SelectOption => NodeTypeTag::SelectOption,
913
3
            Self::TextArea => NodeTypeTag::TextArea,
914
            Self::Output => NodeTypeTag::Output,
915
            Self::Progress => NodeTypeTag::Progress,
916
            Self::Meter => NodeTypeTag::Meter,
917
            Self::DataList => NodeTypeTag::DataList,
918
1078
            Self::Span => NodeTypeTag::Span,
919
542
            Self::A => NodeTypeTag::A,
920
308
            Self::Em => NodeTypeTag::Em,
921
308
            Self::Strong => NodeTypeTag::Strong,
922
            Self::B => NodeTypeTag::B,
923
            Self::I => NodeTypeTag::I,
924
            Self::U => NodeTypeTag::U,
925
            Self::S => NodeTypeTag::S,
926
            Self::Mark => NodeTypeTag::Mark,
927
            Self::Del => NodeTypeTag::Del,
928
            Self::Ins => NodeTypeTag::Ins,
929
308
            Self::Code => NodeTypeTag::Code,
930
            Self::Samp => NodeTypeTag::Samp,
931
            Self::Kbd => NodeTypeTag::Kbd,
932
            Self::Var => NodeTypeTag::Var,
933
            Self::Cite => NodeTypeTag::Cite,
934
            Self::Dfn => NodeTypeTag::Dfn,
935
            Self::Abbr => NodeTypeTag::Abbr,
936
            Self::Acronym => NodeTypeTag::Acronym,
937
            Self::Q => NodeTypeTag::Q,
938
            Self::Time => NodeTypeTag::Time,
939
            Self::Sub => NodeTypeTag::Sub,
940
            Self::Sup => NodeTypeTag::Sup,
941
            Self::Small => NodeTypeTag::Small,
942
            Self::Big => NodeTypeTag::Big,
943
            Self::Bdo => NodeTypeTag::Bdo,
944
            Self::Bdi => NodeTypeTag::Bdi,
945
            Self::Wbr => NodeTypeTag::Wbr,
946
            Self::Ruby => NodeTypeTag::Ruby,
947
            Self::Rt => NodeTypeTag::Rt,
948
            Self::Rtc => NodeTypeTag::Rtc,
949
            Self::Rp => NodeTypeTag::Rp,
950
            Self::Data => NodeTypeTag::Data,
951
            Self::Canvas => NodeTypeTag::Canvas,
952
            Self::Object => NodeTypeTag::Object,
953
            Self::Param => NodeTypeTag::Param,
954
            Self::Embed => NodeTypeTag::Embed,
955
            Self::Audio => NodeTypeTag::Audio,
956
            Self::Video => NodeTypeTag::Video,
957
            Self::Source => NodeTypeTag::Source,
958
            Self::Track => NodeTypeTag::Track,
959
            Self::Map => NodeTypeTag::Map,
960
            Self::Area => NodeTypeTag::Area,
961
            // SVG — all variants map 1:1 to NodeTypeTag
962
3
            Self::Svg => NodeTypeTag::Svg,
963
            Self::SvgG => NodeTypeTag::SvgG,
964
            Self::SvgDefs => NodeTypeTag::SvgDefs,
965
            Self::SvgSymbol => NodeTypeTag::SvgSymbol,
966
            Self::SvgUse => NodeTypeTag::SvgUse,
967
            Self::SvgSwitch => NodeTypeTag::SvgSwitch,
968
3
            Self::SvgPath => NodeTypeTag::SvgPath,
969
            Self::SvgCircle => NodeTypeTag::SvgCircle,
970
            Self::SvgRect => NodeTypeTag::SvgRect,
971
            Self::SvgEllipse => NodeTypeTag::SvgEllipse,
972
            Self::SvgLine => NodeTypeTag::SvgLine,
973
            Self::SvgPolygon => NodeTypeTag::SvgPolygon,
974
            Self::SvgPolyline => NodeTypeTag::SvgPolyline,
975
3
            Self::SvgText(_) => NodeTypeTag::SvgText,
976
            Self::SvgTspan => NodeTypeTag::SvgTspan,
977
            Self::SvgTextPath => NodeTypeTag::SvgTextPath,
978
            Self::SvgLinearGradient => NodeTypeTag::SvgLinearGradient,
979
            Self::SvgRadialGradient => NodeTypeTag::SvgRadialGradient,
980
            Self::SvgStop => NodeTypeTag::SvgStop,
981
            Self::SvgPattern => NodeTypeTag::SvgPattern,
982
            Self::SvgClipPathElement => NodeTypeTag::SvgClipPathElement,
983
            Self::SvgMask => NodeTypeTag::SvgMask,
984
            Self::SvgFilter => NodeTypeTag::SvgFilter,
985
            Self::SvgFeBlend => NodeTypeTag::SvgFeBlend,
986
            Self::SvgFeColorMatrix => NodeTypeTag::SvgFeColorMatrix,
987
            Self::SvgFeComponentTransfer => NodeTypeTag::SvgFeComponentTransfer,
988
            Self::SvgFeComposite => NodeTypeTag::SvgFeComposite,
989
            Self::SvgFeConvolveMatrix => NodeTypeTag::SvgFeConvolveMatrix,
990
            Self::SvgFeDiffuseLighting => NodeTypeTag::SvgFeDiffuseLighting,
991
            Self::SvgFeDisplacementMap => NodeTypeTag::SvgFeDisplacementMap,
992
            Self::SvgFeDistantLight => NodeTypeTag::SvgFeDistantLight,
993
            Self::SvgFeDropShadow => NodeTypeTag::SvgFeDropShadow,
994
            Self::SvgFeFlood => NodeTypeTag::SvgFeFlood,
995
            Self::SvgFeFuncR => NodeTypeTag::SvgFeFuncR,
996
            Self::SvgFeFuncG => NodeTypeTag::SvgFeFuncG,
997
            Self::SvgFeFuncB => NodeTypeTag::SvgFeFuncB,
998
            Self::SvgFeFuncA => NodeTypeTag::SvgFeFuncA,
999
            Self::SvgFeGaussianBlur => NodeTypeTag::SvgFeGaussianBlur,
            Self::SvgFeImage => NodeTypeTag::SvgFeImage,
            Self::SvgFeMerge => NodeTypeTag::SvgFeMerge,
            Self::SvgFeMergeNode => NodeTypeTag::SvgFeMergeNode,
            Self::SvgFeMorphology => NodeTypeTag::SvgFeMorphology,
            Self::SvgFeOffset => NodeTypeTag::SvgFeOffset,
            Self::SvgFePointLight => NodeTypeTag::SvgFePointLight,
            Self::SvgFeSpecularLighting => NodeTypeTag::SvgFeSpecularLighting,
            Self::SvgFeSpotLight => NodeTypeTag::SvgFeSpotLight,
            Self::SvgFeTile => NodeTypeTag::SvgFeTile,
            Self::SvgFeTurbulence => NodeTypeTag::SvgFeTurbulence,
            Self::SvgMarker => NodeTypeTag::SvgMarker,
3
            Self::SvgImage(_) => NodeTypeTag::SvgImage,
            Self::SvgForeignObject => NodeTypeTag::SvgForeignObject,
            Self::SvgTitle => NodeTypeTag::SvgTitle,
            Self::SvgDesc => NodeTypeTag::SvgDesc,
            Self::SvgMetadata => NodeTypeTag::SvgMetadata,
            Self::SvgA => NodeTypeTag::SvgA,
            Self::SvgView => NodeTypeTag::SvgView,
            Self::SvgStyle => NodeTypeTag::SvgStyle,
            Self::SvgScript => NodeTypeTag::SvgScript,
            Self::SvgAnimate => NodeTypeTag::SvgAnimate,
            Self::SvgAnimateMotion => NodeTypeTag::SvgAnimateMotion,
            Self::SvgAnimateTransform => NodeTypeTag::SvgAnimateTransform,
            Self::SvgSet => NodeTypeTag::SvgSet,
            Self::SvgMpath => NodeTypeTag::SvgMpath,
            // HTML metadata
            Self::Title => NodeTypeTag::Title,
            Self::Meta => NodeTypeTag::Meta,
            Self::Link => NodeTypeTag::Link,
            Self::Script => NodeTypeTag::Script,
            Self::Style => NodeTypeTag::Style,
            Self::Base => NodeTypeTag::Base,
271617
            Self::Text(_) => NodeTypeTag::Text,
30
            Self::Image(_) => NodeTypeTag::Img,
179
            Self::VirtualView => NodeTypeTag::VirtualView,
18021
            Self::Icon(_) => NodeTypeTag::Icon,
3
            Self::GeolocationProbe(_) => NodeTypeTag::GeolocationProbe,
22
            Self::PageBreak => NodeTypeTag::PageBreak,
3
            Self::Before => NodeTypeTag::Before,
3
            Self::After => NodeTypeTag::After,
3
            Self::Marker => NodeTypeTag::Marker,
3
            Self::Placeholder => NodeTypeTag::Placeholder,
        }
570400
    }
    /// Returns whether this node type is a semantic HTML element that should
    /// automatically generate an accessibility tree node.
    ///
    /// These are elements with inherent semantic meaning that assistive
    /// technologies should be aware of, even without explicit ARIA attributes.
41
    #[must_use] pub const fn is_semantic_for_accessibility(&self) -> bool {
24
        matches!(
41
            self,
            Self::Button
                | Self::Input
                | Self::TextArea
                | Self::Select
                | Self::A
                | Self::H1
                | Self::H2
                | Self::H3
                | Self::H4
                | Self::H5
                | Self::H6
                | Self::Article
                | Self::Section
                | Self::Nav
                | Self::Main
                | Self::Header
                | Self::Footer
                | Self::Aside
        )
41
    }
}
/// Represents the CSS formatting context for an element
#[derive(Clone, Copy, PartialEq, Eq)]
// [g147f az-web-lift] `#[repr(C, u8)]` forces an explicit u8 discriminant at offset 0 instead of letting
// Rust niche-pack the other variants' discriminants into the payload variants' (Block{bool}/Float/OutOfFlow)
// invalid byte values. The remill lift mis-decodes that niche encoding: `Block` (byte 0/1) reads correctly
// but `Inline` (a niche value) reads as garbage → `match` falls to `_` → nested <div>text</div> dispatches
// to layout_bfc instead of layout_ifc and its text never lays out (g147 root cause). Same fix pattern as the
// text3 enums (InlineContent/LogicalItem/ShapedItem/FontStack/LayoutError). Harmless + correct for native.
#[repr(C, u8)]
// +spec:display-property:844893 - block-level box establishing a new formatting context (BFC) modeled here
pub enum FormattingContext {
    /// Block-level formatting context
    Block {
        /// Whether this element establishes a new block formatting context
        establishes_new_context: bool,
    },
    /// Inline-level formatting context
    Inline,
    /// Inline-block (participates in an IFC but creates a BFC)
    InlineBlock,
    /// Flex formatting context
    Flex,
    /// Float (left or right)
    Float(LayoutFloat),
    /// Absolutely positioned (out of flow)
    OutOfFlow(LayoutPosition),
    /// Table formatting context (container)
    Table,
    /// Table row group formatting context (thead, tbody, tfoot)
    TableRowGroup,
    /// Table row formatting context
    TableRow,
    /// Table cell formatting context (td, th)
    TableCell,
    /// Table column group formatting context
    TableColumnGroup,
    /// Table caption formatting context
    TableCaption,
    /// Grid formatting context
    Grid,
    /// display:contents - element generates no box, children promoted to parent
    Contents,
    /// No formatting context (display: none)
    None,
}
impl fmt::Debug for FormattingContext {
463680
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463680
        match self {
            Self::Block {
108982
                establishes_new_context,
108982
            } => write!(
108982
                f,
108982
                "Block {{ establishes_new_context: {establishes_new_context:?} }}"
            ),
322480
            Self::Inline => write!(f, "Inline"),
649
            Self::InlineBlock => write!(f, "InlineBlock"),
27114
            Self::Flex => write!(f, "Flex"),
            Self::Float(layout_float) => write!(f, "Float({layout_float:?})"),
            Self::OutOfFlow(layout_position) => {
                write!(f, "OutOfFlow({layout_position:?})")
            }
33
            Self::Grid => write!(f, "Grid"),
            Self::None => write!(f, "None"),
462
            Self::Table => write!(f, "Table"),
            Self::TableRowGroup => write!(f, "TableRowGroup"),
55
            Self::TableRow => write!(f, "TableRow"),
3905
            Self::TableCell => write!(f, "TableCell"),
            Self::TableColumnGroup => write!(f, "TableColumnGroup"),
            Self::TableCaption => write!(f, "TableCaption"),
            Self::Contents => write!(f, "Contents"),
        }
463680
    }
}
impl Default for FormattingContext {
680
    fn default() -> Self {
680
        Self::Block {
680
            establishes_new_context: false,
680
        }
680
    }
}
/// Defines the type of event that can trigger a callback action.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum On {
    /// Mouse cursor is hovering over the element.
    MouseOver,
    /// Mouse cursor has is over element and is pressed
    /// (not good for "click" events - use `MouseUp` instead).
    MouseDown,
    /// (Specialization of `MouseDown`). Fires only if the left mouse button
    /// has been pressed while cursor was over the element.
    LeftMouseDown,
    /// (Specialization of `MouseDown`). Fires only if the middle mouse button
    /// has been pressed while cursor was over the element.
    MiddleMouseDown,
    /// (Specialization of `MouseDown`). Fires only if the right mouse button
    /// has been pressed while cursor was over the element.
    RightMouseDown,
    /// Mouse button has been released while cursor was over the element.
    MouseUp,
    /// (Specialization of `MouseUp`). Fires only if the left mouse button has
    /// been released while cursor was over the element.
    LeftMouseUp,
    /// (Specialization of `MouseUp`). Fires only if the middle mouse button has
    /// been released while cursor was over the element.
    MiddleMouseUp,
    /// (Specialization of `MouseUp`). Fires only if the right mouse button has
    /// been released while cursor was over the element.
    RightMouseUp,
    /// Mouse cursor has entered the element.
    MouseEnter,
    /// Mouse cursor has left the element.
    MouseLeave,
    /// Mousewheel / touchpad scrolling.
    Scroll,
    /// The window received a unicode character (also respects the system locale).
    /// Check `keyboard_state.current_char` to get the current pressed character.
    TextInput,
    /// A **virtual keycode** was pressed. Note: This is only the virtual keycode,
    /// not the actual char. If you want to get the character, use `TextInput` instead.
    /// A virtual key does not have to map to a printable character.
    ///
    /// You can get all currently pressed virtual keycodes in the
    /// `keyboard_state.current_virtual_keycodes` and / or just the last keycode in the
    /// `keyboard_state.latest_virtual_keycode`.
    VirtualKeyDown,
    /// A **virtual keycode** was release. See `VirtualKeyDown` for more info.
    VirtualKeyUp,
    /// A file has been dropped on the element.
    HoveredFile,
    /// A file is being hovered on the element.
    DroppedFile,
    /// A file was hovered, but has exited the window.
    HoveredFileCancelled,
    /// Equivalent to `onfocus`.
    FocusReceived,
    /// Equivalent to `onblur`.
    FocusLost,
    // Accessibility-specific events
    /// Default action triggered by screen reader (usually same as click/activate)
    Default,
    /// Element should collapse (e.g., accordion panel, tree node)
    Collapse,
    /// Element should expand (e.g., accordion panel, tree node)
    Expand,
    /// Increment value (e.g., number input, slider)
    Increment,
    /// Decrement value (e.g., number input, slider)
    Decrement,
    /// A structural document edit (split / merge / wrap / replace…) was
    /// recorded on (or under) this element and awaits the app's
    /// apply-and-ack (fires once per changeset; focus-scoped, bubbles to
    /// the contenteditable root). APPENDED at the enum tail for ABI
    /// stability.
    DocumentEdit,
}
/// Contains the necessary information to render an embedded `VirtualView` node.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct VirtualViewNode {
    /// The callback function that returns the DOM for the virtualized view's content.
    pub callback: VirtualViewCallback,
    /// The application data passed to the virtualized view's layout callback.
    pub refany: RefAny,
}
/// An enum that holds either a CSS ID or a class name as a string.
#[repr(C, u8)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum IdOrClass {
    Id(AzString),
    Class(AzString),
}
impl_option!(
    IdOrClass,
    OptionIdOrClass,
    copy = false,
    [Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord]
);
impl_vec!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor, IdOrClassVecDestructorType, IdOrClassVecSlice, OptionIdOrClass);
impl_vec_debug!(IdOrClass, IdOrClassVec);
impl_vec_partialord!(IdOrClass, IdOrClassVec);
impl_vec_ord!(IdOrClass, IdOrClassVec);
impl_vec_clone!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor);
impl_vec_partialeq!(IdOrClass, IdOrClassVec);
impl_vec_eq!(IdOrClass, IdOrClassVec);
impl_vec_hash!(IdOrClass, IdOrClassVec);
impl IdOrClass {
3
    #[must_use] pub fn as_id(&self) -> Option<&str> {
3
        match self {
2
            Self::Id(s) => Some(s.as_str()),
1
            Self::Class(_) => None,
        }
3
    }
3
    #[must_use] pub fn as_class(&self) -> Option<&str> {
3
        match self {
2
            Self::Class(s) => Some(s.as_str()),
1
            Self::Id(_) => None,
        }
3
    }
}
/// Name-value pair for custom attributes (data-*, aria-*, etc.)
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct AttributeNameValue {
    pub attr_name: AzString,
    pub value: AzString,
}
/// Strongly-typed HTML attribute with type-safe values.
///
/// This enum provides a type-safe way to represent HTML attributes, ensuring that
/// values are validated at compile-time and properly converted to their string
/// representations at runtime.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum AttributeType {
    /// Element ID attribute (`id="..."`)
    Id(AzString),
    /// CSS class attribute (`class="..."`)
    Class(AzString),
    /// Accessible name/label (`aria-label="..."`)
    AriaLabel(AzString),
    /// Element that labels this one (`aria-labelledby="..."`)
    AriaLabelledBy(AzString),
    /// Element that describes this one (`aria-describedby="..."`)
    AriaDescribedBy(AzString),
    /// Role for accessibility (`role="..."`)
    AriaRole(AzString),
    /// Current state of an element (`aria-checked`, `aria-selected`, etc.)
    AriaState(AttributeNameValue),
    /// ARIA property (`aria-*`)
    AriaProperty(AttributeNameValue),
    /// Hyperlink target URL (`href="..."`)
    Href(AzString),
    /// Link relationship (`rel="..."`)
    Rel(AzString),
    /// Link target frame (`target="..."`)
    Target(AzString),
    /// Image source URL (`src="..."`)
    Src(AzString),
    /// Alternative text for images (`alt="..."`)
    Alt(AzString),
    /// Image title (tooltip) (`title="..."`)
    Title(AzString),
    /// Form input name (`name="..."`)
    Name(AzString),
    /// Form input value (`value="..."`)
    Value(AzString),
    /// Input type (`type="text|password|email|..."`)
    InputType(AzString),
    /// Placeholder text (`placeholder="..."`)
    Placeholder(AzString),
    /// Input is required (`required`)
    Required,
    /// Input is disabled (`disabled`)
    Disabled,
    /// Input is readonly (`readonly`)
    Readonly,
    /// Input is checked (checkbox/radio) (`checked`)
    CheckedTrue,
    /// Input is unchecked (checkbox/radio)
    CheckedFalse,
    /// Input is selected (option) (`selected`)
    Selected,
    /// Maximum value for number inputs (`max="..."`)
    Max(AzString),
    /// Minimum value for number inputs (`min="..."`)
    Min(AzString),
    /// Step value for number inputs (`step="..."`)
    Step(AzString),
    /// Input pattern for validation (`pattern="..."`)
    Pattern(AzString),
    /// Minimum length (`minlength="..."`)
    MinLength(i32),
    /// Maximum length (`maxlength="..."`)
    MaxLength(i32),
    /// Autocomplete behavior (`autocomplete="on|off|..."`)
    Autocomplete(AzString),
    /// Table header scope (`scope="row|col|rowgroup|colgroup"`)
    Scope(AzString),
    /// Number of columns to span (`colspan="..."`)
    ColSpan(i32),
    /// Number of rows to span (`rowspan="..."`)
    RowSpan(i32),
    /// Tab index for keyboard navigation (`tabindex="..."`)
    TabIndex(i32),
    /// Element can receive focus (`tabindex="0"` equivalent)
    Focusable,
    /// Language code (`lang="..."`)
    Lang(AzString),
    /// Text direction (`dir="ltr|rtl|auto"`)
    Dir(AzString),
    /// Content is editable (`contenteditable="true|false"`)
    ContentEditable(bool),
    /// Element is draggable (`draggable="true|false"`)
    Draggable(bool),
    /// Element is hidden (`hidden`)
    Hidden,
    /// Generic data attribute (`data-*="..."`)
    Data(AttributeNameValue),
    /// Generic custom attribute (for future extensibility)
    Custom(AttributeNameValue),
}
impl_option!(
    AttributeType,
    OptionAttributeType,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor, AttributeTypeVecDestructorType, AttributeTypeVecSlice, OptionAttributeType);
impl_vec_debug!(AttributeType, AttributeTypeVec);
impl_vec_partialord!(AttributeType, AttributeTypeVec);
impl_vec_ord!(AttributeType, AttributeTypeVec);
impl_vec_clone!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor);
impl_vec_partialeq!(AttributeType, AttributeTypeVec);
impl_vec_eq!(AttributeType, AttributeTypeVec);
impl_vec_hash!(AttributeType, AttributeTypeVec);
impl AttributeType {
    /// Returns the id string if this is an `Id` attribute, `None` otherwise.
47736
    #[must_use] pub fn as_id(&self) -> Option<&str> {
47736
        match self {
41607
            Self::Id(s) => Some(s.as_str()),
6129
            _ => None,
        }
47736
    }
    /// Returns the class string if this is a `Class` attribute, `None` otherwise.
208000
    #[must_use] pub fn as_class(&self) -> Option<&str> {
208000
        match self {
205092
            Self::Class(s) => Some(s.as_str()),
2908
            _ => None,
        }
208000
    }
    /// Get the attribute name (e.g., "href", "aria-label", "data-foo")
419
    #[must_use] pub fn name(&self) -> &str {
419
        match self {
20
            Self::Id(_) => "id",
69
            Self::Class(_) => "class",
1
            Self::AriaLabel(_) => "aria-label",
1
            Self::AriaLabelledBy(_) => "aria-labelledby",
1
            Self::AriaDescribedBy(_) => "aria-describedby",
1
            Self::AriaRole(_) => "role",
1
            Self::AriaState(nv)
1
            | Self::AriaProperty(nv)
20
            | Self::Data(nv)
55
            | Self::Custom(nv) => nv.attr_name.as_str(),
134
            Self::Href(_) => "href",
1
            Self::Rel(_) => "rel",
1
            Self::Target(_) => "target",
1
            Self::Src(_) => "src",
1
            Self::Alt(_) => "alt",
1
            Self::Title(_) => "title",
1
            Self::Name(_) => "name",
2
            Self::Value(_) => "value",
39
            Self::InputType(_) => "type",
1
            Self::Placeholder(_) => "placeholder",
1
            Self::Required => "required",
1
            Self::Disabled => "disabled",
1
            Self::Readonly => "readonly",
4
            Self::CheckedTrue | Self::CheckedFalse => "checked",
1
            Self::Selected => "selected",
1
            Self::Max(_) => "max",
1
            Self::Min(_) => "min",
1
            Self::Step(_) => "step",
1
            Self::Pattern(_) => "pattern",
1
            Self::MinLength(_) => "minlength",
1
            Self::MaxLength(_) => "maxlength",
1
            Self::Autocomplete(_) => "autocomplete",
1
            Self::Scope(_) => "scope",
1
            Self::ColSpan(_) => "colspan",
1
            Self::RowSpan(_) => "rowspan",
4
            Self::TabIndex(_) | Self::Focusable => "tabindex",
63
            Self::Lang(_) => "lang",
1
            Self::Dir(_) => "dir",
1
            Self::ContentEditable(_) => "contenteditable",
1
            Self::Draggable(_) => "draggable",
1
            Self::Hidden => "hidden",
        }
419
    }
    /// Get the attribute value as a string
428
    #[must_use] pub fn value(&self) -> AzString {
428
        match self {
20
            Self::Id(v)
69
            | Self::Class(v)
1
            | Self::AriaLabel(v)
1
            | Self::AriaLabelledBy(v)
1
            | Self::AriaDescribedBy(v)
1
            | Self::AriaRole(v)
134
            | Self::Href(v)
1
            | Self::Rel(v)
1
            | Self::Target(v)
1
            | Self::Src(v)
1
            | Self::Alt(v)
1
            | Self::Title(v)
1
            | Self::Name(v)
2
            | Self::Value(v)
39
            | Self::InputType(v)
1
            | Self::Placeholder(v)
1
            | Self::Max(v)
1
            | Self::Min(v)
1
            | Self::Step(v)
1
            | Self::Pattern(v)
1
            | Self::Autocomplete(v)
1
            | Self::Scope(v)
63
            | Self::Lang(v)
345
            | Self::Dir(v) => v.clone(),
1
            Self::AriaState(nv)
1
            | Self::AriaProperty(nv)
20
            | Self::Data(nv)
50
            | Self::Custom(nv) => nv.value.clone(),
2
            Self::MinLength(n)
2
            | Self::MaxLength(n)
2
            | Self::ColSpan(n)
2
            | Self::RowSpan(n)
10
            | Self::TabIndex(n) => n.to_string().into(),
2
            Self::Focusable => "0".into(),
3
            Self::ContentEditable(b) | Self::Draggable(b) => {
6
                if *b {
3
                    "true".into()
                } else {
3
                    "false".into()
                }
            }
            Self::Required
            | Self::Disabled
            | Self::Readonly
            | Self::CheckedTrue
                | Self::CheckedFalse
            | Self::Selected
15
            | Self::Hidden => "".into(), // Boolean attributes
        }
428
    }
    /// Check if this is a boolean attribute (present = true, absent = false)
48
    #[must_use] pub const fn is_boolean(&self) -> bool {
39
        matches!(
48
            self,
            Self::Required
                | Self::Disabled
                | Self::Readonly
                | Self::CheckedTrue
                | Self::CheckedFalse
                | Self::Selected
                | Self::Hidden
        )
48
    }
}
/// Represents all data associated with a single DOM node, such as its type,
/// classes, IDs, callbacks, and inline styles.
#[repr(C)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeData {
    /// `div`, `p`, `img`, etc.
    pub node_type: NodeType,
    /// Callbacks attached to this node:
    ///
    /// `On::MouseUp` -> `Callback(my_button_click_handler)`
    pub callbacks: CoreCallbackDataVec,
    /// Inline style: a `Css` value that applies only to this node (implicit `:scope`).
    /// Each rule carries conditions (@media/@os/:hover/...) and declarations; rules
    /// produced by parsing inline strings are tagged `rule_priority::INLINE`, while
    /// widget defaults pushed via `with_css_props` keep the same INLINE priority so
    /// they override author CSS — preserving the cascade priority that the previous
    /// per-property `css_props` field had.
    pub style: azul_css::css::Css,
    /// Packed flags: `tab_index` + contenteditable + `is_anonymous`.
    pub flags: NodeFlags,
    /// Optional extra accessibility information about this DOM node (MSAA, AT-SPI, UA).
    /// 8 bytes (Option<Box<T>> is pointer-sized).
    pub accessibility: Option<Box<AccessibilityInfo>>,
    /// Stores "extra", not commonly used data of the node: clip-mask, menus, etc.
    ///
    /// SHOULD NOT EXPOSED IN THE API - necessary to retroactively add functionality
    /// to the node without breaking the ABI.
    extra: Option<Box<NodeDataExt>>,
}
impl_option!(
    NodeData,
    OptionNodeData,
    copy = false,
    [Debug, PartialEq, Eq, PartialOrd, Ord]
);
impl Hash for NodeData {
74051
    fn hash<H: Hasher>(&self, state: &mut H) {
74051
        self.node_type.hash(state);
74051
        self.attributes().as_ref().hash(state);
74051
        self.flags.hash(state);
        // NOTE: callbacks are NOT hashed regularly, otherwise
        // they'd cause inconsistencies because of the scroll callback
74051
        for callback in self.callbacks.as_ref() {
527
            callback.event.hash(state);
527
            callback.callback.hash(state);
527
            callback.refany.get_type_id().hash(state);
527
        }
        // Hash inline CSS properties (Static declarations only — same set the
        // legacy `css_props` field hashed). Conditions are intentionally
        // skipped to match the previous behaviour.
74051
        for (prop, _conds) in self.style.iter_inline_properties() {
118
            mem::discriminant(prop).hash(state);
118
        }
74051
        if let Some(ext) = self.extra.as_ref() {
65063
            if let Some(ds) = ext.dataset.as_ref() {
61
                ds.hash(state);
65002
            }
65063
            if let Some(c) = ext.svg_data.as_ref() {
4
                c.hash(state);
65059
            }
65063
            if let Some(c) = ext.menu_bar.as_ref() {
                c.hash(state);
65063
            }
65063
            if let Some(c) = ext.context_menu.as_ref() {
4
                c.hash(state);
65059
            }
65063
            if let Some(vv) = ext.virtual_view.as_ref() {
                vv.hash(state);
65063
            }
8988
        }
74051
    }
}
/// Tracks which component rendered a DOM subtree.
///
/// When a component's `render_fn` returns a `StyledDom`, the framework stamps the
/// root node(s) of the output with a `ComponentOrigin`. This enables:
/// - The debugger to show a "Component Tree" alongside the DOM tree
/// - Code generation roundtrips (rendered DOM → component invocations → code)
/// - Clicking a DOM node to navigate to the component that produced it
#[derive(Debug, Clone, PartialEq)]
pub struct ComponentOrigin {
    /// Qualified component name, e.g. "shadcn:card", "builtin:div"
    pub component_id: AzString,
    /// Snapshot of the data model at render time, stored as a JSON value.
    /// The debug server can inspect typed values; the frontend serializes
    /// them back to JSON for display and editing.
    pub data_model_json: crate::json::Json,
}
// Manual impls because Json contains f64 (no Eq/Ord/Hash derive),
// but we need them for NodeDataExt. We compare on the Display string.
impl Eq for ComponentOrigin {}
impl PartialOrd for ComponentOrigin {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for ComponentOrigin {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.component_id.cmp(&other.component_id)
            .then_with(|| {
                let a = alloc::format!("{}", self.data_model_json);
                let b = alloc::format!("{}", other.data_model_json);
                a.cmp(&b)
            })
    }
}
impl Hash for ComponentOrigin {
2
    fn hash<H: Hasher>(&self, state: &mut H) {
2
        self.component_id.hash(state);
2
        alloc::format!("{}", self.data_model_json).hash(state);
2
    }
}
impl Default for ComponentOrigin {
2
    fn default() -> Self {
2
        Self {
2
            component_id: AzString::from_const_str(""),
2
            data_model_json: crate::json::Json::null(),
2
        }
2
    }
}
/// SVG-specific data stored on a DOM node.
///
/// Each SVG element type stores its parsed attribute data here.
/// Also used for raster image clip masks (legacy C API).
#[derive(Debug, Clone, PartialOrd)]
pub enum SvgNodeData {
    /// Raster R8 image clip mask (legacy C API for chart.c style manual masks).
    ImageClipMask(ImageMask),
    /// `<path d="...">` — resolved path geometry.
    Path(crate::svg::SvgMultiPolygon),
    /// `<circle cx="" cy="" r="">`.
    Circle { cx: f32, cy: f32, r: f32 },
    /// `<rect x="" y="" width="" height="" rx="" ry="">`.
    Rect { x: f32, y: f32, width: f32, height: f32, rx: f32, ry: f32 },
    /// `<ellipse cx="" cy="" rx="" ry="">`.
    Ellipse { cx: f32, cy: f32, rx: f32, ry: f32 },
    /// `<line x1="" y1="" x2="" y2="">`.
    Line { x1: f32, y1: f32, x2: f32, y2: f32 },
    /// `<polygon points="">` / `<polyline points="">` — parsed point list.
    PointsList { points: alloc::vec::Vec<azul_css::props::basic::SvgPoint>, closed: bool },
    /// `<svg viewBox="" width="" height="">` — viewport attributes.
    ViewBox { min_x: f32, min_y: f32, width: f32, height: f32 },
    /// `<linearGradient>` attributes.
    LinearGradient { x1: f32, y1: f32, x2: f32, y2: f32 },
    /// `<radialGradient>` attributes.
    RadialGradient { cx: f32, cy: f32, r: f32, fx: f32, fy: f32 },
    /// `<stop offset="" stop-color="" stop-opacity="">`.
    GradientStop { offset: f32 },
    /// `<use href="" x="" y="">`.
    Use { href: AzString, x: f32, y: f32 },
    /// `<image href="" x="" y="" width="" height="">`.
    SvgImageData { href: AzString, x: f32, y: f32, width: f32, height: f32 },
}
// PartialEq compares f32 fields by BIT PATTERN (to_bits), mirroring the Hash impl
// below, so a NaN coordinate is equal to itself and Eq/Hash agree. A derived PartialEq
// used raw float `==` (NaN != NaN), breaking Eq's reflexivity for e.g. a NaN Rect —
// and NodeType embeds this type, so the break propagated.
impl PartialEq for SvgNodeData {
    #[allow(clippy::match_same_arms, clippy::similar_names)] // SVG coord names (cx/cy/fx/fy, min_x/min_y) are domain-standard
4
    fn eq(&self, other: &Self) -> bool {
        // f32 bit-equality (matches Hash's to_bits).
8
        const fn fb(a: f32, b: f32) -> bool {
8
            a.to_bits() == b.to_bits()
8
        }
        use self::SvgNodeData::{
            Circle, Ellipse, GradientStop, ImageClipMask, Line, LinearGradient, Path,
            PointsList, RadialGradient, Rect, SvgImageData, Use, ViewBox,
        };
4
        match (self, other) {
            (ImageClipMask(a), ImageClipMask(b)) => a == b,
            (Path(a), Path(b)) => {
                let ra = a.rings.as_ref();
                let rb = b.rings.as_ref();
                ra.len() == rb.len()
                    && ra.iter().zip(rb.iter()).all(|(x, y)| {
                        let ia = x.items.as_ref();
                        let ib = y.items.as_ref();
                        ia.len() == ib.len()
                            && ia.iter().zip(ib.iter()).all(|(p, q)| svg_path_element_bits_eq(p, q))
                    })
            }
            (Circle { cx, cy, r }, Circle { cx: cx2, cy: cy2, r: r2 }) => {
                fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2)
            }
            (
1
                Rect { x, y, width, height, rx, ry },
1
                Rect { x: x2, y: y2, width: w2, height: h2, rx: rx2, ry: ry2 },
            ) => {
1
                fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2)
1
                    && fb(*height, *h2) && fb(*rx, *rx2) && fb(*ry, *ry2)
            }
            (Ellipse { cx, cy, rx, ry }, Ellipse { cx: cx2, cy: cy2, rx: rx2, ry: ry2 }) => {
                fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*rx, *rx2) && fb(*ry, *ry2)
            }
            (Line { x1, y1, x2, y2 }, Line { x1: a1, y1: b1, x2: a2, y2: b2 })
            | (LinearGradient { x1, y1, x2, y2 }, LinearGradient { x1: a1, y1: b1, x2: a2, y2: b2 }) => {
                fb(*x1, *a1) && fb(*y1, *b1) && fb(*x2, *a2) && fb(*y2, *b2)
            }
            (PointsList { points: pa, closed: ca }, PointsList { points: pb, closed: cb }) => {
                ca == cb
                    && pa.len() == pb.len()
                    && pa.iter().zip(pb.iter()).all(|(p, q)| fb(p.x, q.x) && fb(p.y, q.y))
            }
            (
                ViewBox { min_x, min_y, width, height },
                ViewBox { min_x: a, min_y: b, width: w, height: h },
            ) => fb(*min_x, *a) && fb(*min_y, *b) && fb(*width, *w) && fb(*height, *h),
            (
                RadialGradient { cx, cy, r, fx, fy },
                RadialGradient { cx: cx2, cy: cy2, r: r2, fx: fx2, fy: fy2 },
            ) => {
                fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2) && fb(*fx, *fx2) && fb(*fy, *fy2)
            }
2
            (GradientStop { offset: a }, GradientStop { offset: b }) => fb(*a, *b),
            (Use { href, x, y }, Use { href: h2, x: x2, y: y2 }) => {
                href == h2 && fb(*x, *x2) && fb(*y, *y2)
            }
            (
                SvgImageData { href, x, y, width, height },
                SvgImageData { href: h2, x: x2, y: y2, width: w2, height: hh2 },
            ) => {
                href == h2 && fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2) && fb(*height, *hh2)
            }
            // Different variants are never equal.
1
            _ => false,
        }
4
    }
}
/// Bit-equality for two `SvgPathElement`s (matches the Hash impl's per-coordinate
/// `to_bits`), so NaN path coordinates are self-equal.
const fn svg_path_element_bits_eq(
    a: &crate::svg::SvgPathElement,
    b: &crate::svg::SvgPathElement,
) -> bool {
    use crate::svg::SvgPathElement::{CubicCurve, Line, QuadraticCurve};
    const fn pb(a: azul_css::props::basic::SvgPoint, b: azul_css::props::basic::SvgPoint) -> bool {
        a.x.to_bits() == b.x.to_bits() && a.y.to_bits() == b.y.to_bits()
    }
    match (a, b) {
        (Line(a), Line(b)) => pb(a.start, b.start) && pb(a.end, b.end),
        (QuadraticCurve(a), QuadraticCurve(b)) => {
            pb(a.start, b.start) && pb(a.ctrl, b.ctrl) && pb(a.end, b.end)
        }
        (CubicCurve(a), CubicCurve(b)) => {
            pb(a.start, b.start) && pb(a.ctrl_1, b.ctrl_1)
                && pb(a.ctrl_2, b.ctrl_2) && pb(a.end, b.end)
        }
        _ => false,
    }
}
impl Eq for SvgNodeData {}
// SvgNodeData contains f32 (svg coords) so Ord can't be derived; this Ord is
// defined *in terms of* the derived field-wise PartialOrd (unwrap_or Equal), so
// the two cannot disagree — the derive_ord_xor_partial_ord concern doesn't apply.
#[allow(clippy::derive_ord_xor_partial_ord)]
impl Ord for SvgNodeData {
1
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1
        self.partial_cmp(other).unwrap_or(core::cmp::Ordering::Equal)
1
    }
}
impl Hash for SvgNodeData {
6
    fn hash<H: Hasher>(&self, state: &mut H) {
6
        mem::discriminant(self).hash(state);
6
        match self {
            Self::ImageClipMask(m) => m.hash(state),
            Self::Path(mp) => {
                for ring in mp.rings.as_ref() {
                    for item in ring.items.as_ref() {
                        match item {
                            crate::svg::SvgPathElement::Line(l) => {
                                0u8.hash(state);
                                l.start.x.to_bits().hash(state);
                                l.start.y.to_bits().hash(state);
                                l.end.x.to_bits().hash(state);
                                l.end.y.to_bits().hash(state);
                            }
                            crate::svg::SvgPathElement::QuadraticCurve(q) => {
                                1u8.hash(state);
                                q.start.x.to_bits().hash(state);
                                q.start.y.to_bits().hash(state);
                                q.ctrl.x.to_bits().hash(state);
                                q.ctrl.y.to_bits().hash(state);
                                q.end.x.to_bits().hash(state);
                                q.end.y.to_bits().hash(state);
                            }
                            crate::svg::SvgPathElement::CubicCurve(c) => {
                                2u8.hash(state);
                                c.start.x.to_bits().hash(state);
                                c.start.y.to_bits().hash(state);
                                c.ctrl_1.x.to_bits().hash(state);
                                c.ctrl_1.y.to_bits().hash(state);
                                c.ctrl_2.x.to_bits().hash(state);
                                c.ctrl_2.y.to_bits().hash(state);
                                c.end.x.to_bits().hash(state);
                                c.end.y.to_bits().hash(state);
                            }
                        }
                    }
                }
            }
            Self::Circle { cx, cy, r } => {
                cx.to_bits().hash(state); cy.to_bits().hash(state); r.to_bits().hash(state);
            }
2
            Self::Rect { x, y, width, height, rx, ry } => {
2
                x.to_bits().hash(state); y.to_bits().hash(state);
2
                width.to_bits().hash(state); height.to_bits().hash(state);
2
                rx.to_bits().hash(state); ry.to_bits().hash(state);
2
            }
            Self::Ellipse { cx, cy, rx, ry } => {
                cx.to_bits().hash(state); cy.to_bits().hash(state);
                rx.to_bits().hash(state); ry.to_bits().hash(state);
            }
            // Line and LinearGradient share a { x1, y1, x2, y2 } shape and hash
            // identically (Eq still distinguishes the variants); fold the duplicate bodies.
            Self::Line { x1, y1, x2, y2 } | Self::LinearGradient { x1, y1, x2, y2 } => {
                x1.to_bits().hash(state); y1.to_bits().hash(state);
                x2.to_bits().hash(state); y2.to_bits().hash(state);
            }
            Self::PointsList { points, closed } => {
                for p in points {
                    p.x.to_bits().hash(state); p.y.to_bits().hash(state);
                }
                closed.hash(state);
            }
            Self::ViewBox { min_x, min_y, width, height } => {
                min_x.to_bits().hash(state); min_y.to_bits().hash(state);
                width.to_bits().hash(state); height.to_bits().hash(state);
            }
            Self::RadialGradient { cx, cy, r, fx, fy } => {
                cx.to_bits().hash(state); cy.to_bits().hash(state);
                r.to_bits().hash(state); fx.to_bits().hash(state);
                fy.to_bits().hash(state);
            }
4
            Self::GradientStop { offset } => {
4
                offset.to_bits().hash(state);
4
            }
            Self::Use { href, x, y } => {
                href.hash(state);
                x.to_bits().hash(state); y.to_bits().hash(state);
            }
            Self::SvgImageData { href, x, y, width, height } => {
                href.hash(state);
                x.to_bits().hash(state); y.to_bits().hash(state);
                width.to_bits().hash(state); height.to_bits().hash(state);
            }
        }
6
    }
}
/// NOTE: NOT EXPOSED IN THE API! Stores extra,
/// not commonly used information for the `NodeData`.
/// This helps keep the primary `NodeData` struct smaller for common cases.
#[repr(C)]
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct NodeDataExt {
    /// Strongly-typed HTML attributes (aria-*, href, alt, etc.)
    /// IDs and classes are stored as `AttributeType::Id` and `AttributeType::Class` entries.
    /// Moved from `NodeData` to save 48B for the ~95% of nodes with no attributes.
    pub attributes: AttributeTypeVec,
    /// `VirtualView` callback data, only set when `node_type` == `NodeType::VirtualView`.
    pub virtual_view: Option<VirtualViewNode>,
    /// `data-*` attributes for this node, useful to store UI-related data on the node itself.
    pub dataset: Option<RefAny>,
    /// SVG-specific data or raster clip mask for this DOM node.
    pub svg_data: Option<SvgNodeData>,
    /// Menu bar that should be displayed at the top of this nodes rect.
    pub menu_bar: Option<Box<Menu>>,
    /// Context menu that should be opened when the item is left-clicked.
    pub context_menu: Option<Box<Menu>>,
    /// Stable key for reconciliation. If provided, allows the framework to track
    /// this node across frames even if its position in the array changes.
    /// This is crucial for correct lifecycle events when lists are reordered.
    pub key: Option<u64>,
    /// Callback to merge dataset state from a previous frame's node into the current node.
    /// This enables heavy resource preservation (video decoders, GL textures) across frames.
    pub dataset_merge_callback: Option<DatasetMergeCallback>,
    /// Tracks which component rendered this DOM subtree.
    /// Set by the framework during component rendering — the root node(s) of a
    /// component's output DOM get stamped with the component's qualified name.
    /// Enables the debugger to reconstruct the component invocation tree from the
    /// flat rendered DOM, and enables code generation roundtrips.
    pub component_origin: Option<ComponentOrigin>,
    /// COMPONENT-ATTACHED presence-animation functions, resolvable by NAME
    /// from this node's `-azul-animation-in` / `-azul-animation-out` (after
    /// stylesheet `@keyframes` — the web mechanism stays the only default
    /// name source). This replaced the global `AppConfig` registry (USER
    /// ruling 2026-08-17): a sidebar widget ships its fly-out next to its
    /// own DOM, not in app-global state.
    pub animation_callbacks: Vec<crate::resources::AnimationFunction>,
}
/// A callback function used to merge the state of an old dataset into a new one.
///
/// This enables components with heavy internal state (video players, WebGL contexts)
/// to preserve their resources across frames, while the DOM tree is recreated.
///
/// The callback receives both the old and new datasets as `RefAny` (cheap shallow clones)
/// and returns the dataset that should be used for the new node.
///
/// # Example
///
/// ```rust,ignore
/// fn merge_video_state(new_data: RefAny, old_data: RefAny) -> RefAny {
///     // Transfer heavy resources from old to new
///     if let (Some(mut new), Some(old)) = (
///         new_data.downcast_mut::<VideoState>(),
///         old_data.downcast_ref::<VideoState>()
///     ) {
///         new.decoder = old.decoder.take();
///         new.gl_texture = old.gl_texture.take();
///     }
///     new_data // Return the merged state
/// }
/// ```
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct DatasetMergeCallback {
    /// The function pointer that performs the merge.
    /// Signature: `fn(new_data: RefAny, old_data: RefAny) -> RefAny`
    pub cb: DatasetMergeCallbackType,
    /// Optional callable for FFI language bindings (Python, etc.)
    /// When set, the FFI layer can invoke this instead of `cb`.
    pub callable: OptionRefAny,
}
impl fmt::Debug for DatasetMergeCallback {
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1
        f.debug_struct("DatasetMergeCallback")
1
            .field("cb", &(self.cb as usize))
1
            .field("callable", &self.callable)
1
            .finish()
1
    }
}
/// Allow creating `DatasetMergeCallback` from a raw function pointer.
/// This enables the `Into<DatasetMergeCallback>` pattern for Python bindings.
impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
2339
    fn from(cb: DatasetMergeCallbackType) -> Self {
2339
        Self {
2339
            cb,
2339
            callable: OptionRefAny::None,
2339
        }
2339
    }
}
impl DatasetMergeCallback {
    /// Build from a raw `DatasetMergeCallbackType` function pointer (callable =
    /// None). The concrete parameter is a coercion site, so callers can pass a
    /// bare `extern "C" fn` item without an `as DatasetMergeCallbackType` cast.
    #[must_use]
714
    pub fn from_ptr(cb: DatasetMergeCallbackType) -> Self {
714
        Self::from(cb)
714
    }
}
impl_option!(
    DatasetMergeCallback,
    OptionDatasetMergeCallback,
    copy = false,
    [Debug, Clone]
);
/// Function pointer type for dataset merge callbacks.
///
/// Arguments:
/// - `new_data`: The new node's dataset (shallow clone, cheap)
/// - `old_data`: The old node's dataset (shallow clone, cheap)
///
/// Returns:
/// - The `RefAny` that should be used as the dataset for the new node
pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
impl Clone for NodeData {
    #[inline]
748094
    fn clone(&self) -> Self {
748094
        Self {
748094
            node_type: self.node_type.to_library_owned_nodetype(),
748094
            style: self.style.clone(),
748094
            callbacks: self.callbacks.clone(),
748094
            flags: self.flags,
748094
            accessibility: self.accessibility.clone(),
748094
            extra: self.extra.clone(),
748094
        }
748094
    }
}
// Clone, PartialEq, Eq, Hash, PartialOrd, Ord
impl_vec!(NodeData, NodeDataVec, NodeDataVecDestructor, NodeDataVecDestructorType, NodeDataVecSlice, OptionNodeData);
impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
impl_vec_mut!(NodeData, NodeDataVec);
impl_vec_debug!(NodeData, NodeDataVec);
impl_vec_partialord!(NodeData, NodeDataVec);
impl_vec_ord!(NodeData, NodeDataVec);
impl_vec_partialeq!(NodeData, NodeDataVec);
impl_vec_eq!(NodeData, NodeDataVec);
impl_vec_hash!(NodeData, NodeDataVec);
impl NodeDataVec {
    #[inline]
31963318
    #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeData> {
31963318
        NodeDataContainerRef {
31963318
            internal: self.as_ref(),
31963318
        }
31963318
    }
    #[inline]
31
    pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeData> {
31
        NodeDataContainerRefMut {
31
            internal: self.as_mut(),
31
        }
31
    }
}
// SAFETY: All fields in NodeData are either Send (NodeType, NodeFlags, CssPropertyWithConditionsVec),
// Arc-wrapped (RefAny), or plain data (Box<AccessibilityInfo>, Box<NodeDataExt>).
// Function pointers (callbacks) are inherently Send. The RefAny uses atomic reference counting.
unsafe impl Send for NodeData {}
/// Determines the behavior of an element in sequential focus navigation
// (e.g., using the Tab key).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C, u8)]
#[derive(Default)]
pub enum TabIndex {
    /// Automatic tab index, similar to simply setting `focusable = "true"` or `tabindex = 0`
    /// (both have the effect of making the element focusable).
    ///
    /// Sidenote: See <https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute>
    /// for interesting notes on tabindex and accessibility
    #[default]
    Auto,
    /// Set the tab index in relation to its parent element. I.e. if you have a list of elements,
    /// the focusing order is restricted to the current parent.
    ///
    /// When pressing tab repeatedly, the focusing order will be
    /// determined by `OverrideInParent` elements taking precedence among global order.
    OverrideInParent(u32),
    /// Elements can be focused in callbacks, but are not accessible via
    /// keyboard / tab navigation (-1).
    NoKeyboardFocus,
}
impl_option!(
    TabIndex,
    OptionTabIndex,
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl TabIndex {
    /// Returns the HTML-compatible number of the `tabindex` element.
    // const fn: TryFrom isn't const, and u32 -> isize is lossless on every
    // supported (>= 32-bit) target, so the `as` cast cannot actually wrap here.
    #[allow(clippy::cast_possible_wrap)]
6
    #[must_use] pub const fn get_index(&self) -> isize {
        use self::TabIndex::{Auto, OverrideInParent, NoKeyboardFocus};
6
        match self {
2
            Auto => 0,
2
            OverrideInParent(x) => *x as isize,
2
            NoKeyboardFocus => -1,
        }
6
    }
}
/// Packed representation of tab index + contenteditable flag.
///
/// Bit layout (32 bits):
///   [31]     contenteditable flag (1 = true)
///   [30:29]  `tab_index` variant:
///              00 = None (no tab index set)
///              01 = Auto
///              10 = `OverrideInParent` (value in bits [28:0])
///              11 = `NoKeyboardFocus`
///   [28]     `is_anonymous` (1 = anonymous box for table layout)
///   [27:0]   `OverrideInParent` value (max ~268 million)
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Default)]
pub struct NodeFlags {
    pub inner: u32,
}
impl NodeFlags {
    const CONTENTEDITABLE_BIT: u32 = 1 << 31;
    const TAB_INDEX_MASK: u32      = 0b11 << 29;
    const ANONYMOUS_BIT: u32       = 1 << 28;
    const TAB_VALUE_MASK: u32      = (1 << 28) - 1;
    const TAB_NONE: u32            = 0b00 << 29;
    const TAB_AUTO: u32            = 0b01 << 29;
    const TAB_OVERRIDE: u32        = 0b10 << 29;
    const TAB_NO_KEYBOARD: u32     = 0b11 << 29;
2406057
    #[must_use] pub const fn new() -> Self {
2406057
        Self { inner: 0 }
2406057
    }
3180806
    #[must_use] pub const fn is_contenteditable(&self) -> bool {
3180806
        (self.inner & Self::CONTENTEDITABLE_BIT) != 0
3180806
    }
2
    #[must_use] pub const fn set_contenteditable(mut self, v: bool) -> Self {
2
        if v {
1
            self.inner |= Self::CONTENTEDITABLE_BIT;
1
        } else {
1
            self.inner &= !Self::CONTENTEDITABLE_BIT;
1
        }
2
        self
2
    }
4709
    pub const fn set_contenteditable_mut(&mut self, v: bool) {
4709
        if v {
4706
            self.inner |= Self::CONTENTEDITABLE_BIT;
4706
        } else {
3
            self.inner &= !Self::CONTENTEDITABLE_BIT;
3
        }
4709
    }
2353563
    #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2353563
        match self.inner & Self::TAB_INDEX_MASK {
2353563
            x if x == Self::TAB_NONE => None,
264476
            x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
3148
            x if x == Self::TAB_OVERRIDE => {
2379
                let val = self.inner & Self::TAB_VALUE_MASK;
2379
                Some(TabIndex::OverrideInParent(val))
            }
769
            x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
            _ => None,
        }
2353563
    }
    /// Returns whether this node is an anonymous box generated for table layout.
1045349
    #[must_use] pub const fn is_anonymous(&self) -> bool {
1045349
        (self.inner & Self::ANONYMOUS_BIT) != 0
1045349
    }
15
    pub const fn set_anonymous(&mut self, v: bool) {
15
        if v {
14
            self.inner |= Self::ANONYMOUS_BIT;
14
        } else {
1
            self.inner &= !Self::ANONYMOUS_BIT;
1
        }
15
    }
287913
    pub const fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
        // Clear tab index bits (bits 29-30) and value bits (bits 0-27)
        // keep contenteditable bit (31) and anonymous bit (28)
287913
        self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
287911
        match tab_index {
2
            None => { /* TAB_NONE = 0, already cleared */ }
287019
            Some(TabIndex::Auto) => {
287019
                self.inner |= Self::TAB_AUTO;
287019
            }
663
            Some(TabIndex::OverrideInParent(val)) => {
663
                self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
663
            }
229
            Some(TabIndex::NoKeyboardFocus) => {
229
                self.inner |= Self::TAB_NO_KEYBOARD;
229
            }
        }
287913
    }
}
impl Default for NodeData {
6
    fn default() -> Self {
6
        Self::create_node(NodeType::Div)
6
    }
}
impl fmt::Display for NodeData {
8
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8
        let html_type = self.node_type.get_path();
8
        let attributes_string = node_data_to_string(self);
8
        match self.node_type.format() {
7
            Some(content) => write!(
7
                f,
7
                "<{html_type}{attributes_string}>{content}</{html_type}>"
            ),
1
            None => write!(f, "<{html_type}{attributes_string}/>"),
        }
8
    }
}
341
fn node_data_to_string(node_data: &NodeData) -> String {
341
    let mut id_string = String::new();
341
    let ids = node_data
341
        .attributes()
341
        .as_ref()
341
        .iter()
341
        .filter_map(|s| s.as_id())
341
        .collect::<Vec<_>>()
341
        .join(" ");
341
    if !ids.is_empty() {
36
        id_string = format!(" id=\"{ids}\" ");
305
    }
341
    let mut class_string = String::new();
341
    let classes = node_data
341
        .attributes()
341
        .as_ref()
341
        .iter()
341
        .filter_map(|s| s.as_class())
341
        .collect::<Vec<_>>()
341
        .join(" ");
341
    if !classes.is_empty() {
16
        class_string = format!(" class=\"{classes}\" ");
325
    }
341
    let mut tabindex_string = String::new();
341
    if let Some(tab_index) = node_data.get_tab_index() {
1
        tabindex_string = format!(" tabindex=\"{}\" ", tab_index.get_index());
340
    }
341
    format!("{id_string}{class_string}{tabindex_string}")
341
}
impl NodeData {
    /// Creates a new `NodeData` instance from a given `NodeType`.
    #[inline]
2406039
    #[must_use] pub const fn create_node(node_type: NodeType) -> Self {
2406039
        Self {
2406039
            node_type,
2406039
            callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2406039
            style: azul_css::css::Css {
2406039
                rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2406039
                keyframes: azul_css::css::KeyframesVec::from_const_slice(&[]),
2406039
                },
2406039
            flags: NodeFlags::new(),
2406039
            accessibility: None,
2406039
            extra: None,
2406039
        }
2406039
    }
    /// Returns a reference to the node's attributes (from `NodeDataExt`).
    /// Returns an empty slice if no attributes have been set.
    #[inline]
8302181
    #[must_use] pub fn attributes(&self) -> &AttributeTypeVec {
        static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
8302181
        self.extra.as_ref().map_or(&EMPTY, |ext| &ext.attributes)
8302181
    }
    /// Returns a mutable reference to the node's attributes,
    /// lazily allocating `NodeDataExt` if needed.
    #[inline]
731012
    pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
731012
        &mut self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes
731012
    }
    /// Sets the node's attributes, replacing any existing ones.
    #[inline]
736134
    pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
736134
        self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes = attrs;
736134
    }
    /// Shorthand for `NodeData::create_node(NodeType::Body)`.
    #[inline]
3172
    #[must_use] pub const fn create_body() -> Self {
3172
        Self::create_node(NodeType::Body)
3172
    }
    /// Shorthand for `NodeData::create_node(NodeType::Div)`.
    #[inline]
167491
    #[must_use] pub const fn create_div() -> Self {
167491
        Self::create_node(NodeType::Div)
167491
    }
    /// Shorthand for `NodeData::create_node(NodeType::Br)`.
    #[inline]
4
    #[must_use] pub const fn create_br() -> Self {
4
        Self::create_node(NodeType::Br)
4
    }
    /// Creates a RAW text node - read this before using it.
    ///
    /// **WARNING**: azul does NOT auto-wrap raw text in an anonymous block
    /// the way browsers do. A bare text node has NO box of its own: it gets
    /// no rect, no clip, and no layout constraints. Every box-model CSS
    /// property (width/height/position/overflow/background/border/padding),
    /// every callback, every `tab_index` and every `dataset` attached to a
    /// text node is silently INERT. This has broken shipped widgets before
    /// (text escaping its container, click targets that never fire).
    ///
    /// A raw text node is only correct as the bare leaf INSIDE a block-level
    /// wrapper that carries the styling - `p`, `div`, `h1`... Prefer the
    /// `create_*_with_text` family (`Dom::create_p_with_text`,
    /// `Dom::create_div_with_text`, `Dom::create_span_with_text`, ...), which
    /// builds that shape for you. The engine also logs a warning after layout
    /// when it finds a text node used without a containing block.
    ///
    /// Shorthand for `NodeData::create_node(NodeType::Text(value.into()))`.
    #[inline]
51483
    pub fn create_text_do_not_use_without_block_level_wrapper<S: Into<AzString>>(
51483
        value: S,
51483
    ) -> Self {
51483
        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
51483
    }
    /// Shorthand for `NodeData::create_node(NodeType::Image(image_id))`.
    #[inline]
6
    #[must_use] pub fn create_image(image: ImageRef) -> Self {
6
        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
6
    }
    #[inline]
124
    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
124
        let mut nd = Self::create_node(NodeType::VirtualView);
124
        let ext = nd.extra.get_or_insert_with(|| Box::new(NodeDataExt::default()));
124
        ext.virtual_view = Some(VirtualViewNode {
124
            callback: callback.into(),
124
            refany: data,
124
        });
124
        nd
124
    }
    // -- Accessibility-aware NodeData constructors --
    // Each a11y-able element has two constructors: the canonical one takes a
    // `SmallAriaInfo` so the caller must opt in to an accessible name, and the
    // `*_no_a11y` variant is a deliberate escape hatch with a longer name.
358
    fn with_attribute(mut self, attr: AttributeType) -> Self {
358
        let mut v = self.attributes().clone().into_library_owned_vec();
358
        v.push(attr);
358
        self.set_attributes(v.into());
358
        self
358
    }
    /// Creates a button `NodeData` with accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
3
    #[must_use] pub fn create_button(aria: SmallAriaInfo) -> Self {
3
        let mut nd = Self::create_node(NodeType::Button);
3
        nd.set_accessibility_info(aria.to_full_info());
3
        nd
3
    }
    /// Creates a button `NodeData` without accessibility information.
    #[inline]
55
    #[must_use] pub const fn create_button_no_a11y() -> Self {
55
        Self::create_node(NodeType::Button)
55
    }
    /// Creates an anchor `NodeData` with an href and accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2
    #[must_use] pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2
        let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2
        nd.set_accessibility_info(aria.to_full_info());
2
        nd
2
    }
    /// Creates an anchor `NodeData` with an href but no accessibility information.
    #[inline]
3
    #[must_use] pub fn create_a_no_a11y(href: AzString) -> Self {
3
        Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
3
    }
    /// Creates an input `NodeData` with accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2
    #[must_use] pub fn create_input(
2
        input_type: AzString,
2
        name: AzString,
2
        label: AzString,
2
        aria: SmallAriaInfo,
2
    ) -> Self {
2
        let mut nd = Self::create_node(NodeType::Input)
2
            .with_attribute(AttributeType::InputType(input_type))
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label));
2
        nd.set_accessibility_info(aria.to_full_info());
2
        nd
2
    }
    /// Creates an input `NodeData` without accessibility information.
    #[inline]
2
    #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::Input)
2
            .with_attribute(AttributeType::InputType(input_type))
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates a textarea `NodeData` with accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    #[must_use] pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
1
        let mut nd = Self::create_node(NodeType::TextArea)
1
            .with_attribute(AttributeType::Name(name))
1
            .with_attribute(AttributeType::AriaLabel(label));
1
        nd.set_accessibility_info(aria.to_full_info());
1
        nd
1
    }
    /// Creates a textarea `NodeData` without accessibility information.
    #[inline]
2
    #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::TextArea)
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates a select `NodeData` with accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    #[must_use] pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
1
        let mut nd = Self::create_node(NodeType::Select)
1
            .with_attribute(AttributeType::Name(name))
1
            .with_attribute(AttributeType::AriaLabel(label));
1
        nd.set_accessibility_info(aria.to_full_info());
1
        nd
1
    }
    /// Creates a select `NodeData` without accessibility information.
    #[inline]
2
    #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::Select)
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates a table `NodeData` with accessibility information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
2
    #[must_use] pub fn create_table(aria: SmallAriaInfo) -> Self {
2
        let mut nd = Self::create_node(NodeType::Table);
2
        nd.set_accessibility_info(aria.to_full_info());
2
        nd
2
    }
    /// Creates a table `NodeData` without accessibility information.
    #[inline]
2
    #[must_use] pub const fn create_table_no_a11y() -> Self {
2
        Self::create_node(NodeType::Table)
2
    }
    /// Creates a label `NodeData` with an associated control ID and accessibility
    /// information.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    #[must_use] pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
1
        let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
1
            AttributeNameValue {
1
                attr_name: "for".into(),
1
                value: for_id,
1
            },
1
        ));
1
        nd.set_accessibility_info(aria.to_full_info());
1
        nd
1
    }
    /// Creates a label `NodeData` with an associated control ID but no
    /// accessibility information.
    #[inline]
2
    #[must_use] pub fn create_label_no_a11y(for_id: AzString) -> Self {
2
        Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(AttributeNameValue {
2
            attr_name: "for".into(),
2
            value: for_id,
2
        }))
2
    }
    /// Checks whether this node is of the given node type (div, image, text).
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
29
    #[must_use] pub fn is_node_type(&self, searched_type: NodeType) -> bool {
29
        self.node_type == searched_type
29
    }
    /// Checks whether this node has the searched ID attached.
8774
    #[must_use] pub fn has_id(&self, id: &str) -> bool {
8774
        self.attributes()
8774
            .iter()
8782
            .any(|attr| attr.as_id() == Some(id))
8774
    }
    /// Checks whether this node has the searched class attached.
363749
    #[must_use] pub fn has_class(&self, class: &str) -> bool {
363749
        self.attributes()
363749
            .iter()
363757
            .any(|attr| attr.as_class() == Some(class))
363749
    }
1656434
    #[must_use] pub fn has_context_menu(&self) -> bool {
1656434
        self.extra
1656434
            .as_ref()
1656434
            .is_some_and(|m| m.context_menu.is_some())
1656434
    }
9718534
    #[must_use] pub const fn is_text_node(&self) -> bool {
9718534
        matches!(self.node_type, NodeType::Text(_))
9718534
    }
541870
    #[must_use] pub const fn is_virtual_view_node(&self) -> bool {
541870
        matches!(self.node_type, NodeType::VirtualView)
541870
    }
    // NOTE: Getters are used here in order to allow changing the memory allocator for the NodeData
    // in the future (which is why the fields are all private).
    #[inline]
11097378
    #[must_use] pub const fn get_node_type(&self) -> &NodeType {
11097378
        &self.node_type
11097378
    }
    #[inline]
15
    pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
15
        self.extra.as_mut().and_then(|e| e.dataset.as_mut())
15
    }
    #[inline]
1301986
    #[must_use] pub fn get_dataset(&self) -> Option<&RefAny> {
1301986
        self.extra.as_ref().and_then(|e| e.dataset.as_ref())
1301986
    }
    /// Take the dataset out of the node, replacing it with None.
349
    pub fn take_dataset(&mut self) -> Option<RefAny> {
349
        self.extra.as_mut().and_then(|e| e.dataset.take())
349
    }
    /// Returns IDs and classes as a computed `IdOrClassVec`.
    /// Note: this allocates a new vec each time, prefer `has_id()`/`has_class()` for checks.
    #[inline]
20233
    #[must_use] pub fn get_ids_and_classes(&self) -> IdOrClassVec {
20237
        let v: Vec<IdOrClass> = self.attributes().as_ref().iter().filter_map(|attr| {
7442
            match attr {
93
                AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
7345
                AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
4
                _ => None,
            }
20237
        }).collect();
20233
        v.into()
20233
    }
    #[inline]
4787360
    #[must_use] pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
4787360
        &self.callbacks
4787360
    }
    #[inline]
94014
    #[must_use] pub const fn get_style(&self) -> &azul_css::css::Css {
94014
        &self.style
94014
    }
    #[inline]
739447
    #[must_use] pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
739447
        self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
739447
    }
    /// Legacy accessor for raster clip mask. Returns `Some` only for `SvgNodeData::ImageClipMask`.
    #[inline]
4
    #[must_use] pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
4
        match self.get_svg_data()? {
1
            SvgNodeData::ImageClipMask(m) => Some(m),
1
            _ => None,
        }
4
    }
    #[inline]
2352892
    #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2352892
        self.flags.get_tab_index()
2352892
    }
    #[inline]
518909
    #[must_use] pub fn get_accessibility_info(&self) -> Option<&AccessibilityInfo> {
518909
        self.accessibility.as_deref()
518909
    }
    #[inline]
6
    #[must_use] pub fn get_menu_bar(&self) -> Option<&Menu> {
6
        self.extra.as_ref().and_then(|e| e.menu_bar.as_deref())
6
    }
    #[inline]
1656381
    #[must_use] pub fn get_context_menu(&self) -> Option<&Menu> {
1656381
        self.extra.as_ref().and_then(|e| e.context_menu.as_deref())
1656381
    }
    /// Returns whether this node is an anonymous box generated for table layout.
    #[inline]
1045336
    #[must_use] pub const fn is_anonymous(&self) -> bool {
1045336
        self.flags.is_anonymous()
1045336
    }
    #[inline]
1266
    pub fn set_node_type(&mut self, node_type: NodeType) {
1266
        self.node_type = node_type;
1266
    }
    #[inline]
    /// The node's component-attached animation functions (empty for the
    /// ~100% of nodes that have none — no `extra` allocation is made by
    /// reading).
    #[must_use]
20
    pub fn animation_callbacks(&self) -> &[crate::resources::AnimationFunction] {
20
        self.extra
20
            .as_ref()
20
            .map_or(&[], |e| e.animation_callbacks.as_slice())
20
    }
    /// Attach a presence-animation function under `name`, resolvable from
    /// this node's `-azul-animation-in` / `-azul-animation-out` after
    /// stylesheet `@keyframes`. `callback` is the TYPE-ERASED
    /// `azul_layout::callbacks::ZombieAnimFnType` fn pointer cast to
    /// `usize` (the type-erased `ZombieAnimCallback.cb`).
11
    pub fn add_animation_callback(
11
        &mut self,
11
        name: AzString,
11
        callback: crate::resources::ZombieAnimCallback,
11
        data: RefAny,
11
    ) {
11
        self.extra
11
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
            .animation_callbacks
11
            .push(crate::resources::AnimationFunction {
11
                name,
11
                callback,
11
                data,
11
            });
11
    }
22140
    pub fn set_dataset(&mut self, data: OptionRefAny) {
22140
        match data {
            OptionRefAny::None => {
2
                if let Some(ext) = self.extra.as_mut() {
1
                    ext.dataset = None;
1
                }
            }
22138
            OptionRefAny::Some(r) => {
22138
                self.extra
22138
                    .get_or_insert_with(|| Box::new(NodeDataExt::default()))
22138
                    .dataset = Some(r);
            }
        }
22140
    }
    /// Sets the IDs and classes by converting `IdOrClassVec` entries into
    /// `AttributeType::Id`/`AttributeType::Class` and merging them into `self.attributes`.
    /// Any existing Id/Class attributes are removed first.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
707863
    pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
        // Remove existing Id/Class from attributes
707863
        let mut v: AttributeTypeVec = Vec::new().into();
707863
        mem::swap(&mut v, self.attributes_mut());
707863
        let mut v = v.into_library_owned_vec();
707864
        v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
        // Convert and append
738417
        for ioc in ids_and_classes.as_ref() {
738416
            match ioc {
2048
                IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
736368
                IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
            }
        }
707863
        self.set_attributes(v.into());
707863
    }
    #[inline]
1
    pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
1
        self.callbacks = callbacks;
1
    }
    /// Legacy: replace this node's inline style with a flat list of property+conditions.
    /// Each entry becomes a single-declaration rule at `rule_priority::INLINE`. Prefer
    /// `set_style` (or `with_style` / `with_css(&str)`) for new code.
    #[inline]
387
    pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
387
        self.style = css_props.into();
387
    }
    /// Upsert one runtime-patched CSS property into this node's inline style.
    ///
    /// Every UNCONDITIONAL inline declaration of the same property type is
    /// removed (a patch replaces the property's resting value), then the new
    /// value is appended as its own unconditional rule at
    /// `rule_priority::INLINE`. Conditional declarations (`:hover` styles,
    /// `@media` rules) are left untouched: a runtime patch changes the
    /// property's base value, not the node's whole style.
    ///
    /// The content chokepoint used to `set_css_props(vec![patch])` here,
    /// which REPLACED the entire inline style — a gallery panel patched to
    /// `display: flex` lost its `position: absolute; top: ...` and flowed
    /// into the row, off-window. Remove-then-append also keeps repeated
    /// toggles from growing the style without bound.
1370
    pub fn upsert_inline_css_property(&mut self, prop: azul_css::props::property::CssProperty) {
        use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
1370
        let ty = prop.get_type();
1370
        let mut rules = mem::take(&mut self.style.rules).into_library_owned_vec();
11014
        for rule in &mut rules {
9644
            if !rule.conditions.as_ref().is_empty() {
1330
                continue;
8314
            }
8314
            let mut decls = mem::take(&mut rule.declarations).into_library_owned_vec();
8314
            decls.retain(|d| match d {
8314
                CssDeclaration::Static(p) => p.get_type() != ty,
                CssDeclaration::Dynamic(_) => true,
8314
            });
8314
            rule.declarations = decls.into();
        }
        // Drop rules the retain above emptied out entirely.
9651
        rules.retain(|r| !r.declarations.as_ref().is_empty());
1370
        rules.push(CssRuleBlock {
1370
            path: CssPath {
1370
                selectors: Vec::new().into(),
1370
            },
1370
            declarations: alloc::vec![CssDeclaration::Static(prop)].into(),
1370
            conditions: Vec::new().into(),
1370
            priority: rule_priority::INLINE,
1370
        });
1370
        self.style.rules = rules.into();
1370
    }
    /// Replace this node's inline style with a `Css` value. The Css's rules apply only
    /// to this node (implicit `:scope`).
    #[inline]
1183
    pub fn set_style(&mut self, style: azul_css::css::Css) {
1183
        self.style = style;
1183
    }
    #[inline]
1
    pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
1
        self.extra
1
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
1
            .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
1
    }
    #[inline]
266
    pub fn set_svg_data(&mut self, data: SvgNodeData) {
266
        self.extra
266
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
266
            .svg_data = Some(data);
266
    }
    #[inline]
287897
    pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
287897
        self.flags.set_tab_index(Some(tab_index));
287897
    }
    #[inline]
4701
    pub const fn set_contenteditable(&mut self, contenteditable: bool) {
4701
        self.flags.set_contenteditable_mut(contenteditable);
4701
    }
    #[inline]
3180793
    #[must_use] pub const fn is_contenteditable(&self) -> bool {
3180793
        self.flags.is_contenteditable()
3180793
    }
    #[inline]
856
    pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
856
        self.accessibility = Some(Box::new(accessibility_info));
856
    }
    /// Marks this node as an anonymous box (generated for table layout).
    #[inline]
11
    pub const fn set_anonymous(&mut self, is_anonymous: bool) {
11
        self.flags.set_anonymous(is_anonymous);
11
    }
    #[inline]
4
    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
4
        self.extra
4
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
4
            .menu_bar = Some(Box::new(menu_bar));
4
    }
    #[inline]
181
    pub fn set_context_menu(&mut self, context_menu: Menu) {
181
        self.extra
181
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
181
            .context_menu = Some(Box::new(context_menu));
181
    }
    /// Sets a stable key for this node used in reconciliation.
    ///
    /// This key is used to track node identity across DOM updates, enabling
    /// the framework to distinguish between "moving" a node and "destroying/creating" one.
    /// This is crucial for correct lifecycle events when lists are reordered.
    ///
    /// # Example
    /// ```rust
    /// # use azul_core::dom::NodeData;
    /// # let mut node_data = NodeData::create_div();
    /// node_data.set_key("user-123");
    /// ```
    #[inline]
49
    pub fn set_key<K: Hash>(&mut self, key: K) {
        use core::hash::Hasher;
49
        let mut hasher = crate::hash::DefaultHasher::new();
49
        key.hash(&mut hasher);
49
        self.extra
49
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
49
            .key = Some(hasher.finish());
49
    }
    /// Gets the key for this node, if set.
    #[inline]
2281165
    #[must_use] pub fn get_key(&self) -> Option<u64> {
2281165
        self.extra.as_ref().and_then(|ext| ext.key)
2281165
    }
    /// Sets a dataset merge callback for this node.
    ///
    /// The merge callback is invoked during reconciliation when a node from the
    /// previous frame is matched with a node in the new frame. It allows heavy
    /// resources (video decoders, GL textures, network connections) to be
    /// transferred from the old node to the new node instead of being destroyed.
    ///
    /// # Type Safety
    ///
    /// The callback stores the `TypeId` of `T`. During execution, both the old
    /// and new datasets must match this type, otherwise the merge is skipped.
    ///
    /// # Example
    /// ```rust,ignore
    /// struct VideoPlayer {
    ///     url: String,
    ///     decoder: Option<DecoderHandle>,
    /// }
    ///
    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
    ///     // Transfer the heavy decoder handle from old to new
    ///     if let (Some(mut new), Some(old)) = (
    ///         new_data.downcast_mut::<VideoPlayer>(),
    ///         old_data.downcast_ref::<VideoPlayer>()
    ///     ) {
    ///         new.decoder = old.decoder.take();
    ///     }
    ///     new_data
    /// }
    ///
    /// node_data.set_merge_callback(merge_video);
    /// ```
    #[inline]
1358
    pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
1358
        self.extra
1358
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
1358
            .dataset_merge_callback = Some(callback.into());
1358
    }
    /// Gets the merge callback for this node, if set.
    #[inline]
513
    #[must_use] pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
513
        self.extra.as_ref().and_then(|ext| ext.dataset_merge_callback.clone())
513
    }
    /// Sets the component origin for this node.
    ///
    /// This stamps the node with information about which component rendered it,
    /// enabling the debugger to reconstruct the component invocation tree.
    #[inline]
1
    pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
1
        self.extra
1
            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
1
            .component_origin = Some(origin);
1
    }
    /// Gets the component origin for this node, if set.
    #[inline]
3
    #[must_use] pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
3
        self.extra.as_ref().and_then(|ext| ext.component_origin.as_ref())
3
    }
    #[inline]
1
    #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
1
        self.set_menu_bar(menu_bar);
1
        self
1
    }
    #[inline]
1
    #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
1
        self.set_context_menu(context_menu);
1
        self
1
    }
    #[inline]
1665
    pub fn add_callback<C: Into<CoreCallback>>(
1665
        &mut self,
1665
        event: EventFilter,
1665
        data: RefAny,
1665
        callback: C,
1665
    ) {
1665
        let callback = callback.into();
1665
        let mut v: CoreCallbackDataVec = Vec::new().into();
1665
        mem::swap(&mut v, &mut self.callbacks);
1665
        let mut v = v.into_library_owned_vec();
1665
        v.push(CoreCallbackData {
1665
            event,
1665
            refany: data,
1665
            callback,
1665
        });
1665
        self.callbacks = v.into();
1665
    }
    #[inline]
1083
    pub fn add_id(&mut self, s: AzString) {
1083
        let mut v: AttributeTypeVec = Vec::new().into();
1083
        mem::swap(&mut v, self.attributes_mut());
1083
        let mut v = v.into_library_owned_vec();
1083
        v.push(AttributeType::Id(s));
1083
        self.set_attributes(v.into());
1083
    }
    #[inline]
1059
    pub fn add_class(&mut self, s: AzString) {
1059
        let mut v: AttributeTypeVec = Vec::new().into();
1059
        mem::swap(&mut v, self.attributes_mut());
1059
        let mut v = v.into_library_owned_vec();
1059
        v.push(AttributeType::Class(s));
1059
        self.set_attributes(v.into());
1059
    }
    /// Add a CSS property with optional conditions (hover, focus, active, etc.).
    ///
    /// Wraps the property in a single-declaration rule at `rule_priority::INLINE`
    /// and appends it to this node's inline style.
    #[inline]
202
    pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
        use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
202
        let rule = CssRuleBlock {
202
            path: CssPath { selectors: Vec::new().into() },
202
            declarations: vec![CssDeclaration::Static(p.property)].into(),
202
            conditions: p.apply_if,
202
            priority: rule_priority::INLINE,
202
        };
202
        let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
202
        mem::swap(&mut v, &mut self.style.rules);
202
        let mut v = v.into_library_owned_vec();
202
        v.push(rule);
202
        self.style.rules = v.into();
202
    }
    /// Calculates a deterministic node hash for this node.
74041
    #[must_use] pub fn calculate_node_data_hash(&self) -> DomNodeHash {
        use core::hash::Hasher;
74041
        let mut hasher = crate::hash::DefaultHasher::new();
74041
        self.hash(&mut hasher);
74041
        let h = hasher.finish();
74041
        DomNodeHash { inner: h }
74041
    }
    /// Calculates a structural hash for DOM reconciliation that ignores text content.
    ///
    /// This hash is used for matching nodes across DOM frames where the text content
    /// may have changed (e.g., contenteditable text being edited). It hashes:
    /// - Node type discriminant (but NOT the text content for Text nodes)
    /// - IDs and classes
    /// - Attributes (but NOT contenteditable state which may change with focus)
    /// - Callback events and types
    ///
    /// This allows a Text("Hello") node to match Text("Hello World") during reconciliation,
    /// preserving cursor position and selection state.
28425
    #[must_use] pub fn calculate_structural_hash(&self) -> DomNodeHash {
        use core::hash::Hasher;
        use core::hash::Hasher as StdHasher;
28425
        let mut hasher = crate::hash::DefaultHasher::new();
        // Hash node type discriminant only, not content
        // This means Text("A") and Text("B") have the same structural hash
28425
        mem::discriminant(&self.node_type).hash(&mut hasher);
        // For VirtualView nodes, hash the callback to distinguish different virtualized views
28425
        if self.node_type == NodeType::VirtualView {
            if let Some(ext) = self.extra.as_ref() {
                if let Some(vv) = ext.virtual_view.as_ref() {
                    vv.hash(&mut hasher);
                }
            }
28425
        }
        // For Image nodes, hash the image reference to distinguish different images.
        // For callback images, hash the callback function pointer and RefAny type ID
        // instead of the heap pointer, so that the same callback produces the same
        // structural hash across frames (the heap pointer differs each frame because
        // ImageRef::new() does Box::into_raw(Box::new(...))).
28425
        if let NodeType::Image(ref img_ref) = self.node_type {
1
            match img_ref.get_data() {
                crate::resources::DecodedImage::Callback(cb) => {
                    // Hash callback function pointer (stable across frames)
                    cb.callback.cb.hash(&mut hasher);
                    // Hash RefAny type ID (not instance pointer)
                    cb.refany.get_type_id().hash(&mut hasher);
                }
1
                _ => {
1
                    // Raw images / GL textures: hash normally (pointer identity)
1
                    img_ref.hash(&mut hasher);
1
                }
            }
28424
        }
        // Hash IDs and classes - these are structural and shouldn't change
        // (They are now stored as AttributeType::Id / AttributeType::Class in attributes)
28425
        for attr in self.attributes().as_ref() {
22009
            match attr {
19498
                AttributeType::Id(s) => { 0u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2511
                AttributeType::Class(s) => { 1u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
                _ => {}
            }
        }
        // Hash other attributes - but skip contenteditable since that might change
        // Also skip Id/Class since they were already hashed above
28425
        for attr in self.attributes().as_ref() {
22009
            if !matches!(attr, AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)) {
                attr.hash(&mut hasher);
22009
            }
        }
        // Hash callback events (not the actual callback function pointers)
28425
        for callback in self.callbacks.as_ref() {
235
            callback.event.hash(&mut hasher);
235
        }
28425
        let h = hasher.finish();
28425
        DomNodeHash { inner: h }
28425
    }
    #[inline]
7
    #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
7
        self.set_tab_index(tab_index);
7
        self
7
    }
    #[inline]
8
    #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
8
        self.set_contenteditable(contenteditable);
8
        self
8
    }
    #[inline]
1
    #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
1
        self.set_node_type(node_type);
1
        self
1
    }
    #[inline]
    #[must_use]
6
    pub fn with_callback<C: Into<CoreCallback>>(
6
        mut self,
6
        event: EventFilter,
6
        data: RefAny,
6
        callback: C,
6
    ) -> Self {
6
        self.add_callback(event, data, callback);
6
        self
6
    }
    #[inline]
    #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
        self.set_dataset(data);
        self
    }
    #[inline]
24
    #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
24
        self.set_ids_and_classes(ids_and_classes);
24
        self
24
    }
    #[inline]
    #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
        self.callbacks = callbacks;
        self
    }
    /// Legacy: builder-form of `set_css_props`. Each `CssPropertyWithConditions`
    /// becomes a single-declaration rule at `rule_priority::INLINE`.
    /// Prefer `with_style(Css)` for new code.
    #[inline]
    #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
        self.style = css_props.into();
        self
    }
    /// Builder-form of `set_style`.
    #[inline]
    #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
        self.style = style;
        self
    }
    /// Assigns a stable key to this node for reconciliation.
    ///
    /// This is crucial for performance and correct state preservation when
    /// lists of items change order or items are inserted/removed. Without keys,
    /// the reconciliation algorithm falls back to hash-based matching.
    ///
    /// # Example
    /// ```rust
    /// # use azul_core::dom::NodeData;
    /// NodeData::create_div()
    ///     .with_key("user-avatar-123");
    /// ```
    #[inline]
    #[must_use]
26
    pub fn with_key<K: Hash>(mut self, key: K) -> Self {
26
        self.set_key(key);
26
        self
26
    }
    /// Registers a callback to merge dataset state from the previous frame.
    ///
    /// This is used for components that maintain heavy internal state (video players,
    /// WebGL contexts, network connections) that should not be destroyed and recreated
    /// on every render frame.
    ///
    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
    /// returns the `RefAny` that should be used for the new node.
    ///
    /// # Example
    /// ```rust,ignore
    /// struct VideoPlayer {
    ///     url: String,
    ///     decoder_handle: Option<DecoderHandle>,
    /// }
    ///
    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
    ///     if let (Some(mut new), Some(old)) = (
    ///         new_data.downcast_mut::<VideoPlayer>(),
    ///         old_data.downcast_ref::<VideoPlayer>()
    ///     ) {
    ///         new.decoder_handle = old.decoder_handle.take();
    ///     }
    ///     new_data
    /// }
    ///
    /// NodeData::create_div()
    ///     .with_dataset(RefAny::new(VideoPlayer::new("movie.mp4")).into())
    ///     .with_merge_callback(merge_video)
    /// ```
    #[inline]
    #[must_use]
    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
        self.set_merge_callback(callback);
        self
    }
    /// Parse and set CSS styles with full selector support.
    ///
    /// This is the unified API for setting inline CSS on a node. It supports:
    /// - Simple properties: `color: red; font-size: 14px;`
    /// - Pseudo-selectors: `:hover { background: blue; }`
    /// - @-rules: `@os linux { font-size: 14px; }`
    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
    ///
    /// # Examples
    /// ```rust
    /// # use azul_core::dom::NodeData;
    /// NodeData::create_div().with_css("
    ///     color: blue;
    ///     :hover { color: red; }
    ///     @os linux { font-size: 14px; }
    /// ");
    /// ```
290
    pub fn set_css(&mut self, style: &str) {
        // Parse via Css::parse_inline so the inline path goes through the same
        // selector + nesting machinery as author CSS. Rules are tagged
        // `rule_priority::INLINE` and appended to whatever this node already has.
290
        let parsed = azul_css::css::Css::parse_inline(style);
290
        let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
290
        mem::swap(&mut current, &mut self.style.rules);
290
        let mut v = current.into_library_owned_vec();
290
        v.extend(parsed.rules.into_library_owned_vec());
290
        self.style.rules = v.into();
290
    }
    /// Builder method for `set_css`
275
    #[must_use] pub fn with_css(mut self, style: &str) -> Self {
275
        self.set_css(style);
275
        self
275
    }
    #[inline]
    #[must_use]
1
    pub const fn swap_with_default(&mut self) -> Self {
1
        let mut s = Self::create_div();
1
        mem::swap(&mut s, self);
1
        s
1
    }
    #[inline]
599917
    #[must_use] pub fn copy_special(&self) -> Self {
599917
        Self {
599917
            node_type: self.node_type.to_library_owned_nodetype(),
599917
            style: self.style.clone(),
599917
            callbacks: self.callbacks.clone(),
599917
            flags: self.flags,
599917
            accessibility: self.accessibility.clone(),
599917
            extra: self.extra.clone(),
599917
        }
599917
    }
    /// Like [`copy_special`], but MOVES the inline `style` and the `extra` (`NodeDataExt`)
    /// box out of `self` into the returned copy instead of cloning them.
    ///
    /// Both the derived `Clone` for the `CssProperty` values inside `style` AND the derived
    /// `Clone` for `Box<NodeDataExt>` (which transitively clones an `AttributeTypeVec` of
    /// `AzString`s, menus, etc.) lower to indirect-jump jump tables that remill mis-lifts on
    /// the web backend: the mis-lifted clone reads/writes wrong-sized data, which on the
    /// stack clobbers the adjacent `style` temporary inside `copy_special` and produces a
    /// "memory access out of bounds" later in the cascade (`StyledDom::create` → `restyle`'s
    /// inheritance loop reads the corrupted `style`). Native builds are unaffected.
    ///
    /// `convert_dom_into_compact_dom` consumes the `Dom`, so moving these fields out is sound:
    /// `copy_special` then clones an EMPTY style + `None` extra (no broken clone runs), and we
    /// restore the moved-out values afterward. Mirrors the pre-existing `style`-only fix.
599916
    pub(crate) fn copy_special_moving_complex(&mut self) -> Self {
        // WEB-LIFT (2026-06-03): `copy_special`'s `to_library_owned_nodetype()` RECONSTRUCTS the
        // node_type (Text/Image arms clone the boxed AzString + rebuild the variant); the lifted
        // sret store of that data-bearing variant DROPS the whole thing (disc 177->0 AND the box
        // ptr -> styled_dom text node_type = all-zero, box LOST). Earlier attempts to fix this
        // "trapped" — but that was the missing `-C target-feature=-lse` build flag (LSE atomics
        // remill can't lift), NOT this code. With -lse + the fork remill, MOVE the node_type out
        // bitwise instead of reconstructing it: transfers the ORIGINAL box (preserving disc + the
        // AzString) with no clone. The Dom is consumed by convert_dom_into_compact_dom so moving is
        // sound; self.node_type becomes Div (no heap) -> dropped trivially. ptr::write avoids
        // dropping copy's placeholder Div (whose auto-Drop disc-match could mis-lift).
599916
        let taken_style = mem::take(&mut self.style);
599916
        let taken_extra = self.extra.take();
599916
        let taken_node_type = mem::replace(&mut self.node_type, NodeType::Div);
599916
        let mut copy = self.copy_special();
        // SAFETY: `&raw mut copy.node_type` is aligned and points at an initialized
        // `NodeType` (the placeholder `Div` that `copy_special` reconstructed from
        // `self.node_type`, which we replaced with `NodeType::Div` above). `ptr::write`
        // overwrites it WITHOUT running its `Drop` — this is deliberate (the Drop
        // mis-lifts on the web backend) and leaks nothing, because the overwritten
        // value is a heap-free `Div`. Kept unsafe (not a plain `=` assignment)
        // specifically to skip that Drop.
599916
        unsafe { core::ptr::write(&raw mut copy.node_type, taken_node_type); }
599916
        copy.style = taken_style;
599916
        copy.extra = taken_extra;
599916
        copy
599916
    }
528305
    #[must_use] pub fn is_focusable(&self) -> bool {
        // Inherently focusable elements per HTML spec
528305
        if matches!(self.node_type,
            NodeType::A | NodeType::Button | NodeType::Input
            | NodeType::Select | NodeType::TextArea
        ) {
37241
            return true;
491064
        }
        // Contenteditable elements are implicitly focusable (W3C spec)
491064
        if self.is_contenteditable() {
1490
            return true;
489574
        }
        // Element is focusable if it has a tab index or any focus-related callback
489574
        self.get_tab_index().is_some()
488450
            || self
488450
                .get_callbacks()
488450
                .iter()
488450
                .any(|cb| cb.event.is_focus_callback())
528305
    }
    /// Returns true if this element has "activation behavior" per HTML5 spec.
    ///
    /// Elements with activation behavior can be activated via Enter or Space key
    /// when focused, which generates a synthetic click event.
    ///
    /// Per HTML5 spec, elements with activation behavior include:
    /// - Button elements
    /// - Input elements (submit, button, reset, checkbox, radio)
    /// - Anchor elements with href
    /// - Any element with a click callback (implicit activation)
    ///
    /// See: <https://html.spec.whatwg.org/multipage/interaction.html#activation-behavior>
247491
    #[must_use] pub fn has_activation_behavior(&self) -> bool {
        use crate::events::{EventFilter, HoverEventFilter};
        // Inherently activatable elements per HTML spec
247491
        if matches!(self.node_type, NodeType::A | NodeType::Button) {
20192
            return true;
227299
        }
        // Check for click callback (most common case for Azul)
        // In Azul, "click" is typically LeftMouseUp
227299
        let has_click_callback = self
227299
            .get_callbacks()
227299
            .iter()
227299
            .any(|cb| matches!(
38052
                cb.event,
                EventFilter::Hover(HoverEventFilter::MouseUp | HoverEventFilter::LeftMouseUp)
            ));
227299
        if has_click_callback {
6558
            return true;
220741
        }
        // Check accessibility role for button-like elements
220741
        if let Some(ref accessibility) = self.accessibility {
            use crate::a11y::AccessibilityRole;
749
            match accessibility.role {
                AccessibilityRole::PushButton  // Button
                | AccessibilityRole::Link
                | AccessibilityRole::CheckButton  // Checkbox
                | AccessibilityRole::RadioButton  // Radio
                | AccessibilityRole::MenuItem
                | AccessibilityRole::PageTab  // Tab
749
                => return true,
                _ => {}
            }
219992
        }
219992
        false
247491
    }
    /// Returns true if this element is currently activatable.
    ///
    /// An element is activatable if it has activation behavior AND is not disabled.
    /// This checks for common disability patterns (aria-disabled, disabled attribute).
4646
    #[must_use] pub fn is_activatable(&self) -> bool {
4646
        if !self.has_activation_behavior() {
2124
            return false;
2522
        }
        // Check for disabled state in accessibility info
2522
        if let Some(ref accessibility) = self.accessibility {
            // Check if explicitly marked as unavailable
738
            if accessibility
738
                .states
738
                .as_ref()
738
                .iter()
738
                .any(|s| matches!(s, AccessibilityState::Unavailable))
            {
705
                return false;
33
            }
1784
        }
        // Not disabled, so activatable
1817
        true
4646
    }
    /// Returns the tab index for this element.
    ///
    /// Tab index determines keyboard navigation order:
    /// - `None`: Not in tab order (unless naturally focusable)
    /// - `Some(-1)`: Focusable programmatically but not via Tab
    /// - `Some(0)`: In natural tab order
    /// - `Some(n > 0)`: In tab order with priority n (higher = later)
5
    #[must_use] pub fn get_effective_tabindex(&self) -> Option<i32> {
5
        self.flags.get_tab_index().map_or_else(|| if self.get_callbacks().iter().any(|cb| cb.event.is_focus_callback()) {
1
                    Some(0)
                } else {
1
                    None
3
                }, |tab_idx| match tab_idx {
1
                    TabIndex::Auto => Some(0),
1
                    TabIndex::OverrideInParent(n) => Some(i32::try_from(n).unwrap_or(i32::MAX)),
1
                    TabIndex::NoKeyboardFocus => Some(-1),
3
                })
5
    }
    /// Returns the accessible label for this node.
    ///
    /// Priority: `aria-label` attribute > `alt` attribute > `title` attribute > None.
    /// Does NOT include child text — the caller should collect that separately
    /// using the DOM hierarchy.
242840
    #[must_use] pub fn get_accessible_label(&self) -> Option<&str> {
242843
        for attr in self.attributes().as_ref() {
104640
            if let AttributeType::AriaLabel(s) = attr { return Some(s.as_str()) }
        }
242816
        for attr in self.attributes().as_ref() {
104612
            match attr {
2
                AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
104610
                _ => {}
            }
        }
242814
        None
242840
    }
    /// Returns the accessible value for this node.
    ///
    /// Priority: `value` attribute > None.
    /// For text inputs, this is the input's current value.
242837
    #[must_use] pub fn get_accessible_value(&self) -> Option<&str> {
242837
        for attr in self.attributes().as_ref() {
104677
            if let AttributeType::Value(s) = attr {
23
                return Some(s.as_str());
104654
            }
        }
242814
        None
242837
    }
    /// Returns the placeholder text for this node.
2
    #[must_use] pub fn get_placeholder(&self) -> Option<&str> {
3
        for attr in self.attributes().as_ref() {
3
            if let AttributeType::Placeholder(s) = attr {
1
                return Some(s.as_str());
2
            }
        }
1
        None
2
    }
211
    pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
211
        self.extra.as_mut()?.virtual_view.as_mut()
211
    }
232
    #[must_use] pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
232
        self.extra.as_ref()?.virtual_view.as_ref()
232
    }
    pub fn get_render_image_callback_node(
        &mut self,
    ) -> Option<(&mut CoreImageCallback, ImageRefHash)> {
        match &mut self.node_type {
            NodeType::Image(ref mut img) => {
                let hash = image_ref_get_hash(img.as_ref());
                img.as_mut().get_image_callback_mut().map(|r| (r, hash))
            }
            _ => None,
        }
    }
331
    pub fn debug_print_start(
331
        &self,
331
        css_cache: &CssPropertyCache,
331
        node_id: &NodeId,
331
        node_state: &StyledNodeState,
331
    ) -> String {
331
        let html_type = self.node_type.get_path();
331
        let attributes_string = node_data_to_string(self);
331
        let style = css_cache.get_computed_css_style_string(self, node_id, node_state);
331
        format!(
331
            "<{} data-az-node-id=\"{}\" {} {style}>",
            html_type,
331
            node_id.index(),
            attributes_string,
331
            style = if style.trim().is_empty() {
                String::new()
            } else {
331
                format!("style=\"{style}\"")
            }
        )
331
    }
332
    #[must_use] pub fn debug_print_end(&self) -> String {
332
        let html_type = self.node_type.get_path();
332
        format!("</{html_type}>")
332
    }
}
impl crate::events::ActivationBehavior for NodeData {
2
    fn has_activation_behavior(&self) -> bool {
2
        Self::has_activation_behavior(self)
2
    }
1
    fn is_activatable(&self) -> bool {
1
        Self::is_activatable(self)
1
    }
}
impl crate::events::Focusable for NodeData {
    fn get_tabindex(&self) -> Option<i32> {
        self.get_effective_tabindex()
    }
1
    fn is_focusable(&self) -> bool {
1
        Self::is_focusable(self)
1
    }
3
    fn is_naturally_focusable(&self) -> bool {
1
        matches!(
3
            self.node_type,
            NodeType::A
                | NodeType::Button
                | NodeType::Input
                | NodeType::Select
                | NodeType::TextArea
        )
3
    }
}
/// A unique, runtime-generated identifier for a single `Dom` instance.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub struct DomId {
    pub inner: usize,
}
impl fmt::Display for DomId {
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2
        write!(f, "{}", self.inner)
2
    }
}
impl DomId {
    pub const ROOT_ID: Self = Self { inner: 0 };
}
impl Default for DomId {
1
    fn default() -> Self {
1
        Self::ROOT_ID
1
    }
}
impl_option!(
    DomId,
    OptionDomId,
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(DomId, DomIdVec, DomIdVecDestructor, DomIdVecDestructorType, DomIdVecSlice, OptionDomId);
impl_vec_debug!(DomId, DomIdVec);
impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
impl_vec_partialeq!(DomId, DomIdVec);
impl_vec_partialord!(DomId, DomIdVec);
/// A UUID for a DOM node within a `LayoutWindow`.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct DomNodeId {
    /// The ID of the `Dom` this node belongs to.
    pub dom: DomId,
    /// The hierarchical ID of the node within its `Dom`.
    pub node: NodeHierarchyItemId,
}
impl_option!(
    DomNodeId,
    OptionDomNodeId,
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl DomNodeId {
    pub const ROOT: Self = Self {
        dom: DomId::ROOT_ID,
        node: NodeHierarchyItemId::NONE,
    };
}
/// The document model, similar to HTML. This is a create-only structure, you don't actually read
/// anything back from it. It's designed for ease of construction.
///
/// This is the "slow" tree-based DOM. For bulk construction (XML parsing),
/// use `FastDom` which builds flat arenas directly and skips the tree→arena conversion.
#[repr(C)]
#[derive(PartialEq, Clone)]
pub struct Dom {
    /// The data for the root node of this DOM (or sub-DOM).
    pub root: NodeData,
    /// The children of this DOM node.
    pub children: DomVec,
    /// Ordered list of CSS stylesheets to apply to this DOM subtree.
    /// Stylesheets are applied in push order during the single deferred cascade pass.
    /// Later entries override earlier ones (higher cascade priority).
    pub css: azul_css::css::CssVec,
    // Tracks the number of sub-children of the current children, so that
    // the `Dom` can be converted into a `CompactDom`.
    //
    // AUDIT: this is a cached count that MUST equal the recursive
    // `1-per-descendant` total of `children`. The builder methods
    // (`add_child` / `set_children` / `with_child*` / `FromIterator`) keep it in
    // sync, but `children` is a public field — mutating it directly desyncs this
    // counter. A too-small value makes `convert_dom_into_compact_dom` under-allocate
    // its arenas and panic on out-of-bounds writes. Call
    // `fixup_children_estimated()` after any direct `children` mutation;
    // `StyledDom::new` already does so as a safety net. Debug builds assert
    // consistency in the builder methods (see `recompute_estimated_total_children`).
    pub estimated_total_children: usize,
}
/// CSS stylesheet associated with a specific node ID in the flat arena.
///
/// In the tree DOM, each node carries its own `css` field. In the flat arena,
/// we record which node a stylesheet scopes to (e.g. for `<style>` tags
/// in different parts of the document).
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub struct CssWithNodeId {
    /// 1-based encoded `NodeId` (0 = root / global scope).
    pub node_id: usize,
    /// The CSS stylesheet.
    pub css: azul_css::css::Css,
}
impl_vec!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor, CssWithNodeIdVecDestructorType, CssWithNodeIdVecSlice, OptionCssWithNodeId);
impl_option!(CssWithNodeId, OptionCssWithNodeId, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd]);
impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
/// Arena-based DOM for bulk construction (e.g. XML/XHTML parsing).
/// The hierarchy and node data are stored in two parallel flat vectors,
/// skipping the tree→arena conversion step entirely.
///
/// Use `FastDom::into_dom()` to convert to a tree-based `Dom` if needed.
/// `StyledDom::create_from_fast_dom()` consumes this directly without conversion.
#[repr(C)]
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct FastDom {
    /// Flat arena of parent/child/sibling relationships.
    pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
    /// Flat arena of node data, parallel to `node_hierarchy`.
    pub node_data: NodeDataVec,
    /// CSS stylesheets with the node ID they scope to.
    pub css: CssWithNodeIdVec,
}
// Manual Eq/Hash/Ord impls that skip the transient `css` field,
// since CssVec does not implement Eq/Hash/Ord.
impl Eq for Dom {}
impl Hash for Dom {
8
    fn hash<H: Hasher>(&self, state: &mut H) {
8
        self.root.hash(state);
8
        self.children.hash(state);
8
        self.estimated_total_children.hash(state);
8
    }
}
// PartialOrd delegates to the field-wise Ord so the two never diverge.
impl PartialOrd for Dom {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for Dom {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.root.cmp(&other.root)
            .then_with(|| self.children.cmp(&other.children))
            .then_with(|| self.estimated_total_children.cmp(&other.estimated_total_children))
    }
}
impl_option!(
    Dom,
    OptionDom,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(Dom, DomVec, DomVecDestructor, DomVecDestructorType, DomVecSlice, OptionDom);
impl_vec_clone!(Dom, DomVec, DomVecDestructor);
impl_vec_mut!(Dom, DomVec);
impl_vec_debug!(Dom, DomVec);
impl_vec_partialord!(Dom, DomVec);
impl_vec_ord!(Dom, DomVec);
impl_vec_partialeq!(Dom, DomVec);
impl_vec_eq!(Dom, DomVec);
impl_vec_hash!(Dom, DomVec);
/// An empty `<body>` DOM. Used as the safe fallback return value when a layout
/// callback cannot produce a DOM (e.g. a foreign-language binding's trampoline
/// raised, or an app-data downcast failed). `StyledDom` is the post-cascade
/// CSSOM; layout callbacks return an un-cascaded `Dom`, so an empty body is the
/// natural "nothing to show" default.
impl Default for Dom {
971
    fn default() -> Self {
971
        Self::create_body()
971
    }
}
impl Dom {
    // ----- DOM CONSTRUCTORS
    /// Creates an empty DOM with a give `NodeType`. Note: This is a `const fn` and
    /// doesn't allocate, it only allocates once you add at least one child node.
    #[inline]
624049
    #[must_use] pub fn create_node(node_type: NodeType) -> Self {
624049
        Self {
624049
            root: NodeData::create_node(node_type),
624049
            children: Vec::new().into(),
624049
            css: Vec::new().into(),
624049
            estimated_total_children: 0,
624049
        }
624049
    }
    #[inline]
2571
    #[must_use] pub fn create_from_data(node_data: NodeData) -> Self {
2571
        Self {
2571
            root: node_data,
2571
            children: Vec::new().into(),
2571
            css: Vec::new().into(),
2571
            estimated_total_children: 0,
2571
        }
2571
    }
    // Document Structure Elements
    /// Creates the root HTML element.
    ///
    /// **Accessibility**: The `<html>` element is the root of an HTML document and should have a
    /// `lang` attribute.
    #[inline]
773
    #[must_use] pub const fn create_html() -> Self {
773
        Self {
773
            root: NodeData::create_node(NodeType::Html),
773
            children: DomVec::from_const_slice(&[]),
773
            css: azul_css::css::CssVec::from_const_slice(&[]),
773
            estimated_total_children: 0,
773
        }
773
    }
    /// Creates the document head element.
    ///
    /// **Accessibility**: The `<head>` contains metadata. Use `<title>` for page titles.
    #[inline]
1
    #[must_use] pub const fn create_head() -> Self {
1
        Self {
1
            root: NodeData::create_node(NodeType::Head),
1
            children: DomVec::from_const_slice(&[]),
1
            css: azul_css::css::CssVec::from_const_slice(&[]),
1
            estimated_total_children: 0,
1
        }
1
    }
    #[inline]
47802
    #[must_use] pub const fn create_body() -> Self {
47802
        Self {
47802
            root: NodeData::create_node(NodeType::Body),
47802
            children: DomVec::from_const_slice(&[]),
47802
            css: azul_css::css::CssVec::from_const_slice(&[]),
47802
            estimated_total_children: 0,
47802
        }
47802
    }
    /// Creates a generic block-level container.
    ///
    /// **Accessibility**: Prefer semantic elements like `<article>`, `<section>`, `<nav>` when
    /// applicable.
    #[inline]
916883
    #[must_use] pub const fn create_div() -> Self {
916883
        Self {
916883
            root: NodeData::create_node(NodeType::Div),
916883
            children: DomVec::from_const_slice(&[]),
916883
            css: azul_css::css::CssVec::from_const_slice(&[]),
916883
            estimated_total_children: 0,
916883
        }
916883
    }
    // Semantic Structure Elements
    /// Creates an article element.
    ///
    /// **Accessibility**: Represents self-contained content that could be distributed
    /// independently. Screen readers can navigate by articles. Consider adding aria-label for
    /// multiple articles.
    #[inline]
    #[must_use] pub const fn create_article() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Article),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a section element.
    ///
    /// **Accessibility**: Represents a thematic grouping of content with a heading.
    /// Should typically have a heading (h1-h6) as a child. Consider aria-labelledby.
    #[inline]
    #[must_use] pub const fn create_section() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Section),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a navigation element.
    ///
    /// **Accessibility**: Represents navigation links. Screen readers can jump to navigation.
    /// Use aria-label to distinguish multiple nav elements (e.g., "Main navigation", "Footer
    /// links").
    #[inline]
    #[must_use] pub const fn create_nav() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Nav),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an aside element.
    ///
    /// **Accessibility**: Represents content tangentially related to main content (sidebars,
    /// callouts). Screen readers announce this as complementary content.
    #[inline]
    #[must_use] pub const fn create_aside() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Aside),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a header element.
    ///
    /// **Accessibility**: Represents introductory content or navigational aids.
    /// Can be used for page headers or section headers.
    #[inline]
    #[must_use] pub const fn create_header() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Header),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a footer element.
    ///
    /// **Accessibility**: Represents footer for nearest section or page.
    /// Typically contains copyright, author info, or related links.
    #[inline]
    #[must_use] pub const fn create_footer() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Footer),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a main content element.
    ///
    /// **Accessibility**: Represents the dominant content. There should be only ONE main per page.
    /// Screen readers can jump directly to main content. Do not nest inside
    /// article/aside/footer/header/nav.
    #[inline]
    #[must_use] pub const fn create_main() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Main),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a figure element.
    ///
    /// **Accessibility**: Represents self-contained content like diagrams, photos, code listings.
    /// Use with `<figcaption>` to provide a caption. Screen readers associate caption with figure.
    #[inline]
    #[must_use] pub const fn create_figure() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Figure),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a figure caption element.
    ///
    /// **Accessibility**: Provides a caption for `<figure>`. Screen readers announce this as the
    /// figure description.
    #[inline]
    #[must_use] pub const fn create_figcaption() -> Self {
        Self {
            root: NodeData::create_node(NodeType::FigCaption),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    // Interactive Elements
    /// Creates a details disclosure element without accessibility information.
    ///
    /// Prefer [`Dom::create_details`] so that screen readers announce the
    /// disclosure widget's purpose.
    #[inline]
    #[must_use] pub const fn create_details_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Details),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a details disclosure element with accessibility information.
    ///
    /// **Accessibility**: Creates a disclosure widget. Screen readers announce expanded/collapsed
    /// state. Must contain a `<summary>` element. Keyboard accessible by default.
    ///
    /// Use [`Dom::create_details_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_details(aria: SmallAriaInfo) -> Self {
        Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates an empty summary element for details without accessibility information.
    ///
    /// Prefer [`Dom::create_summary`] so that screen readers can announce the
    /// disclosure heading.
    #[inline]
    #[must_use] pub const fn create_summary_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Summary),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an empty summary element for details with accessibility information.
    ///
    /// **Accessibility**: The visible heading/label for `<details>`.
    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
    ///
    /// Use [`Dom::create_summary_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_summary(aria: SmallAriaInfo) -> Self {
        Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a summary element with text without accessibility information.
    ///
    /// Prefer [`Dom::create_summary_with_text`] so that screen readers
    /// announce the disclosure heading.
    #[inline]
    pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
        Self::create_summary_no_a11y().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a summary element with text and accessibility information for details.
    ///
    /// **Accessibility**: The visible heading/label for `<details>`.
    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
    ///
    /// Use [`Dom::create_summary_with_text_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
        Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
    }
    /// Creates a dialog element without accessibility information.
    ///
    /// Prefer [`Dom::create_dialog`] so that the dialog's purpose, modality,
    /// and described-by relationship are surfaced to assistive technologies.
    #[inline]
    #[must_use] pub const fn create_dialog_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Dialog),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a dialog element with accessibility information.
    ///
    /// **Accessibility**: Represents a modal or non-modal dialog.
    /// When opened as modal, focus is trapped. Use aria-label or aria-labelledby.
    /// Escape key should close modal dialogs.
    ///
    /// Use [`Dom::create_dialog_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_dialog(aria: DialogAriaInfo) -> Self {
        Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    // Basic Structural Elements
    #[inline]
1
    #[must_use] pub const fn create_br() -> Self {
1
        Self {
1
            root: NodeData::create_node(NodeType::Br),
1
            children: DomVec::from_const_slice(&[]),
1
            css: azul_css::css::CssVec::from_const_slice(&[]),
1
            estimated_total_children: 0,
1
        }
1
    }
    /// Creates a RAW text node - read this before using it.
    ///
    /// **WARNING**: azul does NOT auto-wrap raw text in an anonymous block
    /// the way browsers do. A bare text node has NO box of its own: it gets
    /// no rect, no clip, and no layout constraints. Every box-model CSS
    /// property (width/height/position/overflow/background/border/padding),
    /// every callback, every `tab_index` and every `dataset` attached to a
    /// text node is silently INERT. This has broken shipped widgets before
    /// (text escaping its container, click targets that never fire).
    ///
    /// A raw text node is only correct as the bare leaf INSIDE a block-level
    /// wrapper that carries the styling - `p`, `div`, `h1`... Prefer the
    /// `create_*_with_text` family ([`Dom::create_p_with_text`],
    /// [`Dom::create_div_with_text`], [`Dom::create_span_with_text`], ...),
    /// which builds that shape for you. The engine also logs a warning after
    /// layout when it finds a text node used without a containing block.
    #[inline]
96003
    pub fn create_text_do_not_use_without_block_level_wrapper<S: Into<AzString>>(
96003
        value: S,
96003
    ) -> Self {
96003
        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
96003
    }
    #[inline]
1030
    #[must_use] pub fn create_image(image: ImageRef) -> Self {
1030
        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
1030
    }
    /// Creates an icon node with the given icon name.
    ///
    /// The icon name should match names from the icon provider (e.g., "home", "settings", "search").
    /// Icons are resolved to actual content (font glyph, image, etc.) during `StyledDom` creation
    /// based on the configured `IconProvider`.
    ///
    /// # Example
    /// ```rust,ignore
    /// Dom::create_icon("home")
    ///     .with_class("nav-icon")
    /// ```
    #[inline]
18693
    pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
18693
        Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
18693
    }
    #[inline]
123
    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
123
        Self::create_from_data(NodeData::create_virtual_view(data, callback))
123
    }
    /// Creates an invisible `NodeType::GeolocationProbe` node that
    /// signals "this subtree needs the user's location". Lays out as
    /// zero-size and is skipped in the display list - the framework
    /// scans for it at end-of-layout and starts / stops the native
    /// `CLLocationManager` / `LocationManager` / `geoclue`
    /// subscription. See `SUPER_PLAN_2.md` section 1.5.
    #[inline]
1
    #[must_use] pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
1
        Self::create_node(NodeType::GeolocationProbe(config))
1
    }
    // Semantic HTML Elements with Accessibility Guidance
    /// Creates a paragraph element.
    ///
    /// **Accessibility**: Paragraphs provide semantic structure for screen readers.
    #[inline]
398911
    #[must_use] pub const fn create_p() -> Self {
398911
        Self {
398911
            root: NodeData::create_node(NodeType::P),
398911
            children: DomVec::from_const_slice(&[]),
398911
            css: azul_css::css::CssVec::from_const_slice(&[]),
398911
            estimated_total_children: 0,
398911
        }
398911
    }
    /// Creates an empty heading level 1 element.
    ///
    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
    /// per page.
    #[inline]
21
    #[must_use] pub const fn create_h1() -> Self {
21
        Self {
21
            root: NodeData::create_node(NodeType::H1),
21
            children: DomVec::from_const_slice(&[]),
21
            css: azul_css::css::CssVec::from_const_slice(&[]),
21
            estimated_total_children: 0,
21
        }
21
    }
    /// Creates a heading level 1 element with text.
    ///
    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
    /// per page.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
3
    pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
3
        Self::create_h1().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
3
    }
    /// Creates an empty heading level 2 element.
    ///
    /// **Accessibility**: Use `h2` for major section headings under `h1`.
    #[inline]
10
    #[must_use] pub const fn create_h2() -> Self {
10
        Self {
10
            root: NodeData::create_node(NodeType::H2),
10
            children: DomVec::from_const_slice(&[]),
10
            css: azul_css::css::CssVec::from_const_slice(&[]),
10
            estimated_total_children: 0,
10
        }
10
    }
    /// Creates a heading level 2 element with text.
    ///
    /// **Accessibility**: Use `h2` for major section headings under `h1`.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
1
    pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
1
        Self::create_h2().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
1
    }
    /// Creates an empty heading level 3 element.
    ///
    /// **Accessibility**: Use `h3` for subsections under `h2`.
    #[inline]
20
    #[must_use] pub const fn create_h3() -> Self {
20
        Self {
20
            root: NodeData::create_node(NodeType::H3),
20
            children: DomVec::from_const_slice(&[]),
20
            css: azul_css::css::CssVec::from_const_slice(&[]),
20
            estimated_total_children: 0,
20
        }
20
    }
    /// Creates a heading level 3 element with text.
    ///
    /// **Accessibility**: Use `h3` for subsections under `h2`.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
2
    pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
2
        Self::create_h3().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
2
    }
    /// Creates an empty heading level 4 element.
    #[inline]
    #[must_use] pub const fn create_h4() -> Self {
        Self {
            root: NodeData::create_node(NodeType::H4),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a heading level 4 element with text.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
    pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_h4().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty heading level 5 element.
    #[inline]
    #[must_use] pub const fn create_h5() -> Self {
        Self {
            root: NodeData::create_node(NodeType::H5),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a heading level 5 element with text.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
    pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_h5().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty heading level 6 element.
    #[inline]
    #[must_use] pub const fn create_h6() -> Self {
        Self {
            root: NodeData::create_node(NodeType::H6),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a heading level 6 element with text.
    ///
    /// **Parameters:**
    /// - `text`: Heading text
    #[inline]
    pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_h6().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty generic inline container (span).
    ///
    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
    /// applicable.
    #[inline]
6
    #[must_use] pub const fn create_span() -> Self {
6
        Self {
6
            root: NodeData::create_node(NodeType::Span),
6
            children: DomVec::from_const_slice(&[]),
6
            css: azul_css::css::CssVec::from_const_slice(&[]),
6
            estimated_total_children: 0,
6
        }
6
    }
    /// Creates a generic inline container (span) with text.
    ///
    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
    /// applicable.
    ///
    /// **Parameters:**
    /// - `text`: Span content
    #[inline]
2
    pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
2
        Self::create_span().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
2
    }
    /// Creates an empty strong importance element.
    ///
    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning.
    #[inline]
    #[must_use] pub const fn create_strong() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Strong),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a strongly emphasized text element with text (strong importance).
    ///
    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning. Screen readers can
    /// convey the importance. Use for text that has strong importance, seriousness, or urgency.
    ///
    /// **Parameters:**
    /// - `text`: Text to emphasize
    #[inline]
    pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_strong().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty emphasis element (stress emphasis).
    ///
    /// **Accessibility**: Use `em` instead of `i` for semantic meaning.
    #[inline]
1
    #[must_use] pub const fn create_em() -> Self {
1
        Self {
1
            root: NodeData::create_node(NodeType::Em),
1
            children: DomVec::from_const_slice(&[]),
1
            css: azul_css::css::CssVec::from_const_slice(&[]),
1
            estimated_total_children: 0,
1
        }
1
    }
    /// Creates an emphasized text element with text (stress emphasis).
    ///
    /// **Accessibility**: Use `em` instead of `i` for semantic meaning. Screen readers can
    /// convey the emphasis. Use for text that has stress emphasis.
    ///
    /// **Parameters:**
    /// - `text`: Text to emphasize
    #[inline]
    pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_em().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty code element.
    ///
    /// **Accessibility**: Represents a fragment of computer code.
    #[inline]
    #[must_use] pub fn create_code() -> Self {
        Self::create_node(NodeType::Code)
    }
    /// Creates a code/computer code element with text.
    ///
    /// **Accessibility**: Represents a fragment of computer code. Screen readers can identify
    /// this as code content.
    ///
    /// **Parameters:**
    /// - `code`: Code content
    #[inline]
    pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
        Self::create_code().with_child(Self::create_text_do_not_use_without_block_level_wrapper(code))
    }
    /// Creates an empty preformatted text element.
    ///
    /// **Accessibility**: Preserves whitespace and line breaks.
    #[inline]
10
    #[must_use] pub fn create_pre() -> Self {
10
        Self::create_node(NodeType::Pre)
10
    }
    /// Creates a preformatted text element with text.
    ///
    /// **Accessibility**: Preserves whitespace and line breaks. Useful for code blocks or
    /// ASCII art. Screen readers will read the content as-is.
    ///
    /// **Parameters:**
    /// - `text`: Preformatted content
    #[inline]
1
    pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
1
        Self::create_pre().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
1
    }
    /// Creates an empty blockquote element.
    ///
    /// **Accessibility**: Represents a section quoted from another source.
    #[inline]
    #[must_use] pub fn create_blockquote() -> Self {
        Self::create_node(NodeType::BlockQuote)
    }
    /// Creates a blockquote element with text.
    ///
    /// **Accessibility**: Represents a section quoted from another source. Screen readers
    /// can identify quoted content. Consider adding a `cite` attribute.
    ///
    /// **Parameters:**
    /// - `text`: Quote content
    #[inline]
    pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_blockquote().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty citation element.
    ///
    /// **Accessibility**: Represents a reference to a creative work.
    #[inline]
    #[must_use] pub fn create_cite() -> Self {
        Self::create_node(NodeType::Cite)
    }
    /// Creates a citation element with text.
    ///
    /// **Accessibility**: Represents a reference to a creative work. Screen readers can
    /// identify citations.
    ///
    /// **Parameters:**
    /// - `text`: Citation text
    #[inline]
    pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_cite().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty abbreviation element.
    ///
    /// **Accessibility**: Represents an abbreviation or acronym. Use with a `title` attribute
    /// to provide the full expansion for screen readers.
    #[inline]
    #[must_use] pub fn create_abbr() -> Self {
        Self::create_node(NodeType::Abbr)
    }
    /// Creates an abbreviation element with abbreviated text and a `title` expansion.
    ///
    /// **Accessibility**: Represents an abbreviation or acronym. The `title` attribute
    /// provides the full expansion for screen readers.
    ///
    /// **Parameters:**
    /// - `abbr_text`: Abbreviated text
    /// - `title`: Full expansion
    #[inline]
    #[must_use] pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
        Self::create_node(NodeType::Abbr)
            .with_attribute(AttributeType::Title(title))
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(abbr_text))
    }
    /// Creates an empty keyboard input element.
    ///
    /// **Accessibility**: Represents keyboard input or key combinations.
    #[inline]
    #[must_use] pub fn create_kbd() -> Self {
        Self::create_node(NodeType::Kbd)
    }
    /// Creates a keyboard input element with text.
    ///
    /// **Accessibility**: Represents keyboard input or key combinations. Screen readers can
    /// identify keyboard instructions.
    ///
    /// **Parameters:**
    /// - `text`: Keyboard instruction
    #[inline]
    pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_kbd().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty sample output element.
    ///
    /// **Accessibility**: Represents sample output from a program or computing system.
    #[inline]
    #[must_use] pub fn create_samp() -> Self {
        Self::create_node(NodeType::Samp)
    }
    /// Creates a sample output element with text.
    ///
    /// **Accessibility**: Represents sample output from a program or computing system.
    ///
    /// **Parameters:**
    /// - `text`: Sample text
    #[inline]
    pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_samp().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty variable element.
    ///
    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
    #[inline]
    #[must_use] pub fn create_var() -> Self {
        Self::create_node(NodeType::Var)
    }
    /// Creates a variable element with text.
    ///
    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
    ///
    /// **Parameters:**
    /// - `text`: Variable name
    #[inline]
    pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_var().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty subscript element.
    #[inline]
    #[must_use] pub fn create_sub() -> Self {
        Self::create_node(NodeType::Sub)
    }
    /// Creates a subscript element with text.
    ///
    /// **Accessibility**: Screen readers may announce subscript formatting.
    ///
    /// **Parameters:**
    /// - `text`: Subscript content
    #[inline]
    pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_sub().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty superscript element.
    #[inline]
    #[must_use] pub fn create_sup() -> Self {
        Self::create_node(NodeType::Sup)
    }
    /// Creates a superscript element with text.
    ///
    /// **Accessibility**: Screen readers may announce superscript formatting.
    ///
    /// **Parameters:**
    /// - `text`: Superscript content
    #[inline]
    pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_sup().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty underline element.
    #[inline]
    #[must_use] pub fn create_u() -> Self {
        Self::create_node(NodeType::U)
    }
    /// Creates an underline text element with text.
    ///
    /// **Accessibility**: Screen readers typically don't announce underline formatting.
    /// Use semantic elements when possible (e.g., `<em>` for emphasis).
    #[inline]
    pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_u().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty strikethrough element.
    #[inline]
    #[must_use] pub fn create_s() -> Self {
        Self::create_node(NodeType::S)
    }
    /// Creates a strikethrough text element with text.
    ///
    /// **Accessibility**: Represents text that is no longer accurate or relevant.
    /// Consider using `<del>` for deleted content with datetime attribute.
    #[inline]
    pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_s().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty mark element.
    #[inline]
    #[must_use] pub fn create_mark() -> Self {
        Self::create_node(NodeType::Mark)
    }
    /// Creates a marked/highlighted text element with text.
    ///
    /// **Accessibility**: Represents text marked for reference or notation purposes.
    /// Screen readers may announce this as "highlighted".
    #[inline]
    pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_mark().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty deleted text element.
    #[inline]
    #[must_use] pub fn create_del() -> Self {
        Self::create_node(NodeType::Del)
    }
    /// Creates a deleted text element with text.
    ///
    /// **Accessibility**: Represents deleted content in document edits.
    /// Use with `datetime` and `cite` attributes for edit tracking.
    #[inline]
    pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_del().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty inserted text element.
    #[inline]
    #[must_use] pub fn create_ins() -> Self {
        Self::create_node(NodeType::Ins)
    }
    /// Creates an inserted text element with text.
    ///
    /// **Accessibility**: Represents inserted content in document edits.
    /// Use with `datetime` and `cite` attributes for edit tracking.
    #[inline]
    pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_ins().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty definition element.
    #[inline]
    #[must_use] pub fn create_dfn() -> Self {
        Self::create_node(NodeType::Dfn)
    }
    /// Creates a definition element with text.
    ///
    /// **Accessibility**: Represents the defining instance of a term.
    /// Often used within a definition list or with `<abbr>`.
    #[inline]
    pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_dfn().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a time element.
    ///
    /// **Accessibility**: Represents a specific time or date.
    /// Use `datetime` attribute for machine-readable format.
    ///
    /// **Parameters:**
    /// - `text`: Human-readable time/date
    /// - `datetime`: Optional machine-readable datetime
    #[inline]
    #[must_use] pub fn create_time(text: AzString, datetime: OptionString) -> Self {
        let mut element = Self::create_node(NodeType::Time).with_child(Self::create_text_do_not_use_without_block_level_wrapper(text));
        if let OptionString::Some(dt) = datetime {
            element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "datetime".into(),
                value: dt,
            }));
        }
        element
    }
    /// Creates an empty bi-directional override element.
    ///
    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
    #[inline]
    #[must_use] pub fn create_bdo() -> Self {
        Self::create_node(NodeType::Bdo)
    }
    /// Creates a bi-directional override element with text.
    ///
    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
    #[inline]
    pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_bdo().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    // Additional inline / text-level elements
    /// Creates an empty bold element.
    ///
    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
    #[inline]
    #[must_use] pub fn create_b() -> Self {
        Self::create_node(NodeType::B)
    }
    /// Creates a bold element with text.
    ///
    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
    ///
    /// **Parameters:**
    /// - `text`: Bold text content
    #[inline]
    pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_b().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty italic element.
    ///
    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
    #[inline]
    #[must_use] pub fn create_i() -> Self {
        Self::create_node(NodeType::I)
    }
    /// Creates an italic element with text.
    ///
    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
    ///
    /// **Parameters:**
    /// - `text`: Italic text content
    #[inline]
    pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_i().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty small text element.
    ///
    /// **Accessibility**: Represents side-comments and small print like copyright/legal text.
    #[inline]
    #[must_use] pub fn create_small() -> Self {
        Self::create_node(NodeType::Small)
    }
    /// Creates a small text element with text.
    ///
    /// **Parameters:**
    /// - `text`: Small text content
    #[inline]
    pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_small().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty `<big>` element.
    ///
    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
    #[inline]
    #[must_use] pub fn create_big() -> Self {
        Self::create_node(NodeType::Big)
    }
    /// Creates a `<big>` element with text.
    ///
    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
    #[inline]
    pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_big().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty bi-directional isolate element.
    ///
    /// **Accessibility**: Used to isolate text whose direction is unknown,
    /// keeping it from affecting surrounding bidi layout.
    #[inline]
    #[must_use] pub fn create_bdi() -> Self {
        Self::create_node(NodeType::Bdi)
    }
    /// Creates a bi-directional isolate element with text.
    ///
    /// **Accessibility**: Used to isolate text whose direction is unknown,
    /// keeping it from affecting surrounding bidi layout.
    #[inline]
    pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_bdi().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty word break opportunity element.
    ///
    /// **Note**: `<wbr>` is a self-closing element that suggests a line-break opportunity.
    /// It does not take text content.
    #[inline]
    #[must_use] pub fn create_wbr() -> Self {
        Self::create_node(NodeType::Wbr)
    }
    /// Creates an empty ruby annotation element.
    ///
    /// **Accessibility**: Used for East Asian typography to provide
    /// pronunciation/translation annotations. Wraps `<rt>`/`<rp>` children.
    #[inline]
    #[must_use] pub fn create_ruby() -> Self {
        Self::create_node(NodeType::Ruby)
    }
    /// Creates an empty ruby text element.
    ///
    /// **Accessibility**: Pronunciation/translation annotation inside `<ruby>`.
    #[inline]
    #[must_use] pub fn create_rt() -> Self {
        Self::create_node(NodeType::Rt)
    }
    /// Creates a ruby text element with text.
    ///
    /// **Parameters:**
    /// - `text`: Ruby annotation content
    #[inline]
    pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_rt().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty ruby text container element.
    ///
    /// **Accessibility**: Container for ruby text annotations.
    #[inline]
    #[must_use] pub fn create_rtc() -> Self {
        Self::create_node(NodeType::Rtc)
    }
    /// Creates an empty ruby fallback parenthesis element.
    ///
    /// **Accessibility**: Provides parentheses around `<rt>` for browsers without ruby support.
    #[inline]
    #[must_use] pub fn create_rp() -> Self {
        Self::create_node(NodeType::Rp)
    }
    /// Creates a ruby fallback parenthesis element with text.
    ///
    /// **Parameters:**
    /// - `text`: Parenthesis text (typically "(" or ")")
    #[inline]
    pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_rp().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a `<data>` element binding a machine-readable value to its content.
    ///
    /// **Parameters:**
    /// - `value`: Machine-readable value for the `value` attribute.
    #[inline]
    #[must_use] pub fn create_data(value: AzString) -> Self {
        Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
    }
    /// Creates a `<data>` element with both a machine-readable value and visible text.
    ///
    /// **Parameters:**
    /// - `value`: Machine-readable value for the `value` attribute.
    /// - `text`: Human-readable text content.
    #[inline]
    #[must_use] pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
        Self::create_data(value).with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an empty directory list element.
    ///
    /// **Note**: Deprecated in HTML5. Use `<ul>` instead.
    #[inline]
    #[must_use] pub fn create_dir() -> Self {
        Self::create_node(NodeType::Dir)
    }
    /// Creates an empty SVG container element.
    ///
    /// **Accessibility**: Provide `aria-label` or `<title>` child for assistive tech.
    #[inline]
    #[must_use] pub fn create_svg() -> Self {
        Self::create_node(NodeType::Svg)
    }
    /// Creates an anchor/hyperlink element without accessibility information.
    ///
    /// Prefer [`Dom::create_a`] so that screen readers get a meaningful label.
    ///
    /// **Parameters:**
    /// - `href`: Link destination URL
    /// - `label`: Link text (pass `None` for image-only links with alt text)
    #[inline]
2
    #[must_use] pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
2
        let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2
        if let OptionString::Some(text) = label {
1
            link = link.with_child(Self::create_text_do_not_use_without_block_level_wrapper(text));
1
        }
2
        link
2
    }
    /// Creates a button element without accessibility information.
    ///
    /// Prefer [`Dom::create_button`] so that the element has a meaningful accessible
    /// name for screen readers.
    ///
    /// **Parameters:**
    /// - `text`: Button label text
    #[inline]
2
    #[must_use] pub fn create_button_no_a11y(text: AzString) -> Self {
2
        Self::create_node(NodeType::Button).with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
2
    }
    /// Creates a label element for form controls without accessibility information.
    ///
    /// Prefer [`Dom::create_label`] so that screen readers get a descriptive label.
    ///
    /// **Parameters:**
    /// - `for_id`: ID of the associated form control
    /// - `text`: Label text
    #[inline]
2
    #[must_use] pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
2
        Self::create_node(NodeType::Label)
2
            .with_attribute(AttributeType::Custom(AttributeNameValue {
2
                attr_name: "for".into(),
2
                value: for_id,
2
            }))
2
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
2
    }
    /// Creates an input element without accessibility information.
    ///
    /// Prefer [`Dom::create_input`] so that screen readers get a descriptive label
    /// beyond the HTML `aria-label` attribute.
    ///
    /// **Parameters:**
    /// - `input_type`: Input type (text, password, email, etc.)
    /// - `name`: Form field name
    /// - `label`: Accessibility label (required)
    #[inline]
2
    #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::Input)
2
            .with_attribute(AttributeType::InputType(input_type))
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates a textarea element without accessibility information.
    ///
    /// Prefer [`Dom::create_textarea`] so that screen readers get an accurate
    /// description of the control.
    ///
    /// **Parameters:**
    /// - `name`: Form field name
    /// - `label`: Accessibility label (required)
    #[inline]
2
    #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::TextArea)
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates a select dropdown element without accessibility information.
    ///
    /// Prefer [`Dom::create_select`] so that screen readers announce the control
    /// appropriately.
    ///
    /// **Parameters:**
    /// - `name`: Form field name
    /// - `label`: Accessibility label (required)
    #[inline]
2
    #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2
        Self::create_node(NodeType::Select)
2
            .with_attribute(AttributeType::Name(name))
2
            .with_attribute(AttributeType::AriaLabel(label))
2
    }
    /// Creates an option element for select dropdowns.
    ///
    /// **Parameters:**
    /// - `value`: Option value
    /// - `text`: Display text
    #[inline]
    #[must_use] pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
        Self::create_node(NodeType::SelectOption)
            .with_attribute(AttributeType::Value(value))
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates an option element for select dropdowns with accessibility information.
    ///
    /// **Parameters:**
    /// - `value`: Option value
    /// - `text`: Display text
    /// - `aria`: Accessibility information (description, etc.)
    ///
    /// Use [`Dom::create_option_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
        Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
    }
    /// Creates an unordered list element.
    ///
    /// **Accessibility**: Screen readers announce lists and item counts, helping users
    /// understand content structure.
    #[inline]
30
    #[must_use] pub fn create_ul() -> Self {
30
        Self::create_node(NodeType::Ul)
30
    }
    /// Creates an ordered list element.
    ///
    /// **Accessibility**: Screen readers announce lists and item counts, helping users
    /// understand content structure and numbering.
    #[inline]
    #[must_use] pub fn create_ol() -> Self {
        Self::create_node(NodeType::Ol)
    }
    /// Creates a list item element.
    ///
    /// **Accessibility**: Must be a child of `ul`, `ol`, or `menu`. Screen readers announce
    /// list item position (e.g., "2 of 5").
    #[inline]
90
    #[must_use] pub fn create_li() -> Self {
90
        Self::create_node(NodeType::Li)
90
    }
    /// Creates a table element without accessibility information.
    ///
    /// Prefer [`Dom::create_table`] so that screen readers can announce the table's
    /// purpose alongside its caption.
    #[inline]
3
    #[must_use] pub fn create_table_no_a11y() -> Self {
3
        Self::create_node(NodeType::Table)
3
    }
    /// Creates a table caption element.
    ///
    /// **Accessibility**: Describes the purpose of the table. Screen readers announce this first.
    #[inline]
1
    #[must_use] pub fn create_caption() -> Self {
1
        Self::create_node(NodeType::Caption)
1
    }
    /// Creates a table header element.
    ///
    /// **Accessibility**: Groups header rows. Screen readers can navigate table structure.
    #[inline]
1
    #[must_use] pub fn create_thead() -> Self {
1
        Self::create_node(NodeType::THead)
1
    }
    /// Creates a table body element.
    ///
    /// **Accessibility**: Groups body rows. Screen readers can navigate table structure.
    #[inline]
    #[must_use] pub fn create_tbody() -> Self {
        Self::create_node(NodeType::TBody)
    }
    /// Creates a table footer element.
    ///
    /// **Accessibility**: Groups footer rows. Screen readers can navigate table structure.
    #[inline]
    #[must_use] pub fn create_tfoot() -> Self {
        Self::create_node(NodeType::TFoot)
    }
    /// Creates a table row element.
    #[inline]
1
    #[must_use] pub fn create_tr() -> Self {
1
        Self::create_node(NodeType::Tr)
1
    }
    /// Creates a table header cell element.
    ///
    /// **Accessibility**: Use `scope` attribute ("col" or "row") to associate headers with
    /// data cells. Screen readers use this to announce cell context.
    #[inline]
2
    #[must_use] pub fn create_th() -> Self {
2
        Self::create_node(NodeType::Th)
2
    }
    /// Creates a table data cell element.
    #[inline]
    #[must_use] pub fn create_td() -> Self {
        Self::create_node(NodeType::Td)
    }
    /// Creates a form element without accessibility information.
    ///
    /// Prefer [`Dom::create_form`] so that screen readers can announce the form's purpose.
    #[inline]
    #[must_use] pub fn create_form_no_a11y() -> Self {
        Self::create_node(NodeType::Form)
    }
    /// Creates a form element with accessibility information.
    ///
    /// **Accessibility**: Group related form controls with `fieldset` and `legend`.
    /// Provide clear labels for all inputs. Consider `aria-describedby` for instructions.
    ///
    /// Use [`Dom::create_form_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_form(aria: SmallAriaInfo) -> Self {
        Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a fieldset element for grouping form controls without accessibility info.
    ///
    /// Prefer [`Dom::create_fieldset`] so that screen readers can announce the group's purpose.
    #[inline]
    #[must_use] pub fn create_fieldset_no_a11y() -> Self {
        Self::create_node(NodeType::FieldSet)
    }
    /// Creates a fieldset element with accessibility information.
    ///
    /// **Accessibility**: Groups related form controls. Always include a `legend` as the
    /// first child to describe the group. Screen readers announce the legend when entering
    /// the fieldset.
    ///
    /// Use [`Dom::create_fieldset_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
        Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a legend element without accessibility information.
    ///
    /// Prefer [`Dom::create_legend`] so that the legend's accessible name is explicit.
    #[inline]
    #[must_use] pub fn create_legend_no_a11y() -> Self {
        Self::create_node(NodeType::Legend)
    }
    /// Creates a legend element with accessibility information.
    ///
    /// **Accessibility**: Describes the purpose of a fieldset. Must be the first child of
    /// a fieldset. Screen readers announce this when entering the fieldset.
    ///
    /// Use [`Dom::create_legend_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_legend(aria: SmallAriaInfo) -> Self {
        Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a horizontal rule element.
    ///
    /// **Accessibility**: Represents a thematic break. Screen readers may announce this as
    /// a separator. Consider using CSS borders for purely decorative lines.
    #[inline]
    #[must_use] pub fn create_hr() -> Self {
        Self::create_node(NodeType::Hr)
    }
    /// Creates THE canonical page-break element: a zero-size block with
    /// `break-before: page`, carrying the `__azul-native-pagebreak` class.
    ///
    /// This is the one break element the pagination estimator and a screen
    /// DOM treat identically (see `pagination_to_dom_breaks` in
    /// azul-layout): an application materializes an estimated break by
    /// inserting this node at the returned child-index path, and the layout
    /// of the surrounding content does not move - the element is an empty
    /// block with no margins, borders or padding, so sibling margins keep
    /// collapsing through it exactly as they did without it.
    ///
    /// The XML pipeline's `<pagebreak/>` builtin renders the equivalent
    /// element.
33
    #[must_use] pub fn create_page_break() -> Self {
33
        Self::create_node(NodeType::PageBreak)
33
    }
    // Additional Element Constructors
    /// Creates an address element.
    ///
    /// **Accessibility**: Represents contact information. Screen readers identify this
    /// as address content.
    #[inline]
    #[must_use] pub const fn create_address() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Address),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a definition list element.
    ///
    /// **Accessibility**: Screen readers announce definition lists and their structure.
    #[inline]
    #[must_use] pub const fn create_dl() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Dl),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a definition term element.
    ///
    /// **Accessibility**: Must be a child of `dl`. Represents the term being defined.
    #[inline]
    #[must_use] pub const fn create_dt() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Dt),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a definition description element.
    ///
    /// **Accessibility**: Must be a child of `dl`. Provides the definition for the term.
    #[inline]
    #[must_use] pub const fn create_dd() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Dd),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a table column group element.
    #[inline]
    #[must_use] pub const fn create_colgroup() -> Self {
        Self {
            root: NodeData::create_node(NodeType::ColGroup),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a table column element.
    #[inline]
    #[must_use] pub fn create_col(span: i32) -> Self {
        Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
    }
    /// Creates an optgroup element for grouping select options without accessibility info.
    ///
    /// Prefer [`Dom::create_optgroup`] so that screen readers can announce the group's purpose.
    ///
    /// **Parameters:**
    /// - `label`: Label for the option group
    #[inline]
    #[must_use] pub fn create_optgroup_no_a11y(label: AzString) -> Self {
        Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
    }
    /// Creates an optgroup element for grouping select options with accessibility information.
    ///
    /// **Parameters:**
    /// - `label`: Label for the option group (visible)
    /// - `aria`: Additional accessibility information (description, etc.)
    ///
    /// Use [`Dom::create_optgroup_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
        Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
    }
    /// Creates a quotation element.
    ///
    /// **Accessibility**: Represents an inline quotation.
    #[inline]
    #[must_use] pub const fn create_q() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Q),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an empty acronym element.
    ///
    /// **Note**: Deprecated in HTML5. Consider using `create_abbr()` instead.
    #[inline]
    #[must_use] pub const fn create_acronym() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Acronym),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an acronym element with text.
    ///
    /// **Note**: Deprecated in HTML5. Consider using `create_abbr_with_title()` instead.
    #[inline]
    pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_acronym().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a menu element without accessibility information.
    ///
    /// Prefer [`Dom::create_menu`] so that the menu's purpose is announced.
    #[inline]
    #[must_use] pub const fn create_menu_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Menu),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a menu element with accessibility information.
    ///
    /// **Accessibility**: Represents a list of commands. Similar to `<ul>` but semantic for
    /// toolbars/menus.
    ///
    /// Use [`Dom::create_menu_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_menu(aria: SmallAriaInfo) -> Self {
        Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates an empty menu item element without accessibility information.
    ///
    /// Prefer [`Dom::create_menuitem`] so that the menu item's purpose is announced.
    #[inline]
    #[must_use] pub const fn create_menuitem_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::MenuItem),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an empty menu item element with accessibility information.
    ///
    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
    /// attributes.
    ///
    /// Use [`Dom::create_menuitem_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
        Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a menu item element with text but without accessibility information.
    ///
    /// Prefer [`Dom::create_menuitem_with_text`] so that screen readers get a
    /// distinct accessible name in addition to the visible text.
    #[inline]
    pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
        Self::create_menuitem_no_a11y().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a menu item element with text and accessibility information.
    ///
    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
    /// attributes.
    ///
    /// Use [`Dom::create_menuitem_with_text_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
        Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
    }
    /// Creates an output element without accessibility information.
    ///
    /// Prefer [`Dom::create_output`] so that screen readers can announce the
    /// computed value's purpose.
    #[inline]
    #[must_use] pub const fn create_output_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Output),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an output element with accessibility information.
    ///
    /// **Accessibility**: Represents the result of a calculation or user action.
    /// Use `for` attribute to associate with input elements. Screen readers announce updates.
    ///
    /// Use [`Dom::create_output_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_output(aria: SmallAriaInfo) -> Self {
        Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a progress indicator element without accessibility information.
    ///
    /// Prefer [`Dom::create_progress`] so that the task being measured is announced.
    ///
    /// **Parameters:**
    /// - `value`: Current progress value
    /// - `max`: Maximum value
    #[inline]
    #[must_use] pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
        Self::create_node(NodeType::Progress)
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "value".into(),
                value: value.to_string().into(),
            }))
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "max".into(),
                value: max.to_string().into(),
            }))
    }
    /// Creates a progress indicator element with accessibility information.
    ///
    /// **Accessibility**: Represents task progress. Screen readers announce progress
    /// percentage. The `aria` value carries the label, current value, max, and an
    /// indeterminate flag for spinners with no known endpoint.
    ///
    /// Use [`Dom::create_progress_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_progress(aria: ProgressAriaInfo) -> Self {
        let mut node = Self::create_node(NodeType::Progress);
        if !aria.indeterminate {
            if let azul_css::OptionF32::Some(v) = aria.current_value {
                node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
                    attr_name: "value".into(),
                    value: v.to_string().into(),
                }));
            }
        }
        if let azul_css::OptionF32::Some(m) = aria.max {
            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "max".into(),
                value: m.to_string().into(),
            }));
        }
        node.with_accessibility_info(aria.to_full_info())
    }
    /// Creates a meter gauge element without accessibility information.
    ///
    /// Prefer [`Dom::create_meter`] so that the measurement's purpose is announced.
    ///
    /// **Parameters:**
    /// - `value`: Current meter value
    /// - `min`: Minimum value
    /// - `max`: Maximum value
    #[inline]
    #[must_use] pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
        Self::create_node(NodeType::Meter)
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "value".into(),
                value: value.to_string().into(),
            }))
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "min".into(),
                value: min.to_string().into(),
            }))
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "max".into(),
                value: max.to_string().into(),
            }))
    }
    /// Creates a meter gauge element with accessibility information.
    ///
    /// **Accessibility**: Represents a scalar measurement within a known range.
    /// Screen readers announce the measurement. The `aria` value carries the
    /// label plus value/min/max/low/high/optimum metadata.
    ///
    /// Use [`Dom::create_meter_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_meter(aria: MeterAriaInfo) -> Self {
        let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
        if let azul_css::OptionF32::Some(v) = aria.low {
            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "low".into(),
                value: v.to_string().into(),
            }));
        }
        if let azul_css::OptionF32::Some(v) = aria.high {
            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "high".into(),
                value: v.to_string().into(),
            }));
        }
        if let azul_css::OptionF32::Some(v) = aria.optimum {
            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "optimum".into(),
                value: v.to_string().into(),
            }));
        }
        node.with_accessibility_info(aria.to_full_info())
    }
    /// Creates a datalist element without accessibility information.
    ///
    /// Prefer [`Dom::create_datalist`] so that the suggestion list's purpose is announced.
    #[inline]
    #[must_use] pub const fn create_datalist_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::DataList),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a datalist element with accessibility information.
    ///
    /// **Accessibility**: Provides autocomplete options for inputs.
    /// Associate with input using `list` attribute. Screen readers announce available options.
    ///
    /// Use [`Dom::create_datalist_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_datalist(aria: SmallAriaInfo) -> Self {
        Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    // Embedded Content Elements
    /// Creates a canvas element for graphics without accessibility information.
    ///
    /// Prefer [`Dom::create_canvas`] so that the canvas's purpose is announced; canvas
    /// content is otherwise opaque to assistive technologies.
    #[inline]
    #[must_use] pub const fn create_canvas_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Canvas),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a canvas element for graphics with accessibility information.
    ///
    /// **Accessibility**: Canvas content is not accessible by default.
    /// Always provide fallback content as children and/or detailed aria-label.
    /// Consider using SVG for accessible graphics when possible.
    ///
    /// Use [`Dom::create_canvas_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_canvas(aria: SmallAriaInfo) -> Self {
        Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates an object element for embedded content.
    ///
    /// **Accessibility**: Provide fallback content as children. Use aria-label to describe content.
    #[inline]
    #[must_use] pub const fn create_object() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Object),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a param element for object parameters.
    ///
    /// **Parameters:**
    /// - `name`: Parameter name
    /// - `value`: Parameter value
    #[inline]
    #[must_use] pub fn create_param(name: AzString, value: AzString) -> Self {
        Self::create_node(NodeType::Param)
            .with_attribute(AttributeType::Name(name))
            .with_attribute(AttributeType::Value(value))
    }
    /// Creates an embed element.
    ///
    /// **Accessibility**: Provide alternative content or link. Use aria-label to describe embedded
    /// content.
    #[inline]
    #[must_use] pub const fn create_embed() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Embed),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an audio element without accessibility information.
    ///
    /// Prefer [`Dom::create_audio`] so that screen readers announce the audio's purpose.
    #[inline]
    #[must_use] pub const fn create_audio_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Audio),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an audio element with accessibility information.
    ///
    /// **Accessibility**: Always provide controls. Use `<track>` for captions/subtitles.
    /// Provide fallback text for unsupported browsers.
    ///
    /// Use [`Dom::create_audio_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_audio(aria: SmallAriaInfo) -> Self {
        Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a video element without accessibility information.
    ///
    /// Prefer [`Dom::create_video`] so that screen readers announce the video's purpose.
    #[inline]
    #[must_use] pub const fn create_video_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Video),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a video element with accessibility information.
    ///
    /// **Accessibility**: Always provide controls. Use `<track>` for
    /// captions/subtitles/descriptions. Provide fallback text. Consider providing transcript.
    ///
    /// Use [`Dom::create_video_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_video(aria: SmallAriaInfo) -> Self {
        Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    /// Creates a source element for media.
    ///
    /// **Parameters:**
    /// - `src`: Media source URL
    /// - `media_type`: MIME type (e.g., "video/mp4", "audio/ogg")
    #[inline]
    #[must_use] pub fn create_source(src: AzString, media_type: AzString) -> Self {
        Self::create_node(NodeType::Source)
            .with_attribute(AttributeType::Src(src))
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "type".into(),
                value: media_type,
            }))
    }
    /// Creates a track element for media captions/subtitles.
    ///
    /// **Accessibility**: Essential for deaf/hard-of-hearing users and non-native speakers.
    /// Use `kind` (subtitles/captions/descriptions), `srclang`, and `label` attributes.
    ///
    /// **Parameters:**
    /// - `src`: Track file URL (`WebVTT` format)
    /// - `kind`: Track kind ("subtitles", "captions", "descriptions", "chapters", "metadata")
    #[inline]
    #[must_use] pub fn create_track(src: AzString, kind: AzString) -> Self {
        Self::create_node(NodeType::Track)
            .with_attribute(AttributeType::Src(src))
            .with_attribute(AttributeType::Custom(AttributeNameValue {
                attr_name: "kind".into(),
                value: kind,
            }))
    }
    /// Creates a map element for image maps.
    ///
    /// **Accessibility**: Provide text alternatives. Ensure all areas have alt text.
    #[inline]
    #[must_use] pub const fn create_map() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Map),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an area element for image map regions without accessibility information.
    ///
    /// Prefer [`Dom::create_area`] so that screen readers can announce the region's purpose.
    #[inline]
    #[must_use] pub const fn create_area_no_a11y() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Area),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an area element for image map regions with accessibility information.
    ///
    /// **Accessibility**: Always provide `alt` text describing the region/link purpose.
    /// Keyboard users should be able to navigate areas.
    ///
    /// Use [`Dom::create_area_no_a11y`] only as a deliberate escape hatch.
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
    #[must_use] pub fn create_area(aria: SmallAriaInfo) -> Self {
        Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
    }
    // Metadata Elements
    /// Creates an empty title element for document title.
    ///
    /// **Accessibility**: Required for all pages. Screen readers announce this first.
    #[inline]
    #[must_use] pub fn create_title() -> Self {
        Self::create_node(NodeType::Title)
    }
    /// Creates a title element for document title with text.
    ///
    /// **Accessibility**: Required for all pages. Screen readers announce this first.
    /// Should be unique and descriptive. Keep under 60 characters.
    #[inline]
    pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_title().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a meta element.
    ///
    /// **Accessibility**: Use for charset, viewport, description. Crucial for proper text display.
    #[inline]
    #[must_use] pub const fn create_meta() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Meta),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a link element for external resources.
    ///
    /// **Accessibility**: Use for stylesheets, icons, alternate versions.
    /// Provide meaningful `title` attribute for alternate stylesheets.
    #[inline]
    #[must_use] pub const fn create_link() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Link),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a script element.
    ///
    /// **Accessibility**: Ensure scripted content is accessible.
    /// Provide noscript fallbacks for critical functionality.
    #[inline]
    #[must_use] pub const fn create_script() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Script),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates an empty style element for embedded CSS.
    ///
    /// **Note**: In Azul, use `.with_css()` instead for styling.
    /// This creates a `<style>` HTML element for embedded stylesheets.
    #[inline]
    #[must_use] pub const fn create_style() -> Self {
        Self {
            root: NodeData::create_node(NodeType::Style),
            children: DomVec::from_const_slice(&[]),
            css: azul_css::css::CssVec::from_const_slice(&[]),
            estimated_total_children: 0,
        }
    }
    /// Creates a style element for embedded CSS with the given stylesheet text.
    ///
    /// **Note**: In Azul, use `.with_css()` instead for styling.
    /// This creates a `<style>` HTML element for embedded stylesheets.
    #[inline]
    pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_style().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a base element for document base URL.
    ///
    /// **Parameters:**
    /// - `href`: Base URL for relative URLs in the document
    #[inline]
    #[must_use] pub fn create_base(href: AzString) -> Self {
        Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
    }
    // Advanced Constructors with Parameters
    /// Creates a table header cell with scope.
    ///
    /// **Parameters:**
    /// - `scope`: "col", "row", "colgroup", or "rowgroup"
    /// - `text`: Header text
    ///
    /// **Accessibility**: The scope attribute is crucial for associating headers with data cells.
    #[inline]
    #[must_use] pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
        Self::create_node(NodeType::Th)
            .with_attribute(AttributeType::Scope(scope))
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a table data cell with text.
    ///
    /// **Parameters:**
    /// - `text`: Cell content
    #[inline]
    pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_td().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a table header cell with text.
    ///
    /// **Parameters:**
    /// - `text`: Header text
    #[inline]
    pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_th().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    /// Creates a list item with text.
    ///
    /// **Parameters:**
    /// - `text`: List item content
    #[inline]
9
    pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
9
        Self::create_li().with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
9
    }
    /// Creates a paragraph with text.
    ///
    /// **Parameters:**
    /// - `text`: Paragraph content
    #[inline]
57591
    pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
57591
        Self::create_p()
57591
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
57591
    }
    /// Creates a generic block container (div) with text.
    ///
    /// The div is the box the text lives in: put your styling, callbacks and
    /// `tab_index` on the DIV, never on the text leaf (a text node has no box
    /// of its own - see
    /// [`Dom::create_text_do_not_use_without_block_level_wrapper`]).
    ///
    /// **Parameters:**
    /// - `text`: Div content
    #[inline]
    pub fn create_div_with_text<S: Into<AzString>>(text: S) -> Self {
        Self::create_div()
            .with_child(Self::create_text_do_not_use_without_block_level_wrapper(text))
    }
    // Accessibility-Aware Constructors
    // These constructors require explicit accessibility information.
    // Use the `*_no_a11y` variants only as a deliberate escape hatch.
    /// Creates a button with text content and accessibility information.
    ///
    /// Use [`Dom::create_button_no_a11y`] to skip the accessibility information.
    ///
    /// **Parameters:**
    /// - `text`: The visible button text
    /// - `aria`: Accessibility information (role, description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
1
        let mut btn = Self::create_button_no_a11y(text.into());
1
        btn.root.set_accessibility_info(aria.to_full_info());
1
        btn
1
    }
    /// Creates a link (anchor) with href, text, and accessibility information.
    ///
    /// Use [`Dom::create_a_no_a11y`] to skip the accessibility information (e.g. for
    /// image-only links whose accessible name comes from an `<img alt>`).
    ///
    /// **Parameters:**
    /// - `href`: The link destination
    /// - `text`: The visible link text
    /// - `aria`: Accessibility information (expanded description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
1
        href: S1,
1
        text: S2,
1
        aria: SmallAriaInfo,
1
    ) -> Self {
1
        let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
1
        link.root.set_accessibility_info(aria.to_full_info());
1
        link
1
    }
    /// Creates an input element with type, name, and accessibility information.
    ///
    /// Use [`Dom::create_input_no_a11y`] to skip the accessibility information.
    ///
    /// **Parameters:**
    /// - `input_type`: The input type (text, password, email, etc.)
    /// - `name`: The form field name
    /// - `label`: Base accessibility label
    /// - `aria`: Additional accessibility information (description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
1
        input_type: S1,
1
        name: S2,
1
        label: S3,
1
        aria: SmallAriaInfo,
1
    ) -> Self {
1
        let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
1
        input.root.set_accessibility_info(aria.to_full_info());
1
        input
1
    }
    /// Creates a textarea with name and accessibility information.
    ///
    /// Use [`Dom::create_textarea_no_a11y`] to skip the accessibility information.
    ///
    /// **Parameters:**
    /// - `name`: The form field name
    /// - `label`: Base accessibility label
    /// - `aria`: Additional accessibility information (description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
1
        name: S1,
1
        label: S2,
1
        aria: SmallAriaInfo,
1
    ) -> Self {
1
        let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
1
        textarea.root.set_accessibility_info(aria.to_full_info());
1
        textarea
1
    }
    /// Creates a select dropdown with name and accessibility information.
    ///
    /// Use [`Dom::create_select_no_a11y`] to skip the accessibility information.
    ///
    /// **Parameters:**
    /// - `name`: The form field name
    /// - `label`: Base accessibility label
    /// - `aria`: Additional accessibility information (description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
1
        name: S1,
1
        label: S2,
1
        aria: SmallAriaInfo,
1
    ) -> Self {
1
        let mut select = Self::create_select_no_a11y(name.into(), label.into());
1
        select.root.set_accessibility_info(aria.to_full_info());
1
        select
1
    }
    /// Creates a table with caption and accessibility information.
    ///
    /// Use [`Dom::create_table_no_a11y`] to skip the caption and accessibility
    /// information.
    ///
    /// **Parameters:**
    /// - `caption`: Table caption (visible title)
    /// - `aria`: Accessibility information describing table purpose
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
1
        let mut table = Self::create_table_no_a11y()
1
            .with_child(Self::create_caption().with_child(Self::create_text_do_not_use_without_block_level_wrapper(caption)));
1
        table.root.set_accessibility_info(aria.to_full_info());
1
        table
1
    }
    /// Creates a label for a form control with additional accessibility information.
    ///
    /// Use [`Dom::create_label_no_a11y`] to skip the accessibility information.
    ///
    /// **Parameters:**
    /// - `for_id`: The ID of the associated form control
    /// - `text`: The visible label text
    /// - `aria`: Additional accessibility information (description, etc.)
    #[inline]
    #[allow(clippy::needless_pass_by_value)] // owned azul C-ABI value taken by value (FFI ownership-transfer convention)
1
    pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
1
        for_id: S1,
1
        text: S2,
1
        aria: SmallAriaInfo,
1
    ) -> Self {
1
        let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
1
        label.root.set_accessibility_info(aria.to_full_info());
1
        label
1
    }
    /// Parse XML/XHTML string into a DOM
    ///
    /// This is a simple wrapper that parses XML and converts it to a DOM.
    /// For now, it just creates a text node with the content since full XML parsing
    /// requires the xml feature and more complex parsing logic.
    #[cfg(feature = "xml")]
    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
        // TODO: Implement full XML parsing
        // For now, just create a text node showing that XML was loaded
        Self::create_text_do_not_use_without_block_level_wrapper(format!(
            "XML content loaded ({} bytes)",
            xml_str.as_ref().len()
        ))
    }
    /// Parse XML/XHTML string into a DOM (fallback without xml feature)
    #[cfg(not(feature = "xml"))]
    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
        Self::create_text_do_not_use_without_block_level_wrapper(format!(
            "XML parsing requires 'xml' feature ({} bytes)",
            xml_str.as_ref().len()
        ))
    }
    // Swaps `self` with a default DOM, necessary for builder methods
    #[inline]
    #[must_use]
1
    pub const fn swap_with_default(&mut self) -> Self {
1
        let mut s = Self {
1
            root: NodeData::create_div(),
1
            children: DomVec::from_const_slice(&[]),
1
            css: azul_css::css::CssVec::from_const_slice(&[]),
1
            estimated_total_children: 0,
1
        };
1
        mem::swap(&mut s, self);
1
        s
1
    }
    /// AUDIT: recompute the authoritative descendant count (1 per descendant)
    /// from `children`, without mutating anything. Used by the debug-only
    /// consistency assertions in the builder methods so a stale
    /// `estimated_total_children` (from direct `children` mutation) is caught in
    /// tests before it can under-allocate the arena in
    /// `convert_dom_into_compact_dom`.
    #[must_use]
7282
    pub fn recompute_estimated_total_children(&self) -> usize {
7282
        self.children
7282
            .iter()
7282
            .map(|c| c.recompute_estimated_total_children() + 1)
7282
            .sum()
7282
    }
    #[inline]
714443
    pub fn add_child(&mut self, child: Self) {
        // AUDIT: cheap ONE-LEVEL consistency check (O(child.children), not the
        // full O(subtree) recompute — `add_child` is a per-child hot path, so a
        // recursive assert would make debug DOM construction O(n^2)). Assuming
        // grandchildren are already consistent (they are when the tree is built
        // bottom-up), this catches a direct mutation of `child.children` that
        // skipped `fixup_children_estimated()`.
714443
        debug_assert_eq!(
            child.estimated_total_children,
            child
                .children
                .iter()
                .map(|c| c.estimated_total_children + 1)
                .sum::<usize>(),
            "Dom.estimated_total_children desynced for added child; call \
             fixup_children_estimated() after mutating `children` directly",
        );
714443
        let estimated = child.estimated_total_children;
714443
        let mut v: DomVec = Vec::new().into();
714443
        mem::swap(&mut v, &mut self.children);
714443
        let mut v = v.into_library_owned_vec();
714443
        v.push(child);
714443
        self.children = v.into();
714443
        self.estimated_total_children += estimated + 1;
714443
    }
    #[inline]
388906
    pub fn set_children(&mut self, children: DomVec) {
        // AUDIT: one-level check per child (see `add_child`) — verifies each
        // child's own cached estimate is internally consistent before we trust
        // it, without an O(subtree) recompute.
388906
        debug_assert!(
            children.iter().all(|c| c.estimated_total_children
                == c
                    .children
                    .iter()
                    .map(|g| g.estimated_total_children + 1)
                    .sum::<usize>()),
            "Dom.estimated_total_children desynced in set_children; a child's own \
             estimate was stale — call fixup_children_estimated() first",
        );
388906
        let children_estimated = children
388906
            .iter()
451120
            .map(|s| s.estimated_total_children + 1)
388906
            .sum();
388906
        self.children = children;
388906
        self.estimated_total_children = children_estimated;
388906
    }
    #[must_use]
    pub fn copy_except_for_root(&mut self) -> Self {
        Self {
            root: self.root.copy_special(),
            children: self.children.clone(),
            css: self.css.clone(),
            estimated_total_children: self.estimated_total_children,
        }
    }
162
    #[must_use] pub const fn node_count(&self) -> usize {
        // `saturating_add`, not `+`. `estimated_total_children` is a PUBLIC
        // field, so a caller can put `usize::MAX` in it. With `+` that is a
        // panic in debug and a silent wrap to **0** in release — and 0 is the
        // worst available answer, because it reads as "this DOM is empty" and
        // every caller believes it. Saturating gives the same result for every
        // sane value and a defined, obviously-wrong-way-up one otherwise.
162
        self.estimated_total_children.saturating_add(1)
162
    }
    /// Push a parsed `Css` onto this Dom subtree's `.css` list (the
    /// `@scope`-like mechanism that `with_css(&str)` also feeds — a string
    /// parses to a `Css` and lands here). The cascade selector-matches every
    /// entry against the subtree; later pushes win at equal specificity.
    /// This is the low-level Css-struct entry point; prefer `with_css(&str)`.
37622
    pub fn add_component_css(&mut self, css: azul_css::css::Css) {
37622
        let mut v = Vec::new().into();
37622
        mem::swap(&mut v, &mut self.css);
37622
        let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
37622
        v.push(css);
37622
        self.css = v.into();
37622
    }
    /// Builder form of [`Self::add_component_css`]: attach a parsed
    /// stylesheet and hand the `Dom` back, so a component builds in one
    /// expression. `with_css(&str)` is the same thing for a string that still
    /// has to be parsed.
    #[must_use]
    pub fn with_component_css(mut self, css: azul_css::css::Css) -> Self {
        self.add_component_css(css);
        self
    }
    /// Replace the subtree's entire component-level CSS list with the
    /// provided one. Use `add_component_css` / `with_component_css` for
    /// stacking; this is the wholesale-replace form.
1
    pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
1
        self.css = css;
1
    }
    #[inline]
388866
    #[must_use] pub fn with_children(mut self, children: DomVec) -> Self {
388866
        self.set_children(children);
388866
        self
388866
    }
    #[inline]
709326
    #[must_use] pub fn with_child(mut self, child: Self) -> Self {
709326
        self.add_child(child);
709326
        self
709326
    }
    #[inline]
    #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
        self.root.set_node_type(node_type);
        self
    }
    #[inline]
4
    #[must_use] pub fn with_id(mut self, id: AzString) -> Self {
4
        self.root.add_id(id);
4
        self
4
    }
    #[inline]
580
    #[must_use] pub fn with_class(mut self, class: AzString) -> Self {
580
        self.root.add_class(class);
580
        self
580
    }
    #[inline]
    #[must_use]
1430
    pub fn with_callback<C: Into<CoreCallback>>(
1430
        mut self,
1430
        event: EventFilter,
1430
        data: RefAny,
1430
        callback: C,
1430
    ) -> Self {
1430
        self.root.add_callback(event, data, callback);
1430
        self
1430
    }
    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
    #[inline]
    #[must_use] pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
        self.root.add_css_property(prop);
        self
    }
    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
    #[inline]
    pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
        self.root.add_css_property(prop);
    }
    #[inline]
    pub fn add_class(&mut self, class: AzString) {
        self.root.add_class(class);
    }
    #[inline]
    pub fn add_callback<C: Into<CoreCallback>>(
        &mut self,
        event: EventFilter,
        data: RefAny,
        callback: C,
    ) {
        self.root.add_callback(event, data, callback);
    }
    #[inline]
120
    pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
120
        self.root.set_tab_index(tab_index);
120
    }
    #[inline]
282
    pub const fn set_contenteditable(&mut self, contenteditable: bool) {
282
        self.root.set_contenteditable(contenteditable);
282
    }
    #[inline]
261100
    #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
261100
        self.root.set_tab_index(tab_index);
261100
        self
261100
    }
    #[inline]
3710
    #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
3710
        self.root.set_contenteditable(contenteditable);
3710
        self
3710
    }
    #[inline]
17850
    #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
17850
        self.root.set_dataset(data);
17850
        self
17850
    }
    /// Attach a presence-animation function to this node — see
    /// [`NodeData::add_animation_callback`].
    #[must_use] pub fn with_animation_callback(
        mut self,
        name: AzString,
        callback: crate::resources::ZombieAnimCallback,
        data: RefAny,
    ) -> Self {
        self.root.add_animation_callback(name, callback, data);
        self
    }
    #[inline]
639797
    #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
639797
        self.root.set_ids_and_classes(ids_and_classes);
639797
        self
639797
    }
    /// Adds an attribute to this DOM element.
    #[inline]
3242
    #[must_use] pub fn with_attribute(mut self, attr: AttributeType) -> Self {
3242
        let mut attrs = self.root.attributes().clone();
3242
        let mut v = attrs.into_library_owned_vec();
3242
        v.push(attr);
3242
        self.root.set_attributes(v.into());
3242
        self
3242
    }
    /// Adds multiple attributes to this DOM element.
    #[inline]
1
    #[must_use] pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
1
        self.root.set_attributes(attributes);
1
        self
1
    }
    #[inline]
296810
    #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
296810
        self.root.callbacks = callbacks;
296810
        self
296810
    }
    /// Legacy: builder-form for the flat property+conditions list. Each entry
    /// becomes a single-declaration rule at `rule_priority::INLINE`.
    #[inline]
883152
    #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
883152
        self.root.style = css_props.into();
883152
        self
883152
    }
    /// Builder-form for setting the inline `Css` directly.
    #[inline]
    #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
        self.root.style = style;
        self
    }
    /// Assigns a stable key to the root node of this DOM for reconciliation.
    ///
    /// This is crucial for performance and correct state preservation when
    /// lists of items change order or items are inserted/removed.
    ///
    /// # Example
    /// ```rust
    /// # use azul_core::dom::Dom;
    /// Dom::create_div()
    ///     .with_key("user-avatar-123");
    /// ```
    #[inline]
    #[must_use]
    pub fn with_key<K: Hash>(mut self, key: K) -> Self {
        self.root.set_key(key);
        self
    }
    /// Registers a callback to merge dataset state from the previous frame.
    ///
    /// This is used for components that maintain heavy internal state (video players,
    /// WebGL contexts, network connections) that should not be destroyed and recreated
    /// on every render frame.
    ///
    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
    /// returns the `RefAny` that should be used for the new node.
    #[inline]
    #[must_use]
143
    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
143
        self.root.set_merge_callback(callback);
143
        self
143
    }
    /// Parse and set CSS styles with full selector support.
    ///
    /// This is the unified API for setting inline CSS on a DOM node. It supports:
    /// - Simple properties: `color: red; font-size: 14px;`
    /// - Pseudo-selectors: `:hover { background: blue; }`
    /// - @-rules: `@os linux { font-size: 14px; }`
    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
    ///
    /// # Examples
    /// ```rust
    /// # use azul_core::dom::Dom;
    /// // Simple inline styles
    /// Dom::create_div().with_css("color: red; font-size: 14px;");
    ///
    /// // With hover and active states
    /// Dom::create_div().with_css("
    ///     color: blue;
    ///     :hover { color: red; }
    ///     :active { color: green; }
    /// ");
    ///
    /// // OS-specific with nested hover
    /// Dom::create_div().with_css("
    ///     font-size: 12px;
    ///     @os linux { font-size: 14px; :hover { color: red; }}
    ///     @os windows { font-size: 13px; }
    /// ");
    /// ```
37600
    pub fn set_css(&mut self, style: &str) {
        // Unified, `@scope`-like model: a CSS string parses into a `Css` struct that is
        // pushed onto THIS Dom subtree's `.css` vec, where the cascade selector-matches
        // it against the subtree (`collect_css_from_dom` → `CssPropertyCache::restyle`).
        // `with_css` is the single CSS entry point — there is no separate node-only inline
        // path, and the old `with_component_css` is folded into this. A bare-declaration
        // string (`color: red`) parses to `* { color: red }` and so applies to the whole
        // subtree, exactly like attaching a `@scope { :scope { ... } }` block.
37600
        self.add_component_css(azul_css::css::Css::parse_inline(style));
37600
    }
    /// Builder method for `set_css`
37598
    #[must_use] pub fn with_css(mut self, style: &str) -> Self {
37598
        self.set_css(style);
37598
        self
37598
    }
    /// Sets the context menu for the root node
    #[inline]
160
    pub fn set_context_menu(&mut self, context_menu: Menu) {
160
        self.root.set_context_menu(context_menu);
160
    }
    #[inline]
160
    #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
160
        self.set_context_menu(context_menu);
160
        self
160
    }
    /// Sets the menu bar for the root node
    #[inline]
    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
        self.root.set_menu_bar(menu_bar);
    }
    #[inline]
    #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
        self.set_menu_bar(menu_bar);
        self
    }
    #[inline]
    #[must_use] pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
        self.root.set_clip_mask(clip_mask);
        self
    }
    #[inline]
10
    #[must_use] pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
10
        self.root.set_svg_data(SvgNodeData::Path(clip));
10
        self
10
    }
    #[inline]
    #[must_use] pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
        self.root.set_svg_data(data);
        self
    }
    #[inline]
20
    #[must_use] pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
20
        self.root.set_accessibility_info(accessibility_info);
20
        self
20
    }
912039
    pub fn fixup_children_estimated(&mut self) -> usize {
912039
        if self.children.is_empty() {
437111
            self.estimated_total_children = 0;
437111
        } else {
474928
            self.estimated_total_children = self
474928
                .children
474928
                .iter_mut()
861921
                .map(|s| s.fixup_children_estimated() + 1)
474928
                .sum();
        }
912039
        self.estimated_total_children
912039
    }
}
impl core::iter::FromIterator<Self> for Dom {
18
    fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
18
        let mut estimated_total_children = 0;
18
        let children = iter
18
            .into_iter()
52
            .inspect(|c| {
52
                estimated_total_children += c.estimated_total_children + 1;
52
            })
18
            .collect::<Vec<Self>>();
18
        Self {
18
            root: NodeData::create_div(),
18
            children: children.into(),
18
            css: azul_css::css::CssVec::from_const_slice(&[]),
18
            estimated_total_children,
18
        }
18
    }
}
impl fmt::Debug for Dom {
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4
        fn print_dom(d: &Dom, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4
            write!(f, "Dom {{\r\n")?;
4
            write!(f, "\troot: {:#?}\r\n", d.root)?;
4
            write!(
4
                f,
4
                "\testimated_total_children: {:#?}\r\n",
                d.estimated_total_children
            )?;
4
            write!(f, "\tchildren: [\r\n")?;
7
            for c in &d.children {
3
                print_dom(c, f)?;
            }
4
            write!(f, "\t]\r\n")?;
4
            write!(f, "}}\r\n")?;
4
            Ok(())
4
        }
1
        print_dom(self, f)
1
    }
}
#[cfg(test)]
mod audit_tests {
    use super::*;
    #[test]
1
    fn node_count_matches_recompute() {
        // root + [A(+grandchild), B] = 3 descendants, node_count 4.
1
        let dom = Dom::create_div()
1
            .with_child(Dom::create_div().with_child(Dom::create_div()))
1
            .with_child(Dom::create_div());
1
        assert_eq!(
            dom.estimated_total_children,
1
            dom.recompute_estimated_total_children()
        );
1
        assert_eq!(dom.estimated_total_children, 3);
1
        assert_eq!(dom.node_count(), 4);
1
    }
    #[test]
1
    fn single_node_dom_node_count() {
1
        let dom = Dom::create_div();
1
        assert_eq!(dom.estimated_total_children, 0);
1
        assert_eq!(dom.node_count(), 1);
1
        assert_eq!(dom.recompute_estimated_total_children(), 0);
1
    }
    #[test]
1
    fn fixup_repairs_desynced_estimate() {
1
        let mut dom = Dom::create_div().with_child(Dom::create_div());
        // Corrupt the public cached field directly.
1
        dom.estimated_total_children = 999;
1
        let repaired = dom.fixup_children_estimated();
1
        assert_eq!(repaired, 1);
1
        assert_eq!(
            dom.estimated_total_children,
1
            dom.recompute_estimated_total_children()
        );
1
    }
    // The debug_assert only fires with debug_assertions enabled.
    #[cfg(debug_assertions)]
    #[test]
    #[should_panic(expected = "desynced")]
    fn add_child_with_stale_estimate_panics_in_debug() {
        let mut child = Dom::create_div().with_child(Dom::create_div());
        child.estimated_total_children = 0; // corrupt: should be 1
        let mut parent = Dom::create_div();
        parent.add_child(child);
    }
    // NodeData carries a manual `unsafe impl Send`. This is a compile-time
    // assertion that the marker holds (fails to build if a non-Send field is
    // ever added), documenting the invariant the unsafe impl relies on.
    #[test]
1
    fn node_data_is_send() {
1
        fn assert_send<T: Send>() {}
1
        assert_send::<NodeData>();
1
    }
    // Exercises the `core::ptr::write` unsafe path in `copy_special_moving_complex`:
    // the boxed Text `node_type` must be MOVED bitwise into the copy (box pointer
    // preserved, string intact), `self.node_type` must be left as `Div`, and the
    // moved-out `style`/`extra` must land on the copy. Small heap-only tree, so
    // Miri can validate the raw write / box ownership transfer for UB.
    #[test]
1
    fn copy_special_moving_complex_moves_text_node_type() {
1
        let mut nd = NodeData::create_text_do_not_use_without_block_level_wrapper("hello").with_css("color: red;");
1
        assert!(!nd.style.rules.is_empty(), "precondition: style set");
1
        let copy = nd.copy_special_moving_complex();
        // The Text box was transferred to the copy with its string intact.
1
        match copy.get_node_type() {
1
            NodeType::Text(s) => assert_eq!(s.as_ref().as_str(), "hello"),
            other => panic!("expected Text node_type on copy, got {other:?}"),
        }
        // The source's node_type was replaced with the heap-free Div placeholder.
1
        assert!(matches!(nd.get_node_type(), NodeType::Div));
        // `style` was moved out of `self` onto the copy.
1
        assert!(nd.style.rules.is_empty());
1
        assert!(!copy.style.rules.is_empty());
1
    }
    // A non-boxed (Div) node_type must also survive the ptr::write path unchanged.
    #[test]
1
    fn copy_special_moving_complex_moves_div_node_type() {
1
        let mut nd = NodeData::create_div();
1
        let copy = nd.copy_special_moving_complex();
1
        assert!(matches!(copy.get_node_type(), NodeType::Div));
1
        assert!(matches!(nd.get_node_type(), NodeType::Div));
1
    }
}
#[cfg(test)]
#[allow(clippy::cast_possible_wrap, clippy::too_many_lines)]
mod autotest_generated {
    use super::*;
    // ---------------------------------------------------------------------
    // upsert_inline_css_property
    // ---------------------------------------------------------------------
    /// The runtime-patch upsert must REPLACE the unconditional declaration of
    /// the same type, KEEP every other inline property (the content
    /// chokepoint used to wipe the whole inline style, so a patched panel
    /// lost its `position: absolute`), KEEP conditional declarations, and
    /// stay bounded under repeated toggles.
    #[test]
    fn upsert_inline_css_property_replaces_only_the_unconditional_same_type() {
        use azul_css::dynamic_selector::{
            CssPropertyWithConditions, DynamicSelector, PseudoStateType,
        };
        use azul_css::props::layout::display::LayoutDisplay;
        use azul_css::props::layout::position::LayoutPosition;
        use azul_css::props::property::{CssProperty, CssPropertyType};
        let mut node = NodeData::create_div();
        node.set_css_props(
            vec![
                CssPropertyWithConditions::simple(CssProperty::const_position(
                    LayoutPosition::Absolute,
                )),
                CssPropertyWithConditions::simple(CssProperty::const_display(
                    LayoutDisplay::None,
                )),
                // A conditional (hover) display declaration must survive.
                CssPropertyWithConditions {
                    property: CssProperty::const_display(LayoutDisplay::Block),
                    apply_if: vec![DynamicSelector::PseudoState(PseudoStateType::Hover)]
                        .into(),
                },
            ]
            .into(),
        );
        node.upsert_inline_css_property(CssProperty::const_display(LayoutDisplay::Flex));
        let collect = |node: &NodeData| -> Vec<(CssProperty, bool)> {
            node.style
                .iter_inline_properties()
                .map(|(p, conds)| (p.clone(), conds.as_ref().is_empty()))
                .collect()
        };
        let props = collect(&node);
        let unconditional_displays: Vec<&CssProperty> = props
            .iter()
            .filter(|(p, uncond)| *uncond && p.get_type() == CssPropertyType::Display)
            .map(|(p, _)| p)
            .collect();
        assert_eq!(
            unconditional_displays,
            vec![&CssProperty::const_display(LayoutDisplay::Flex)],
            "exactly ONE unconditional display remains: the patched value"
        );
        assert!(
            props.iter().any(|(p, uncond)| *uncond
                && *p == CssProperty::const_position(LayoutPosition::Absolute)),
            "unrelated inline properties must survive the patch"
        );
        assert!(
            props.iter().any(|(p, uncond)| !*uncond
                && *p == CssProperty::const_display(LayoutDisplay::Block)),
            "conditional (hover) declarations must survive the patch"
        );
        // Toggle many times: the style must not grow without bound.
        let len_before = node.style.rules.as_ref().len();
        for i in 0..20 {
            let v = if i % 2 == 0 { LayoutDisplay::None } else { LayoutDisplay::Flex };
            node.upsert_inline_css_property(CssProperty::const_display(v));
        }
        assert_eq!(
            node.style.rules.as_ref().len(),
            len_before,
            "repeated upserts of the same type must not grow the inline style"
        );
    }
    // ---------------------------------------------------------------------
    // helpers
    // ---------------------------------------------------------------------
    fn hash_of<T: Hash>(t: &T) -> u64 {
        let mut h = crate::hash::DefaultHasher::new();
        t.hash(&mut h);
        h.finish()
    }
    extern "C" fn merge_cb_a(new_data: RefAny, _old: RefAny) -> RefAny {
        new_data
    }
    extern "C" fn merge_cb_b(_new: RefAny, old_data: RefAny) -> RefAny {
        old_data
    }
    /// A `VirtualViewCallbackType`-shaped stub. Never invoked — the tests only need
    /// a well-typed callback to hang off a `NodeType::VirtualView` node.
    extern "C" fn virtual_view_cb(
        _data: RefAny,
        _info: crate::callbacks::VirtualViewCallbackInfo,
    ) -> crate::callbacks::VirtualViewReturn {
        unreachable!("virtual view callback is never invoked by these tests")
    }
    fn virtual_view_callback() -> VirtualViewCallback {
        VirtualViewCallback {
            cb: virtual_view_cb,
            ctx: OptionRefAny::None,
        }
    }
    /// A ~100k-char string with multi-byte codepoints, for "huge input" cases.
    fn huge_unicode_string() -> String {
        "ä🎉本".repeat(25_000)
    }
    /// Every `AttributeType` variant, so invariant sweeps can't silently miss one.
    fn all_attribute_variants() -> Vec<AttributeType> {
        let nv = || AttributeNameValue {
            attr_name: "data-x".into(),
            value: "v".into(),
        };
        vec![
            AttributeType::Id("i".into()),
            AttributeType::Class("c".into()),
            AttributeType::AriaLabel("l".into()),
            AttributeType::AriaLabelledBy("lb".into()),
            AttributeType::AriaDescribedBy("db".into()),
            AttributeType::AriaRole("r".into()),
            AttributeType::AriaState(nv()),
            AttributeType::AriaProperty(nv()),
            AttributeType::Href("h".into()),
            AttributeType::Rel("rel".into()),
            AttributeType::Target("t".into()),
            AttributeType::Src("s".into()),
            AttributeType::Alt("a".into()),
            AttributeType::Title("ti".into()),
            AttributeType::Name("n".into()),
            AttributeType::Value("v".into()),
            AttributeType::InputType("text".into()),
            AttributeType::Placeholder("p".into()),
            AttributeType::Required,
            AttributeType::Disabled,
            AttributeType::Readonly,
            AttributeType::CheckedTrue,
            AttributeType::CheckedFalse,
            AttributeType::Selected,
            AttributeType::Max("10".into()),
            AttributeType::Min("0".into()),
            AttributeType::Step("1".into()),
            AttributeType::Pattern(".*".into()),
            AttributeType::MinLength(i32::MIN),
            AttributeType::MaxLength(i32::MAX),
            AttributeType::Autocomplete("off".into()),
            AttributeType::Scope("row".into()),
            AttributeType::ColSpan(-1),
            AttributeType::RowSpan(0),
            AttributeType::TabIndex(i32::MIN),
            AttributeType::Focusable,
            AttributeType::Lang("en".into()),
            AttributeType::Dir("rtl".into()),
            AttributeType::ContentEditable(true),
            AttributeType::Draggable(false),
            AttributeType::Hidden,
            AttributeType::Data(nv()),
            AttributeType::Custom(nv()),
        ]
    }
    /// A spread of `NodeType`s, including every payload-carrying variant.
    fn representative_node_types() -> Vec<NodeType> {
        vec![
            NodeType::Html,
            NodeType::Body,
            NodeType::Div,
            NodeType::Br,
            NodeType::Button,
            NodeType::Input,
            NodeType::TextArea,
            NodeType::Select,
            NodeType::A,
            NodeType::H1,
            NodeType::H6,
            NodeType::Table,
            NodeType::Td,
            NodeType::Svg,
            NodeType::SvgPath,
            NodeType::SvgText("svg text".into()),
            NodeType::SvgImage(ImageRef::null_image(
                1,
                1,
                crate::resources::RawImageFormat::R8,
                Vec::new(),
            )),
            NodeType::Before,
            NodeType::After,
            NodeType::Marker,
            NodeType::Placeholder,
            NodeType::Text(BoxOrStatic::heap(AzString::from("hello"))),
            NodeType::Image(BoxOrStatic::heap(ImageRef::null_image(
                2,
                2,
                crate::resources::RawImageFormat::RGBA8,
                Vec::new(),
            ))),
            NodeType::VirtualView,
            NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))),
            NodeType::GeolocationProbe(crate::geolocation::GeolocationProbeConfig::default()),
        ]
    }
    // =====================================================================
    // NodeFlags — bit-packing round-trips, boundaries, field independence
    // =====================================================================
    #[test]
    fn node_flags_new_is_empty_and_matches_default() {
        let f = NodeFlags::new();
        assert_eq!(f.inner, 0);
        assert_eq!(f, NodeFlags::default());
        assert!(!f.is_contenteditable());
        assert!(!f.is_anonymous());
        assert_eq!(f.get_tab_index(), None);
    }
    #[test]
    fn node_flags_tab_index_round_trips_for_all_variants() {
        for ti in [
            None,
            Some(TabIndex::Auto),
            Some(TabIndex::NoKeyboardFocus),
            Some(TabIndex::OverrideInParent(0)),
            Some(TabIndex::OverrideInParent(1)),
            Some(TabIndex::OverrideInParent(1_000)),
        ] {
            let mut f = NodeFlags::new();
            f.set_tab_index(ti);
            assert_eq!(f.get_tab_index(), ti, "round-trip failed for {ti:?}");
        }
    }
    #[test]
    fn node_flags_tab_index_round_trips_at_the_28_bit_boundary() {
        // The value field is bits [27:0], so 2^28 - 1 is the largest exactly
        // representable OverrideInParent value.
        const MAX_EXACT: u32 = (1 << 28) - 1;
        let mut f = NodeFlags::new();
        f.set_tab_index(Some(TabIndex::OverrideInParent(MAX_EXACT)));
        assert_eq!(
            f.get_tab_index(),
            Some(TabIndex::OverrideInParent(MAX_EXACT))
        );
    }
    #[test]
    fn node_flags_tab_index_above_28_bits_truncates_without_corrupting_other_flags() {
        // AUDIT: `set_tab_index` masks the value with TAB_VALUE_MASK ((1 << 28) - 1),
        // so any OverrideInParent >= 2^28 is SILENTLY TRUNCATED rather than rejected
        // or saturated. That is lossy, but the safety-critical property is that the
        // overflowing bits must not bleed into the anonymous / contenteditable /
        // tab-variant bits. Pin both facts.
        const OVERFLOW: u32 = 1 << 28;
        let mut f = NodeFlags::new();
        f.set_tab_index(Some(TabIndex::OverrideInParent(OVERFLOW)));
        assert_eq!(
            f.get_tab_index(),
            Some(TabIndex::OverrideInParent(0)),
            "2^28 truncates to 0 (documented lossiness)"
        );
        assert!(!f.is_anonymous(), "overflow bit must not set ANONYMOUS");
        assert!(!f.is_contenteditable());
        let mut f = NodeFlags::new();
        f.set_tab_index(Some(TabIndex::OverrideInParent(u32::MAX)));
        assert_eq!(
            f.get_tab_index(),
            Some(TabIndex::OverrideInParent((1 << 28) - 1)),
            "u32::MAX truncates to the 28-bit mask"
        );
        assert!(!f.is_anonymous(), "u32::MAX must not set ANONYMOUS");
        assert!(!f.is_contenteditable(), "u32::MAX must not set CONTENTEDITABLE");
    }
    #[test]
    fn node_flags_set_tab_index_preserves_contenteditable_and_anonymous() {
        let mut f = NodeFlags::new();
        f.set_contenteditable_mut(true);
        f.set_anonymous(true);
        for ti in [
            None,
            Some(TabIndex::Auto),
            Some(TabIndex::NoKeyboardFocus),
            // NodeFlags packs the override into a documented 28-bit field
            // (TAB_VALUE_MASK), so u32::MAX would truncate to (1<<28)-1 and not
            // round-trip. (1<<28)-1 IS the largest encodable value -- still the
            // boundary case, but one this API can actually represent.
            Some(TabIndex::OverrideInParent((1 << 28) - 1)),
            Some(TabIndex::OverrideInParent(7)),
        ] {
            f.set_tab_index(ti);
            assert!(f.is_contenteditable(), "contenteditable lost for {ti:?}");
            assert!(f.is_anonymous(), "anonymous lost for {ti:?}");
            assert_eq!(f.get_tab_index(), ti);
        }
    }
    #[test]
    fn node_flags_set_contenteditable_preserves_tab_index_and_anonymous() {
        let mut f = NodeFlags::new();
        f.set_anonymous(true);
        f.set_tab_index(Some(TabIndex::OverrideInParent(12_345)));
        f.set_contenteditable_mut(true);
        assert!(f.is_contenteditable());
        assert!(f.is_anonymous());
        assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
        f.set_contenteditable_mut(false);
        assert!(!f.is_contenteditable());
        assert!(f.is_anonymous());
        assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
    }
    #[test]
    fn node_flags_set_anonymous_preserves_tab_index_and_contenteditable() {
        let mut f = NodeFlags::new();
        f.set_contenteditable_mut(true);
        f.set_tab_index(Some(TabIndex::NoKeyboardFocus));
        f.set_anonymous(true);
        assert!(f.is_anonymous());
        assert!(f.is_contenteditable());
        assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
        f.set_anonymous(false);
        assert!(!f.is_anonymous());
        assert!(f.is_contenteditable());
        assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
    }
    #[test]
    fn node_flags_consecutive_set_contenteditable_is_idempotent() {
        let mut f = NodeFlags::new();
        f.set_contenteditable_mut(true);
        let once = f;
        f.set_contenteditable_mut(true);
        assert_eq!(f, once, "setting twice must not toggle");
    }
    #[test]
    fn node_flags_builder_and_mut_setter_agree() {
        for v in [true, false] {
            let builder = NodeFlags::new().set_contenteditable(v);
            let mut mutated = NodeFlags::new();
            mutated.set_contenteditable_mut(v);
            assert_eq!(builder, mutated, "builder/mut disagree for {v}");
        }
    }
    #[test]
    fn node_flags_all_bits_set_decodes_without_panicking() {
        // Adversarial: a NodeFlags whose `inner` was never produced by the setters
        // (e.g. deserialized from a hostile FFI caller). Every getter must still
        // return a deterministic value instead of panicking.
        let f = NodeFlags { inner: u32::MAX };
        assert!(f.is_contenteditable());
        assert!(f.is_anonymous());
        // bits [30:29] == 0b11 == TAB_NO_KEYBOARD
        assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
    }
    #[test]
    fn node_flags_get_tab_index_is_total_over_the_tag_bits() {
        // The `_ => None` arm of get_tab_index is unreachable (2 bits => 4 patterns,
        // all matched). Prove every tag pattern decodes to Some/None deterministically.
        for tag in 0u32..4 {
            for extra in [0u32, u32::MAX] {
                let inner = (tag << 29) | (extra & !(0b11 << 29));
                let f = NodeFlags { inner };
                let decoded = f.get_tab_index();
                match tag {
                    0 => assert_eq!(decoded, None),
                    1 => assert_eq!(decoded, Some(TabIndex::Auto)),
                    2 => assert!(matches!(decoded, Some(TabIndex::OverrideInParent(_)))),
                    _ => assert_eq!(decoded, Some(TabIndex::NoKeyboardFocus)),
                }
            }
        }
    }
    // =====================================================================
    // TabIndex — numeric limits
    // =====================================================================
    #[test]
    fn tab_index_default_is_auto_with_index_zero() {
        assert_eq!(TabIndex::default(), TabIndex::Auto);
        assert_eq!(TabIndex::default().get_index(), 0);
    }
    #[test]
    fn tab_index_get_index_at_numeric_limits() {
        assert_eq!(TabIndex::Auto.get_index(), 0);
        assert_eq!(TabIndex::NoKeyboardFocus.get_index(), -1);
        assert_eq!(TabIndex::OverrideInParent(0).get_index(), 0);
        // u32 -> isize must widen, never wrap negative (isize is >= 32 bits on all
        // supported targets, so u32::MAX stays positive).
        let max = TabIndex::OverrideInParent(u32::MAX).get_index();
        assert_eq!(max, u32::MAX as isize);
        assert!(max > 0, "u32::MAX must not wrap to a negative isize");
    }
    #[test]
    fn get_effective_tabindex_saturates_into_i32() {
        // Reached through NodeFlags, OverrideInParent is capped at 2^28 - 1, which
        // always fits i32 — so the i32::MAX saturation arm is not reachable via a
        // NodeData. Pin what IS reachable.
        let nd = NodeData::create_div().with_tab_index(TabIndex::OverrideInParent(u32::MAX));
        assert_eq!(nd.get_effective_tabindex(), Some((1 << 28) - 1));
        assert_eq!(
            NodeData::create_div()
                .with_tab_index(TabIndex::Auto)
                .get_effective_tabindex(),
            Some(0)
        );
        assert_eq!(
            NodeData::create_div()
                .with_tab_index(TabIndex::NoKeyboardFocus)
                .get_effective_tabindex(),
            Some(-1)
        );
        assert_eq!(NodeData::create_div().get_effective_tabindex(), None);
    }
    #[test]
    fn get_effective_tabindex_falls_back_to_zero_for_focus_callbacks() {
        let nd = NodeData::create_div().with_callback(
            EventFilter::Focus(FocusEventFilter::MouseDown),
            RefAny::new(0u32),
            0usize,
        );
        assert_eq!(nd.get_effective_tabindex(), Some(0));
    }
    // =====================================================================
    // TagId / ScrollTagId
    // =====================================================================
    #[test]
    fn tag_id_unique_never_returns_zero_and_never_repeats() {
        // 0 is reserved for "no tag". Other tests in this binary also allocate tags,
        // so assert distinctness rather than a specific starting value.
        let ids: Vec<TagId> = (0..512).map(|_| TagId::unique()).collect();
        for id in &ids {
            assert_ne!(id.inner, 0, "TagId 0 is reserved for 'no tag'");
        }
        let mut sorted: Vec<u64> = ids.iter().map(|t| t.inner).collect();
        sorted.sort_unstable();
        sorted.dedup();
        assert_eq!(sorted.len(), 512, "TagId::unique() handed out a duplicate");
    }
    #[test]
    fn tag_id_crate_internal_conversions_are_identity_at_limits() {
        for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
            let t = TagId { inner };
            assert_eq!(t.into_crate_internal(), t);
            assert_eq!(TagId::from_crate_internal(t), t);
            // Round-trip through both directions.
            assert_eq!(
                TagId::from_crate_internal(t.into_crate_internal()).inner,
                inner
            );
        }
    }
    #[test]
    fn tag_id_display_is_non_empty_at_numeric_limits() {
        for inner in [0u64, 1, u64::MAX] {
            let s = format!("{}", TagId { inner });
            assert!(!s.is_empty());
            assert!(s.contains(&inner.to_string()), "{s} should contain {inner}");
        }
    }
    #[test]
    fn scroll_tag_id_unique_is_distinct_and_debug_matches_display() {
        let a = ScrollTagId::unique();
        let b = ScrollTagId::unique();
        assert_ne!(a, b);
        assert_ne!(a.inner.inner, 0);
        let s = ScrollTagId {
            inner: TagId { inner: u64::MAX },
        };
        assert_eq!(format!("{s:?}"), format!("{s}"));
        assert!(!format!("{s}").is_empty());
    }
    // =====================================================================
    // AttributeType — getters / predicates / serializer invariants
    // =====================================================================
    #[test]
    fn attribute_boolean_attrs_always_have_an_empty_value() {
        // Invariant: is_boolean() means "present == true", so there is nothing to
        // serialize on the right-hand side.
        for attr in all_attribute_variants() {
            if attr.is_boolean() {
                assert_eq!(
                    attr.value().as_str(),
                    "",
                    "boolean attr {} must have an empty value",
                    attr.name()
                );
            }
        }
    }
    #[test]
    fn attribute_name_and_value_never_panic_for_any_variant() {
        for attr in all_attribute_variants() {
            let name = attr.name();
            let value = attr.value();
            // Every built-in variant has a non-empty name; only a Custom/Data
            // attribute can carry a caller-supplied empty name (see next test).
            assert!(!name.is_empty(), "empty name for {attr:?}");
            let _ = value.as_str();
        }
    }
    #[test]
    fn attribute_custom_with_empty_name_returns_empty_name_without_panicking() {
        let attr = AttributeType::Custom(AttributeNameValue {
            attr_name: "".into(),
            value: "".into(),
        });
        assert_eq!(attr.name(), "");
        assert_eq!(attr.value().as_str(), "");
        assert!(!attr.is_boolean());
    }
    #[test]
    fn attribute_as_id_and_as_class_are_mutually_exclusive() {
        for attr in all_attribute_variants() {
            match &attr {
                AttributeType::Id(s) => {
                    assert_eq!(attr.as_id(), Some(s.as_str()));
                    assert_eq!(attr.as_class(), None);
                }
                AttributeType::Class(s) => {
                    assert_eq!(attr.as_class(), Some(s.as_str()));
                    assert_eq!(attr.as_id(), None);
                }
                _ => {
                    assert_eq!(attr.as_id(), None, "as_id must be None for {attr:?}");
                    assert_eq!(attr.as_class(), None, "as_class must be None for {attr:?}");
                }
            }
        }
    }
    #[test]
    fn attribute_numeric_values_serialize_at_i32_limits() {
        assert_eq!(
            AttributeType::MinLength(i32::MIN).value().as_str(),
            "-2147483648"
        );
        assert_eq!(
            AttributeType::MaxLength(i32::MAX).value().as_str(),
            "2147483647"
        );
        assert_eq!(AttributeType::ColSpan(0).value().as_str(), "0");
        assert_eq!(AttributeType::RowSpan(-1).value().as_str(), "-1");
        assert_eq!(
            AttributeType::TabIndex(i32::MIN).value().as_str(),
            "-2147483648"
        );
    }
    #[test]
    fn attribute_focusable_is_tabindex_zero_and_not_boolean() {
        // Boundary: `Focusable` shares the "tabindex" name with TabIndex(i32) but,
        // unlike the boolean attrs, serializes a value ("0").
        let f = AttributeType::Focusable;
        assert_eq!(f.name(), "tabindex");
        assert_eq!(f.value().as_str(), "0");
        assert!(!f.is_boolean());
        assert_eq!(AttributeType::TabIndex(0).name(), "tabindex");
    }
    #[test]
    fn attribute_checked_true_and_false_are_both_boolean_and_share_a_name() {
        // AUDIT: CheckedFalse is_boolean() == true and value() == "", so a serializer
        // that emits boolean attrs as bare names would render `checked` for the
        // *unchecked* state. Pinning current behaviour — see report.
        assert!(AttributeType::CheckedTrue.is_boolean());
        assert!(AttributeType::CheckedFalse.is_boolean());
        assert_eq!(AttributeType::CheckedTrue.name(), "checked");
        assert_eq!(AttributeType::CheckedFalse.name(), "checked");
        assert_eq!(AttributeType::CheckedFalse.value().as_str(), "");
        // The two are still distinguishable as values.
        assert_ne!(AttributeType::CheckedTrue, AttributeType::CheckedFalse);
    }
    #[test]
    fn attribute_content_editable_and_draggable_stringify_bools() {
        assert_eq!(AttributeType::ContentEditable(true).value().as_str(), "true");
        assert_eq!(
            AttributeType::ContentEditable(false).value().as_str(),
            "false"
        );
        assert_eq!(AttributeType::Draggable(true).value().as_str(), "true");
        assert_eq!(AttributeType::Draggable(false).value().as_str(), "false");
        assert!(!AttributeType::ContentEditable(false).is_boolean());
    }
    #[test]
    fn attribute_round_trips_huge_unicode_values() {
        let big = huge_unicode_string();
        let attr = AttributeType::Value(big.clone().into());
        assert_eq!(attr.value().as_str(), big.as_str());
        assert_eq!(attr.name(), "value");
        let id = AttributeType::Id(big.clone().into());
        assert_eq!(id.as_id(), Some(big.as_str()));
    }
    #[test]
    fn id_or_class_accessors_are_mutually_exclusive() {
        let id = IdOrClass::Id("my-id".into());
        let class = IdOrClass::Class("my-class".into());
        assert_eq!(id.as_id(), Some("my-id"));
        assert_eq!(id.as_class(), None);
        assert_eq!(class.as_class(), Some("my-class"));
        assert_eq!(class.as_id(), None);
        // Empty strings are legal and round-trip as Some("").
        assert_eq!(IdOrClass::Id("".into()).as_id(), Some(""));
        assert_eq!(IdOrClass::Class("".into()).as_class(), Some(""));
    }
    // =====================================================================
    // InputType
    // =====================================================================
    #[test]
    fn input_type_as_str_is_non_empty_and_unique_per_variant() {
        let all = [
            InputType::Text,
            InputType::Button,
            InputType::Checkbox,
            InputType::Color,
            InputType::Date,
            InputType::Datetime,
            InputType::DatetimeLocal,
            InputType::Email,
            InputType::File,
            InputType::Hidden,
            InputType::Image,
            InputType::Month,
            InputType::Number,
            InputType::Password,
            InputType::Radio,
            InputType::Range,
            InputType::Reset,
            InputType::Search,
            InputType::Submit,
            InputType::Tel,
            InputType::Time,
            InputType::Url,
            InputType::Week,
        ];
        let mut seen: Vec<&str> = all.iter().map(InputType::as_str).collect();
        for s in &seen {
            assert!(!s.is_empty());
            assert!(
                !s.contains(char::is_whitespace),
                "{s} is not a valid HTML attribute value"
            );
        }
        let len = seen.len();
        seen.sort_unstable();
        seen.dedup();
        assert_eq!(seen.len(), len, "two InputType variants share an as_str()");
        assert_eq!(InputType::DatetimeLocal.as_str(), "datetime-local");
        assert_eq!(InputType::Text.as_str(), "text");
    }
    // =====================================================================
    // NodeType
    // =====================================================================
    #[test]
    fn node_type_to_library_owned_round_trips_every_variant() {
        // encode == decode: the deep-copy must be value-equal to the original,
        // including the payload-carrying (boxed) variants.
        for nt in representative_node_types() {
            let owned = nt.to_library_owned_nodetype();
            assert_eq!(owned, nt, "to_library_owned_nodetype lost data for {nt:?}");
            assert_eq!(owned.get_path(), nt.get_path());
        }
    }
    #[test]
    fn node_type_get_path_and_format_never_panic() {
        for nt in representative_node_types() {
            let _tag = nt.get_path();
            let _fmt = nt.format();
            let _semantic = nt.is_semantic_for_accessibility();
        }
    }
    #[test]
    fn node_type_format_returns_content_only_for_content_variants() {
        assert_eq!(NodeType::Div.format(), None);
        assert_eq!(NodeType::Br.format(), None);
        assert_eq!(NodeType::Button.format(), None);
        assert_eq!(
            NodeType::Text(BoxOrStatic::heap(AzString::from("hi"))).format(),
            Some("hi".to_string())
        );
        assert_eq!(
            NodeType::VirtualView.format(),
            Some("virtualized-view".to_string())
        );
        assert_eq!(
            NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))).format(),
            Some("icon(home)".to_string())
        );
    }
    #[test]
    fn node_type_format_handles_empty_and_unicode_text() {
        assert_eq!(
            NodeType::Text(BoxOrStatic::heap(AzString::from(""))).format(),
            Some(String::new())
        );
        let unicode = "日本語 🎉 ünïcødé";
        assert_eq!(
            NodeType::Text(BoxOrStatic::heap(AzString::from(unicode))).format(),
            Some(unicode.to_string())
        );
    }
    #[test]
    fn node_type_format_of_geolocation_probe_survives_nan_and_infinity() {
        // Adversarial floats: the probe config is formatted with `{}`, which must
        // print NaN/inf rather than panicking.
        for max_accuracy_m in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.0, f32::MAX] {
            let cfg = crate::geolocation::GeolocationProbeConfig {
                high_accuracy: true,
                background: true,
                max_accuracy_m,
                min_interval_ms: u32::MAX,
            };
            let out = NodeType::GeolocationProbe(cfg)
                .format()
                .expect("GeolocationProbe always formats");
            assert!(out.starts_with("geolocation-probe("));
            assert!(out.contains("4294967295"), "min_interval_ms must be printed");
        }
    }
    #[test]
    fn geolocation_probe_nan_is_self_equal_and_hash_consistent() {
        // GeolocationProbeConfig gives f32 a total order via to_bits, so (unlike raw
        // f32) NaN == NaN. Eq and Hash must agree, or NodeType breaks as a HashMap key.
        let cfg = crate::geolocation::GeolocationProbeConfig {
            max_accuracy_m: f32::NAN,
            ..Default::default()
        };
        let a = NodeType::GeolocationProbe(cfg);
        let b = NodeType::GeolocationProbe(cfg);
        assert_eq!(a, b, "bitwise-NaN configs must compare equal");
        assert_eq!(
            hash_of(&a),
            hash_of(&b),
            "Eq == true but hashes differ: violates the Hash/Eq contract"
        );
        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
    }
    #[test]
    fn node_type_is_semantic_for_accessibility_known_true_and_false() {
        for nt in [
            NodeType::Button,
            NodeType::Input,
            NodeType::TextArea,
            NodeType::Select,
            NodeType::A,
            NodeType::H1,
            NodeType::H6,
            NodeType::Article,
            NodeType::Nav,
            NodeType::Main,
        ] {
            assert!(
                nt.is_semantic_for_accessibility(),
                "{nt:?} should be semantic"
            );
        }
        for nt in [
            NodeType::Div,
            NodeType::Span,
            NodeType::Br,
            NodeType::VirtualView,
            NodeType::Text(BoxOrStatic::heap(AzString::from("x"))),
        ] {
            assert!(
                !nt.is_semantic_for_accessibility(),
                "{nt:?} should not be semantic"
            );
        }
    }
    #[test]
    fn node_type_text_variants_are_content_sensitive() {
        let a = NodeType::Text(BoxOrStatic::heap(AzString::from("a")));
        let b = NodeType::Text(BoxOrStatic::heap(AzString::from("b")));
        assert_ne!(a, b);
        assert_eq!(a.get_path(), b.get_path(), "same tag, different content");
    }
    // =====================================================================
    // NodeData — attributes, ids, classes
    // =====================================================================
    #[test]
    fn node_data_default_has_no_attributes_and_no_extra_state() {
        let nd = NodeData::default();
        assert!(nd.is_node_type(NodeType::Div));
        assert!(nd.attributes().as_ref().is_empty());
        assert!(nd.get_ids_and_classes().as_ref().is_empty());
        assert!(nd.get_dataset().is_none());
        assert!(nd.get_key().is_none());
        assert!(nd.get_menu_bar().is_none());
        assert!(nd.get_context_menu().is_none());
        assert!(nd.get_svg_data().is_none());
        assert!(nd.get_image_clip_mask().is_none());
        assert!(nd.get_accessibility_info().is_none());
        assert!(nd.get_merge_callback().is_none());
        assert!(nd.get_component_origin().is_none());
        assert!(!nd.has_context_menu());
        assert!(!nd.is_contenteditable());
        assert!(!nd.is_anonymous());
        assert_eq!(nd.get_tab_index(), None);
    }
    #[test]
    fn attributes_mut_lazily_allocates_but_stays_empty() {
        let mut nd = NodeData::create_div();
        assert!(nd.attributes().as_ref().is_empty());
        let _ = nd.attributes_mut(); // allocates NodeDataExt
        assert!(
            nd.attributes().as_ref().is_empty(),
            "lazy alloc must not invent attributes"
        );
        nd.add_id("x".into());
        assert_eq!(nd.attributes().as_ref().len(), 1);
    }
    #[test]
    fn has_id_and_has_class_match_exactly_not_by_prefix() {
        let mut nd = NodeData::create_div();
        nd.add_id("header".into());
        nd.add_class("btn".into());
        assert!(nd.has_id("header"));
        assert!(nd.has_class("btn"));
        // No prefix/substring matching.
        assert!(!nd.has_id("head"));
        assert!(!nd.has_id("header2"));
        assert!(!nd.has_class("bt"));
        assert!(!nd.has_class(""));
        // Ids and classes must not cross over.
        assert!(!nd.has_class("header"));
        assert!(!nd.has_id("btn"));
    }
    #[test]
    fn has_id_matches_the_empty_string_id() {
        let mut nd = NodeData::create_div();
        assert!(!nd.has_id(""), "no ids at all => empty id must not match");
        nd.add_id("".into());
        assert!(nd.has_id(""), "an explicitly-added empty id must match");
        assert!(!nd.has_id("x"));
    }
    #[test]
    fn has_id_and_has_class_handle_unicode_and_huge_strings() {
        let unicode = "日本語-🎉-ünïcødé";
        let big = huge_unicode_string();
        let mut nd = NodeData::create_div();
        nd.add_id(unicode.into());
        nd.add_class(big.clone().into());
        assert!(nd.has_id(unicode));
        assert!(nd.has_class(big.as_str()));
        // A truncated-at-a-codepoint-boundary prefix must not match.
        assert!(!nd.has_id("日本語"));
    }
    #[test]
    fn duplicate_ids_are_kept_and_still_match() {
        let mut nd = NodeData::create_div();
        nd.add_id("dup".into());
        nd.add_id("dup".into());
        assert!(nd.has_id("dup"));
        assert_eq!(
            nd.get_ids_and_classes().as_ref().len(),
            2,
            "add_id does not deduplicate"
        );
    }
    #[test]
    fn get_ids_and_classes_preserves_insertion_order_and_kind() {
        let mut nd = NodeData::create_div();
        nd.add_id("i1".into());
        nd.add_class("c1".into());
        nd.add_id("i2".into());
        let v = nd.get_ids_and_classes();
        let v = v.as_ref();
        assert_eq!(v.len(), 3);
        assert_eq!(v[0], IdOrClass::Id("i1".into()));
        assert_eq!(v[1], IdOrClass::Class("c1".into()));
        assert_eq!(v[2], IdOrClass::Id("i2".into()));
    }
    #[test]
    fn get_ids_and_classes_ignores_non_id_class_attributes() {
        let mut nd = NodeData::create_div();
        nd.set_attributes(
            vec![
                AttributeType::Href("/x".into()),
                AttributeType::Id("i".into()),
                AttributeType::Disabled,
                AttributeType::Class("c".into()),
            ]
            .into(),
        );
        let v = nd.get_ids_and_classes();
        assert_eq!(v.as_ref().len(), 2);
    }
    #[test]
    fn set_ids_and_classes_replaces_ids_but_preserves_other_attributes() {
        // The dangerous part of set_ids_and_classes: it rebuilds the attribute vec.
        // Non-Id/Class attributes must survive.
        let mut nd = NodeData::create_div();
        nd.set_attributes(
            vec![
                AttributeType::Href("/old".into()),
                AttributeType::Id("old-id".into()),
                AttributeType::Class("old-class".into()),
                AttributeType::Disabled,
            ]
            .into(),
        );
        nd.set_ids_and_classes(vec![IdOrClass::Class("new-class".into())].into());
        assert!(!nd.has_id("old-id"), "old id must be dropped");
        assert!(!nd.has_class("old-class"), "old class must be dropped");
        assert!(nd.has_class("new-class"));
        // Href / Disabled must NOT have been collateral damage.
        let attrs = nd.attributes().as_ref();
        assert!(attrs.contains(&AttributeType::Href("/old".into())));
        assert!(attrs.contains(&AttributeType::Disabled));
        assert_eq!(attrs.len(), 3);
    }
    #[test]
    fn set_ids_and_classes_with_an_empty_vec_clears_all_ids_and_classes() {
        let mut nd = NodeData::create_div();
        nd.add_id("i".into());
        nd.add_class("c".into());
        nd.set_ids_and_classes(Vec::new().into());
        assert!(nd.get_ids_and_classes().as_ref().is_empty());
        assert!(!nd.has_id("i"));
        assert!(!nd.has_class("c"));
    }
    #[test]
    fn set_ids_and_classes_is_idempotent_when_reapplied() {
        let mut nd = NodeData::create_div();
        let ids: IdOrClassVec = vec![
            IdOrClass::Id("i".into()),
            IdOrClass::Class("c".into()),
        ]
        .into();
        nd.set_ids_and_classes(ids.clone());
        let after_first = nd.attributes().clone();
        nd.set_ids_and_classes(ids);
        assert_eq!(
            nd.attributes().as_ref(),
            after_first.as_ref(),
            "re-applying the same ids/classes must not duplicate them"
        );
    }
    #[test]
    fn with_attribute_appends_without_dropping_existing_attributes() {
        // `with_attribute` is private, so this can only be exercised from an inline
        // test module.
        let nd = NodeData::create_div()
            .with_attribute(AttributeType::Href("/a".into()))
            .with_attribute(AttributeType::Alt("alt".into()));
        let attrs = nd.attributes().as_ref();
        assert_eq!(attrs.len(), 2);
        assert_eq!(attrs[0], AttributeType::Href("/a".into()));
        assert_eq!(attrs[1], AttributeType::Alt("alt".into()));
    }
    // =====================================================================
    // NodeData — constructors
    // =====================================================================
    #[test]
    fn create_node_shorthands_produce_the_right_node_type() {
        assert!(NodeData::create_body().is_node_type(NodeType::Body));
        assert!(NodeData::create_div().is_node_type(NodeType::Div));
        assert!(NodeData::create_br().is_node_type(NodeType::Br));
        assert!(NodeData::create_button_no_a11y().is_node_type(NodeType::Button));
        assert!(NodeData::create_table_no_a11y().is_node_type(NodeType::Table));
    }
    #[test]
    fn create_text_accepts_empty_unicode_and_huge_input() {
        for s in ["", "x", "日本語 🎉"] {
            let nd = NodeData::create_text_do_not_use_without_block_level_wrapper(s);
            assert!(nd.is_text_node());
            assert_eq!(nd.get_node_type().format(), Some(s.to_string()));
        }
        let big = huge_unicode_string();
        let nd = NodeData::create_text_do_not_use_without_block_level_wrapper(big.clone());
        assert!(nd.is_text_node());
        assert_eq!(nd.get_node_type().format(), Some(big));
    }
    #[test]
    fn create_a_stores_href_and_accessibility_name() {
        let nd = NodeData::create_a("/home".into(), SmallAriaInfo::label("Home"));
        assert!(nd.is_node_type(NodeType::A));
        assert!(nd
            .attributes()
            .as_ref()
            .contains(&AttributeType::Href("/home".into())));
        let info = nd
            .get_accessibility_info()
            .expect("create_a must set accessibility info");
        assert_eq!(info.accessibility_name, OptionString::Some("Home".into()));
    }
    #[test]
    fn create_a_no_a11y_has_href_but_no_accessibility_info() {
        let nd = NodeData::create_a_no_a11y("/x".into());
        assert!(nd
            .attributes()
            .as_ref()
            .contains(&AttributeType::Href("/x".into())));
        assert!(nd.get_accessibility_info().is_none());
    }
    #[test]
    fn create_a_accepts_an_empty_href() {
        let nd = NodeData::create_a_no_a11y("".into());
        assert_eq!(
            nd.attributes().as_ref()[0],
            AttributeType::Href("".into()),
            "empty href is stored verbatim, not dropped"
        );
    }
    #[test]
    fn create_input_stores_all_three_attributes_in_order() {
        let nd = NodeData::create_input_no_a11y("text".into(), "user".into(), "Username".into());
        assert!(nd.is_node_type(NodeType::Input));
        let attrs = nd.attributes().as_ref();
        assert_eq!(attrs.len(), 3);
        assert_eq!(attrs[0], AttributeType::InputType("text".into()));
        assert_eq!(attrs[1], AttributeType::Name("user".into()));
        assert_eq!(attrs[2], AttributeType::AriaLabel("Username".into()));
    }
    #[test]
    fn create_input_with_a11y_sets_both_attributes_and_accessibility_info() {
        let nd = NodeData::create_input(
            "password".into(),
            "pw".into(),
            "Password".into(),
            SmallAriaInfo::label("Password").with_role(AccessibilityRole::Text),
        );
        assert_eq!(nd.attributes().as_ref().len(), 3);
        let info = nd.get_accessibility_info().expect("a11y info");
        assert_eq!(info.role, AccessibilityRole::Text);
    }
    #[test]
    fn create_textarea_and_select_store_name_and_label() {
        let ta = NodeData::create_textarea_no_a11y("body".into(), "Body".into());
        assert!(ta.is_node_type(NodeType::TextArea));
        assert_eq!(ta.attributes().as_ref().len(), 2);
        let sel = NodeData::create_select_no_a11y("country".into(), "Country".into());
        assert!(sel.is_node_type(NodeType::Select));
        assert_eq!(
            sel.attributes().as_ref()[0],
            AttributeType::Name("country".into())
        );
    }
    #[test]
    fn create_label_uses_a_custom_for_attribute() {
        let nd = NodeData::create_label_no_a11y("email-input".into());
        assert!(nd.is_node_type(NodeType::Label));
        assert_eq!(
            nd.attributes().as_ref()[0],
            AttributeType::Custom(AttributeNameValue {
                attr_name: "for".into(),
                value: "email-input".into(),
            })
        );
        assert_eq!(nd.attributes().as_ref()[0].name(), "for");
        assert_eq!(nd.attributes().as_ref()[0].value().as_str(), "email-input");
    }
    #[test]
    fn create_button_and_table_with_aria_set_accessibility_info() {
        let btn = NodeData::create_button(
            SmallAriaInfo::label("Save").with_role(AccessibilityRole::PushButton),
        );
        let info = btn.get_accessibility_info().expect("a11y info");
        assert_eq!(info.role, AccessibilityRole::PushButton);
        assert_eq!(info.accessibility_name, OptionString::Some("Save".into()));
        let table = NodeData::create_table(SmallAriaInfo::label("Results"));
        assert!(table.is_node_type(NodeType::Table));
        assert!(table.get_accessibility_info().is_some());
    }
    #[test]
    fn a11y_constructors_accept_empty_aria_labels() {
        let btn = NodeData::create_button(SmallAriaInfo::label(""));
        let info = btn.get_accessibility_info().expect("a11y info");
        assert_eq!(info.accessibility_name, OptionString::Some("".into()));
        // An unset role degrades to Unknown rather than panicking.
        assert_eq!(info.role, AccessibilityRole::Unknown);
    }
    #[test]
    fn create_image_and_is_node_type_round_trip() {
        let img = ImageRef::null_image(4, 4, crate::resources::RawImageFormat::RGBA8, Vec::new());
        let nd = NodeData::create_image(img.clone());
        assert!(!nd.is_text_node());
        assert_eq!(nd.get_node_type().get_path(), NodeTypeTag::Img);
        assert!(nd.is_node_type(NodeType::Image(BoxOrStatic::heap(img))));
    }
    // =====================================================================
    // NodeData — predicates
    // =====================================================================
    #[test]
    fn is_node_type_is_content_sensitive_for_text() {
        let nd = NodeData::create_text_do_not_use_without_block_level_wrapper("a");
        assert!(nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("a")))));
        assert!(
            !nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("b")))),
            "is_node_type compares payloads, not just the discriminant"
        );
        assert!(!nd.is_node_type(NodeType::Div));
    }
    #[test]
    fn is_text_node_and_is_virtual_view_node() {
        assert!(NodeData::create_text_do_not_use_without_block_level_wrapper("x").is_text_node());
        assert!(!NodeData::create_div().is_text_node());
        let vv = NodeData::create_virtual_view(RefAny::new(1u32), virtual_view_callback());
        assert!(vv.is_virtual_view_node());
        assert!(!vv.is_text_node());
        assert!(vv.get_virtual_view_node_ref().is_some());
        assert!(!NodeData::create_div().is_virtual_view_node());
        assert!(NodeData::create_div().get_virtual_view_node_ref().is_none());
    }
    #[test]
    fn has_context_menu_flips_only_after_set_context_menu() {
        let mut nd = NodeData::create_div();
        assert!(!nd.has_context_menu());
        // A menu bar is a different slot and must not be mistaken for a context menu.
        nd.set_menu_bar(Menu::create(Vec::new().into()));
        assert!(
            !nd.has_context_menu(),
            "menu_bar must not satisfy has_context_menu"
        );
        assert!(nd.get_menu_bar().is_some());
        nd.set_context_menu(Menu::create(Vec::new().into()));
        assert!(nd.has_context_menu());
        assert!(nd.get_context_menu().is_some());
    }
    #[test]
    fn with_menu_bar_and_with_context_menu_are_independent_slots() {
        let nd = NodeData::create_div()
            .with_menu_bar(Menu::create(Vec::new().into()))
            .with_context_menu(Menu::create(Vec::new().into()));
        assert!(nd.get_menu_bar().is_some());
        assert!(nd.get_context_menu().is_some());
        assert!(nd.has_context_menu());
    }
    #[test]
    fn is_focusable_for_naturally_focusable_and_opted_in_nodes() {
        for nt in [
            NodeType::A,
            NodeType::Button,
            NodeType::Input,
            NodeType::Select,
            NodeType::TextArea,
        ] {
            assert!(
                NodeData::create_node(nt.clone()).is_focusable(),
                "{nt:?} is naturally focusable"
            );
        }
        assert!(!NodeData::create_div().is_focusable());
        assert!(NodeData::create_div()
            .with_contenteditable(true)
            .is_focusable());
        assert!(NodeData::create_div()
            .with_tab_index(TabIndex::NoKeyboardFocus)
            .is_focusable());
        assert!(NodeData::create_div()
            .with_callback(
                EventFilter::Focus(FocusEventFilter::MouseDown),
                RefAny::new(0u32),
                0usize,
            )
            .is_focusable());
        // A non-focus callback must NOT make a plain div focusable.
        assert!(!NodeData::create_div()
            .with_callback(
                EventFilter::Hover(HoverEventFilter::MouseOver),
                RefAny::new(0u32),
                0usize,
            )
            .is_focusable());
    }
    #[test]
    fn has_activation_behavior_for_elements_callbacks_and_roles() {
        assert!(NodeData::create_node(NodeType::A).has_activation_behavior());
        assert!(NodeData::create_button_no_a11y().has_activation_behavior());
        assert!(!NodeData::create_div().has_activation_behavior());
        for f in [HoverEventFilter::MouseUp, HoverEventFilter::LeftMouseUp] {
            assert!(NodeData::create_div()
                .with_callback(EventFilter::Hover(f), RefAny::new(0u32), 0usize)
                .has_activation_behavior());
        }
        // MouseDown is not a click.
        assert!(!NodeData::create_div()
            .with_callback(
                EventFilter::Hover(HoverEventFilter::MouseDown),
                RefAny::new(0u32),
                0usize,
            )
            .has_activation_behavior());
        let mut nd = NodeData::create_div();
        nd.set_accessibility_info(
            SmallAriaInfo::label("x")
                .with_role(AccessibilityRole::PushButton)
                .to_full_info(),
        );
        assert!(nd.has_activation_behavior(), "role=PushButton activates");
    }
    #[test]
    fn is_activatable_is_false_for_unavailable_elements() {
        let mut nd = NodeData::create_button_no_a11y();
        assert!(nd.is_activatable());
        let mut info = SmallAriaInfo::label("Save")
            .with_role(AccessibilityRole::PushButton)
            .to_full_info();
        info.states = vec![AccessibilityState::Unavailable].into();
        nd.set_accessibility_info(info);
        assert!(nd.has_activation_behavior());
        assert!(
            !nd.is_activatable(),
            "an Unavailable (disabled) button must not be activatable"
        );
        // Something with no activation behaviour at all is never activatable.
        assert!(!NodeData::create_div().is_activatable());
    }
    // =====================================================================
    // NodeData — accessible label / value / placeholder
    // =====================================================================
    #[test]
    fn get_accessible_label_prefers_aria_label_over_alt_and_title() {
        let mut nd = NodeData::create_div();
        nd.set_attributes(
            vec![
                AttributeType::Title("title".into()),
                AttributeType::Alt("alt".into()),
                AttributeType::AriaLabel("aria".into()),
            ]
            .into(),
        );
        assert_eq!(
            nd.get_accessible_label(),
            Some("aria"),
            "aria-label wins regardless of attribute order"
        );
    }
    #[test]
    fn get_accessible_label_alt_vs_title_is_order_dependent() {
        // AUDIT: the doc comment promises `aria-label > alt > title`, but the
        // implementation's second pass matches `Alt(s) | Title(s)` in a single arm,
        // so whichever appears FIRST in the attribute vec wins. With [Title, Alt]
        // that yields "title" — contradicting the documented priority. Pinned here
        // so a fix has to update this test deliberately. See report.
        let mut title_first = NodeData::create_div();
        title_first.set_attributes(
            vec![
                AttributeType::Title("title".into()),
                AttributeType::Alt("alt".into()),
            ]
            .into(),
        );
        assert_eq!(title_first.get_accessible_label(), Some("title"));
        let mut alt_first = NodeData::create_div();
        alt_first.set_attributes(
            vec![
                AttributeType::Alt("alt".into()),
                AttributeType::Title("title".into()),
            ]
            .into(),
        );
        assert_eq!(alt_first.get_accessible_label(), Some("alt"));
    }
    #[test]
    fn get_accessible_label_value_and_placeholder_default_to_none() {
        let nd = NodeData::create_div();
        assert_eq!(nd.get_accessible_label(), None);
        assert_eq!(nd.get_accessible_value(), None);
        assert_eq!(nd.get_placeholder(), None);
    }
    #[test]
    fn get_accessible_value_and_placeholder_return_the_first_match() {
        let mut nd = NodeData::create_div();
        nd.set_attributes(
            vec![
                AttributeType::Value("first".into()),
                AttributeType::Value("second".into()),
                AttributeType::Placeholder("ph".into()),
            ]
            .into(),
        );
        assert_eq!(nd.get_accessible_value(), Some("first"));
        assert_eq!(nd.get_placeholder(), Some("ph"));
    }
    #[test]
    fn get_accessible_label_returns_empty_string_not_none_for_empty_aria_label() {
        // Boundary: an empty aria-label is still "present" — Some("") not None.
        let mut nd = NodeData::create_div();
        nd.set_attributes(vec![AttributeType::AriaLabel("".into())].into());
        assert_eq!(nd.get_accessible_label(), Some(""));
    }
    // =====================================================================
    // NodeData — dataset / key / merge callback / component origin
    // =====================================================================
    #[test]
    fn dataset_set_get_take_round_trip() {
        let mut nd = NodeData::create_div();
        assert!(nd.get_dataset().is_none());
        assert!(nd.take_dataset().is_none(), "take on empty must be None");
        nd.set_dataset(OptionRefAny::Some(RefAny::new(42u32)));
        assert!(nd.get_dataset().is_some());
        assert!(nd.get_dataset_mut().is_some());
        let mut taken = nd.take_dataset().expect("dataset was set");
        assert_eq!(taken.downcast_ref::<u32>().map(|r| *r), Some(42));
        assert!(nd.get_dataset().is_none(), "take must clear the slot");
        assert!(nd.take_dataset().is_none(), "double-take must be None");
    }
    #[test]
    fn set_dataset_none_clears_without_allocating_extra() {
        let mut nd = NodeData::create_div();
        // Setting None on a node that never had a dataset must be a no-op, not a panic.
        nd.set_dataset(OptionRefAny::None);
        assert!(nd.get_dataset().is_none());
        nd.set_dataset(OptionRefAny::Some(RefAny::new(1u8)));
        nd.set_dataset(OptionRefAny::None);
        assert!(nd.get_dataset().is_none());
    }
    #[test]
    fn set_key_is_deterministic_and_input_sensitive() {
        let mut a = NodeData::create_div();
        let mut b = NodeData::create_div();
        a.set_key("user-123");
        b.set_key("user-123");
        assert_eq!(a.get_key(), b.get_key(), "same key input => same hash");
        assert!(a.get_key().is_some());
        let mut c = NodeData::create_div();
        c.set_key("user-124");
        assert_ne!(a.get_key(), c.get_key(), "different inputs => different keys");
    }
    #[test]
    fn set_key_hashes_str_and_string_identically() {
        let mut a = NodeData::create_div();
        let mut b = NodeData::create_div();
        a.set_key("x");
        b.set_key(String::from("x"));
        assert_eq!(
            a.get_key(),
            b.get_key(),
            "&str and String must hash the same (Hash for str)"
        );
    }
    #[test]
    fn set_key_accepts_extreme_inputs() {
        for nd in [
            NodeData::create_div().with_key(""),
            NodeData::create_div().with_key(u64::MAX),
            NodeData::create_div().with_key(i64::MIN),
            NodeData::create_div().with_key(huge_unicode_string()),
        ] {
            assert!(nd.get_key().is_some());
        }
    }
    #[test]
    fn set_key_overwrites_rather_than_accumulating() {
        let mut nd = NodeData::create_div();
        nd.set_key("a");
        let first = nd.get_key();
        nd.set_key("b");
        assert_ne!(nd.get_key(), first, "the last set_key wins");
    }
    #[test]
    fn merge_callback_round_trips_the_function_pointer() {
        let mut nd = NodeData::create_div();
        assert!(nd.get_merge_callback().is_none());
        nd.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
        let cb = nd.get_merge_callback().expect("merge callback was set");
        assert_eq!(cb.cb as usize, merge_cb_a as usize);
        assert_eq!(cb.callable, OptionRefAny::None);
        // Overwriting swaps the pointer.
        nd.set_merge_callback(merge_cb_b as DatasetMergeCallbackType);
        let cb = nd.get_merge_callback().expect("merge callback was replaced");
        assert_eq!(cb.cb as usize, merge_cb_b as usize);
    }
    #[test]
    fn dataset_merge_callback_from_ptr_matches_the_from_impl() {
        let via_ptr = DatasetMergeCallback::from_ptr(merge_cb_a);
        let via_from = DatasetMergeCallback::from(merge_cb_a as DatasetMergeCallbackType);
        assert_eq!(via_ptr, via_from);
        assert_eq!(via_ptr.cb as usize, merge_cb_a as usize);
        assert_eq!(via_ptr.callable, OptionRefAny::None);
        // Distinct functions must not compare equal.
        assert_ne!(via_ptr, DatasetMergeCallback::from_ptr(merge_cb_b));
    }
    #[test]
    fn dataset_merge_callback_debug_is_non_empty_and_names_the_type() {
        let cb = DatasetMergeCallback::from_ptr(merge_cb_a);
        let s = format!("{cb:?}");
        assert!(s.contains("DatasetMergeCallback"));
        assert!(s.contains("cb"));
    }
    #[test]
    fn merge_callback_is_actually_callable_through_the_stored_pointer() {
        let cb = DatasetMergeCallback::from_ptr(merge_cb_b);
        let mut out = (cb.cb)(RefAny::new(1u32), RefAny::new(2u32));
        assert_eq!(
            out.downcast_ref::<u32>().map(|r| *r),
            Some(2),
            "merge_cb_b returns the OLD data"
        );
    }
    #[test]
    fn component_origin_round_trips_and_defaults_to_none() {
        let mut nd = NodeData::create_div();
        assert!(nd.get_component_origin().is_none());
        nd.set_component_origin(ComponentOrigin {
            component_id: "shadcn:card".into(),
            data_model_json: crate::json::Json::null(),
        });
        let origin = nd.get_component_origin().expect("origin was set");
        assert_eq!(origin.component_id.as_str(), "shadcn:card");
        // The Default impl is well-formed and hashable.
        let d = ComponentOrigin::default();
        assert_eq!(d.component_id.as_str(), "");
        assert_eq!(hash_of(&d), hash_of(&ComponentOrigin::default()));
    }
    // =====================================================================
    // NodeData — svg data / clip mask
    // =====================================================================
    #[test]
    fn get_image_clip_mask_returns_none_for_non_mask_svg_data() {
        let mut nd = NodeData::create_div();
        assert!(nd.get_image_clip_mask().is_none());
        nd.set_svg_data(SvgNodeData::Circle {
            cx: 1.0,
            cy: 2.0,
            r: 3.0,
        });
        assert!(nd.get_svg_data().is_some());
        assert!(
            nd.get_image_clip_mask().is_none(),
            "a Circle is not an ImageClipMask"
        );
    }
    #[test]
    fn set_clip_mask_is_readable_through_get_image_clip_mask() {
        let mask = ImageMask {
            image: ImageRef::null_image(2, 2, crate::resources::RawImageFormat::R8, Vec::new()),
            rect: crate::geom::LogicalRect::new(
                LogicalPosition { x: 0.0, y: 0.0 },
                crate::geom::LogicalSize {
                    width: 2.0,
                    height: 2.0,
                },
            ),
            repeat: false,
        };
        let mut nd = NodeData::create_div();
        nd.set_clip_mask(mask.clone());
        assert_eq!(nd.get_image_clip_mask(), Some(&mask));
        // set_clip_mask stores through the same slot as set_svg_data.
        assert!(matches!(
            nd.get_svg_data(),
            Some(SvgNodeData::ImageClipMask(_))
        ));
    }
    #[test]
    fn svg_node_data_with_nan_coords_is_self_equal_and_hash_consistent() {
        // SvgNodeData hashes f32 via to_bits and derives Eq, so a NaN-carrying shape
        // must be equal to (and hash like) itself, or NodeData's Hash/Eq contract
        // breaks for SVG nodes.
        let a = SvgNodeData::Rect {
            x: f32::NAN,
            y: f32::INFINITY,
            width: f32::NEG_INFINITY,
            height: -0.0,
            rx: 0.0,
            ry: f32::MAX,
        };
        let b = a.clone();
        assert_eq!(a, b);
        assert_eq!(hash_of(&a), hash_of(&b));
        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
    }
    #[test]
    fn svg_node_data_line_and_linear_gradient_are_distinct_despite_a_shared_hash_body() {
        // The Hash impl deliberately folds Line and LinearGradient into one arm, so
        // they can hash alike — but Eq must still tell them apart.
        let line = SvgNodeData::Line {
            x1: 1.0,
            y1: 2.0,
            x2: 3.0,
            y2: 4.0,
        };
        let grad = SvgNodeData::LinearGradient {
            x1: 1.0,
            y1: 2.0,
            x2: 3.0,
            y2: 4.0,
        };
        assert_ne!(line, grad, "same field values, different variants");
    }
    // =====================================================================
    // NodeData — hashing
    // =====================================================================
    #[test]
    fn calculate_node_data_hash_is_deterministic_and_equal_for_equal_nodes() {
        let a = NodeData::create_div().with_key("k").with_contenteditable(true);
        let b = a.clone();
        assert_eq!(a, b);
        assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
        assert_eq!(
            a.calculate_node_data_hash(),
            a.calculate_node_data_hash(),
            "hashing must not depend on call count"
        );
    }
    #[test]
    fn structural_hash_ignores_text_content_but_data_hash_does_not() {
        // Documented behaviour: Text("Hello") must match Text("Hello World") during
        // reconciliation so the cursor survives an edit.
        let a = NodeData::create_text_do_not_use_without_block_level_wrapper("Hello");
        let b = NodeData::create_text_do_not_use_without_block_level_wrapper("Hello World");
        assert_eq!(
            a.calculate_structural_hash(),
            b.calculate_structural_hash(),
            "structural hash must ignore text content"
        );
        assert_ne!(
            a.calculate_node_data_hash(),
            b.calculate_node_data_hash(),
            "the full data hash must NOT ignore text content"
        );
    }
    #[test]
    fn structural_hash_ignores_contenteditable_but_data_hash_does_not() {
        let plain = NodeData::create_div();
        let editable = NodeData::create_div().with_contenteditable(true);
        assert_eq!(
            plain.calculate_structural_hash(),
            editable.calculate_structural_hash(),
            "contenteditable flips with focus; it must not move the structural hash"
        );
        assert_ne!(
            plain.calculate_node_data_hash(),
            editable.calculate_node_data_hash(),
            "flags ARE part of the full data hash"
        );
    }
    #[test]
    fn structural_hash_is_sensitive_to_ids_classes_and_node_type() {
        let mut a = NodeData::create_div();
        a.add_id("a".into());
        let mut b = NodeData::create_div();
        b.add_id("b".into());
        assert_ne!(a.calculate_structural_hash(), b.calculate_structural_hash());
        let mut c = NodeData::create_div();
        c.add_class("a".into());
        assert_ne!(
            a.calculate_structural_hash(),
            c.calculate_structural_hash(),
            "id=\"a\" and class=\"a\" must not collide"
        );
        assert_ne!(
            NodeData::create_div().calculate_structural_hash(),
            NodeData::create_br().calculate_structural_hash()
        );
    }
    #[test]
    fn node_data_eq_implies_equal_hash_for_a_richly_populated_node() {
        let mut a = NodeData::create_div();
        a.add_id("id".into());
        a.add_class("cls".into());
        a.set_tab_index(TabIndex::OverrideInParent(9));
        a.set_contenteditable(true);
        a.set_anonymous(true);
        a.set_key("key");
        a.set_dataset(OptionRefAny::Some(RefAny::new(7u64)));
        a.set_svg_data(SvgNodeData::GradientStop { offset: 0.5 });
        a.set_context_menu(Menu::create(Vec::new().into()));
        a.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
        a.set_css("color: red;");
        let b = a.clone();
        assert_eq!(a, b, "clone must be value-equal");
        assert_eq!(
            hash_of(&a),
            hash_of(&b),
            "Eq == true but hashes differ: Hash/Eq contract violated"
        );
        assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
        // copy_special must agree with Clone.
        assert_eq!(a.copy_special(), b);
    }
    // =====================================================================
    // NodeData — Display / node_data_to_string (serializer)
    // =====================================================================
    #[test]
    fn node_data_to_string_is_empty_for_a_bare_node() {
        // Private fn — only reachable from an inline test module.
        assert_eq!(node_data_to_string(&NodeData::create_div()), "");
    }
    #[test]
    fn node_data_to_string_emits_ids_classes_and_tabindex() {
        let mut nd = NodeData::create_div();
        nd.add_id("i1".into());
        nd.add_id("i2".into());
        nd.add_class("c1".into());
        nd.set_tab_index(TabIndex::NoKeyboardFocus);
        let s = node_data_to_string(&nd);
        assert!(s.contains(r#"id="i1 i2""#), "ids are space-joined: {s}");
        assert!(s.contains(r#"class="c1""#), "{s}");
        assert!(s.contains(r#"tabindex="-1""#), "{s}");
    }
    #[test]
    fn node_data_display_is_self_closing_without_content() {
        let s = format!("{}", NodeData::create_div());
        assert!(s.starts_with('<'), "{s}");
        assert!(s.ends_with("/>"), "content-less nodes self-close: {s}");
    }
    #[test]
    fn node_data_display_wraps_text_content_in_a_tag_pair() {
        let s = format!("{}", NodeData::create_text_do_not_use_without_block_level_wrapper("hello"));
        assert!(s.starts_with('<'));
        assert!(s.ends_with('>'));
        assert!(s.contains("hello"), "{s}");
        assert!(!s.ends_with("/>"), "a node with content must not self-close");
    }
    #[test]
    fn node_data_display_does_not_panic_on_hostile_text() {
        // NOTE: Display is a debug/inspection aid and does NOT escape markup — a text
        // node containing `<script>` reproduces it verbatim. Assert only that it is
        // total (no panic) and round-trips the bytes; see report.
        for text in [
            "",
            "<script>alert(1)</script>",
            "\" onload=\"x",
            "日本語 🎉",
            "line\nbreak\ttab",
        ] {
            let s = format!("{}", NodeData::create_text_do_not_use_without_block_level_wrapper(text));
            assert!(s.contains(text), "Display dropped content for {text:?}");
        }
    }
    #[test]
    fn node_data_display_survives_a_huge_text_payload() {
        let big = huge_unicode_string();
        let s = format!("{}", NodeData::create_text_do_not_use_without_block_level_wrapper(big.clone()));
        assert!(s.len() > big.len());
    }
    #[test]
    fn debug_print_end_matches_the_node_tag() {
        let s = NodeData::create_div().debug_print_end();
        assert!(s.starts_with("</"));
        assert!(s.ends_with('>'));
    }
    // =====================================================================
    // NodeData — setters / builders / swap
    // =====================================================================
    #[test]
    fn set_node_type_replaces_the_type_and_keeps_the_attributes() {
        let mut nd = NodeData::create_div();
        nd.add_id("keep".into());
        nd.set_node_type(NodeType::Span);
        assert!(nd.is_node_type(NodeType::Span));
        assert!(nd.has_id("keep"), "changing the tag must not drop attributes");
    }
    #[test]
    fn add_callback_appends_and_get_callbacks_reflects_it() {
        let mut nd = NodeData::create_div();
        assert!(nd.get_callbacks().as_ref().is_empty());
        nd.add_callback(
            EventFilter::Hover(HoverEventFilter::MouseUp),
            RefAny::new(1u32),
            0usize,
        );
        nd.add_callback(
            EventFilter::Focus(FocusEventFilter::MouseDown),
            RefAny::new(2u32),
            1usize,
        );
        assert_eq!(nd.get_callbacks().as_ref().len(), 2);
        assert_eq!(
            nd.get_callbacks().as_ref()[0].event,
            EventFilter::Hover(HoverEventFilter::MouseUp)
        );
    }
    #[test]
    fn add_css_property_appends_an_inline_rule() {
        use azul_css::props::property::{CssProperty, CssPropertyType};
        let mut nd = NodeData::create_div();
        assert!(nd.get_style().rules.as_ref().is_empty());
        nd.add_css_property(CssPropertyWithConditions {
            property: CssProperty::const_none(CssPropertyType::Display),
            apply_if: Vec::new().into(),
        });
        assert_eq!(nd.get_style().rules.as_ref().len(), 1);
        nd.add_css_property(CssPropertyWithConditions {
            property: CssProperty::const_none(CssPropertyType::Display),
            apply_if: Vec::new().into(),
        });
        assert_eq!(
            nd.get_style().rules.as_ref().len(),
            2,
            "add_css_property appends, it does not replace"
        );
    }
    #[test]
    fn set_style_replaces_whereas_set_css_appends() {
        let mut nd = NodeData::create_div();
        nd.set_css("color: red;");
        let after_first = nd.get_style().rules.as_ref().len();
        assert!(after_first > 0);
        nd.set_css("color: blue;");
        assert!(
            nd.get_style().rules.as_ref().len() > after_first,
            "set_css appends to the existing inline style"
        );
        nd.set_style(azul_css::css::Css {
            rules: Vec::new().into(),
            keyframes: Vec::new().into(),
        });
        assert!(
            nd.get_style().rules.as_ref().is_empty(),
            "set_style replaces wholesale"
        );
    }
    #[test]
    fn set_css_with_empty_and_malformed_input_does_not_panic() {
        for style in [
            "",
            "   ",
            ";;;;",
            "color",
            "color:",
            ":",
            "}",
            "{",
            "color: ;",
            "not-a-property: not-a-value;",
            ":hover {",
            "@os {",
            "color: red",           // no trailing semicolon
            "\u{0}color: red;",     // NUL byte
            "color: 日本語;",
        ] {
            let nd = NodeData::create_div().with_css(style);
            // The only contract for malformed input is "don't panic"; whether a rule
            // survives parsing is the CSS parser's business.
            let _ = nd.get_style().rules.as_ref().len();
        }
    }
    #[test]
    fn swap_with_default_returns_the_original_and_leaves_a_div() {
        let mut nd = NodeData::create_text_do_not_use_without_block_level_wrapper("payload");
        let taken = nd.swap_with_default();
        assert!(taken.is_text_node());
        assert!(nd.is_node_type(NodeType::Div), "the slot becomes a fresh div");
        assert!(nd.attributes().as_ref().is_empty());
    }
    #[test]
    fn node_data_builders_are_equivalent_to_their_setters() {
        let built = NodeData::create_div()
            .with_tab_index(TabIndex::Auto)
            .with_contenteditable(true)
            .with_node_type(NodeType::Span);
        let mut set = NodeData::create_div();
        set.set_tab_index(TabIndex::Auto);
        set.set_contenteditable(true);
        set.set_node_type(NodeType::Span);
        assert_eq!(built, set);
    }
    // =====================================================================
    // NodeDataVec containers
    // =====================================================================
    #[test]
    fn node_data_vec_as_container_is_empty_for_an_empty_vec() {
        let v: NodeDataVec = Vec::new().into();
        assert_eq!(v.as_container().internal.len(), 0);
    }
    #[test]
    fn node_data_vec_containers_expose_and_mutate_the_backing_slice() {
        let mut v: NodeDataVec = vec![
            NodeData::create_div(),
            NodeData::create_br(),
            NodeData::create_text_do_not_use_without_block_level_wrapper("t"),
        ]
        .into();
        assert_eq!(v.as_container().internal.len(), 3);
        assert!(v.as_container().internal[2].is_text_node());
        v.as_container_mut().internal[0].set_node_type(NodeType::Span);
        assert!(v.as_container().internal[0].is_node_type(NodeType::Span));
    }
    // =====================================================================
    // Dom — child bookkeeping
    // =====================================================================
    #[test]
    fn dom_default_is_an_empty_body() {
        let d = Dom::default();
        assert!(d.root.is_node_type(NodeType::Body));
        assert_eq!(d.estimated_total_children, 0);
        assert_eq!(d.node_count(), 1);
    }
    #[test]
    fn dom_set_children_recomputes_the_estimate_from_scratch() {
        let child = Dom::create_div().with_child(Dom::create_div());
        let mut parent = Dom::create_div();
        parent.add_child(Dom::create_div());
        assert_eq!(parent.estimated_total_children, 1);
        // set_children REPLACES; the old child must not be counted twice.
        parent.set_children(vec![child].into());
        assert_eq!(parent.estimated_total_children, 2);
        assert_eq!(
            parent.estimated_total_children,
            parent.recompute_estimated_total_children()
        );
    }
    #[test]
    fn dom_set_children_with_an_empty_vec_zeroes_the_estimate() {
        let mut d = Dom::create_div().with_child(Dom::create_div().with_child(Dom::create_div()));
        assert_eq!(d.estimated_total_children, 2);
        d.set_children(Vec::new().into());
        assert_eq!(d.estimated_total_children, 0);
        assert_eq!(d.node_count(), 1);
    }
    #[test]
    fn dom_deeply_nested_chain_keeps_an_exact_estimate() {
        // 256-deep chain: every level adds exactly one descendant.
        const DEPTH: usize = 256;
        let mut d = Dom::create_div();
        for _ in 0..DEPTH {
            d = Dom::create_div().with_child(d);
        }
        assert_eq!(d.estimated_total_children, DEPTH);
        assert_eq!(d.node_count(), DEPTH + 1);
        assert_eq!(d.recompute_estimated_total_children(), DEPTH);
    }
    #[test]
    fn dom_very_wide_child_list_keeps_an_exact_estimate() {
        const WIDTH: usize = 5_000;
        let children: Vec<Dom> = (0..WIDTH).map(|_| Dom::create_div()).collect();
        let d = Dom::create_div().with_children(children.into());
        assert_eq!(d.estimated_total_children, WIDTH);
        assert_eq!(d.node_count(), WIDTH + 1);
    }
    #[test]
    fn dom_from_iterator_counts_nested_grandchildren() {
        let empty: Dom = Vec::new().into_iter().collect();
        assert_eq!(empty.estimated_total_children, 0);
        assert!(empty.root.is_node_type(NodeType::Div));
        // Two children, one of which has a child of its own => 3 descendants.
        let d: Dom = vec![
            Dom::create_div().with_child(Dom::create_div()),
            Dom::create_div(),
        ]
        .into_iter()
        .collect();
        assert_eq!(d.estimated_total_children, 3);
        assert_eq!(d.estimated_total_children, d.recompute_estimated_total_children());
        assert_eq!(d.node_count(), 4);
    }
    #[test]
    fn dom_fixup_repairs_a_corrupted_estimate_at_every_depth() {
        let mut d = Dom::create_div()
            .with_child(Dom::create_div().with_child(Dom::create_div()))
            .with_child(Dom::create_div());
        // Corrupt the cached counter at BOTH levels (the public field makes this
        // reachable from safe code, which is what fixup exists to undo).
        d.estimated_total_children = 0;
        d.children.as_mut()[0].estimated_total_children = 99;
        let repaired = d.fixup_children_estimated();
        assert_eq!(repaired, 3);
        assert_eq!(d.children.as_ref()[0].estimated_total_children, 1);
        assert_eq!(
            d.estimated_total_children,
            d.recompute_estimated_total_children()
        );
    }
    #[test]
    fn dom_fixup_on_a_leaf_zeroes_a_bogus_estimate() {
        let mut d = Dom::create_div();
        d.estimated_total_children = usize::MAX;
        assert_eq!(d.fixup_children_estimated(), 0);
        assert_eq!(d.node_count(), 1, "node_count is safe again after fixup");
    }
    // `estimated_total_children` is a public field, so `usize::MAX` is
    // reachable. This used to be `#[cfg(debug_assertions)] #[should_panic]`,
    // pinning "panics in debug, wraps to 0 in release" — i.e. pinning a
    // divergence where the release answer was the dangerous one (0 reads as
    // "empty DOM"). `node_count` saturates now, so assert the SAME defined
    // answer in both configurations, and assert it is not the empty-DOM value.
    #[test]
    fn dom_node_count_saturates_on_a_corrupted_max_estimate() {
        let mut d = Dom::create_div();
        d.estimated_total_children = usize::MAX;
        assert_eq!(d.node_count(), usize::MAX);
        assert_ne!(
            d.node_count(),
            0,
            "wrapping to 0 would claim an empty DOM — the one answer callers \
             act on without checking"
        );
    }
    #[test]
    fn dom_swap_with_default_returns_the_original_tree() {
        let mut d = Dom::create_div().with_child(Dom::create_div());
        let taken = d.swap_with_default();
        assert_eq!(taken.estimated_total_children, 1);
        assert_eq!(d.estimated_total_children, 0, "the slot is reset");
        assert!(d.root.is_node_type(NodeType::Div));
    }
    // =====================================================================
    // Dom — builders
    // =====================================================================
    #[test]
    fn dom_with_id_and_with_class_apply_to_the_root() {
        let d = Dom::create_div()
            .with_id("root".into())
            .with_class("card".into());
        assert!(d.root.has_id("root"));
        assert!(d.root.has_class("card"));
    }
    #[test]
    fn dom_with_attribute_appends_and_with_attributes_replaces() {
        let d = Dom::create_div()
            .with_attribute(AttributeType::Href("/a".into()))
            .with_attribute(AttributeType::Alt("alt".into()));
        assert_eq!(d.root.attributes().as_ref().len(), 2);
        let d = d.with_attributes(vec![AttributeType::Disabled].into());
        assert_eq!(
            d.root.attributes().as_ref().len(),
            1,
            "with_attributes replaces wholesale"
        );
        assert_eq!(d.root.attributes().as_ref()[0], AttributeType::Disabled);
    }
    #[test]
    fn dom_add_component_css_stacks_stylesheets() {
        let mut d = Dom::create_div();
        assert!(d.css.as_ref().is_empty());
        d.set_css("color: red;");
        d.set_css("color: blue;");
        assert_eq!(d.css.as_ref().len(), 2, "each set_css pushes a stylesheet");
        d.set_component_css(Vec::new().into());
        assert!(d.css.as_ref().is_empty(), "set_component_css replaces");
    }
    #[test]
    fn dom_with_css_does_not_panic_on_malformed_input() {
        for style in ["", "}}}", "@os {", "color:", "\u{0}"] {
            let d = Dom::create_div().with_css(style);
            assert_eq!(d.css.as_ref().len(), 1, "a Css is pushed even if it parses empty");
        }
    }
    #[test]
    fn dom_text_helpers_produce_a_text_child() {
        let d = Dom::create_h1_with_text("Title");
        assert!(d.root.is_node_type(NodeType::H1));
        assert_eq!(d.estimated_total_children, 1);
        assert!(d.children.as_ref()[0].root.is_text_node());
    }
    #[test]
    fn dom_create_geolocation_probe_carries_its_config() {
        let cfg = crate::geolocation::GeolocationProbeConfig {
            high_accuracy: true,
            background: false,
            max_accuracy_m: 25.0,
            min_interval_ms: 1_000,
        };
        let d = Dom::create_geolocation_probe(cfg);
        match d.root.get_node_type() {
            NodeType::GeolocationProbe(c) => {
                assert!(c.high_accuracy);
                assert_eq!(c.min_interval_ms, 1_000);
            }
            other => panic!("expected GeolocationProbe, got {other:?}"),
        }
    }
    #[test]
    fn dom_clone_and_eq_agree_on_a_nested_tree() {
        let d = Dom::create_div()
            .with_id("r".into())
            .with_child(Dom::create_text_do_not_use_without_block_level_wrapper("a"))
            .with_child(Dom::create_div().with_child(Dom::create_text_do_not_use_without_block_level_wrapper("b")));
        let c = d.clone();
        assert_eq!(d, c);
        assert_eq!(hash_of(&d), hash_of(&c));
        // text("a") + div + text("b") == 3 descendants.
        assert_eq!(c.estimated_total_children, 3);
        assert_eq!(c.node_count(), 4);
    }
    #[test]
    fn dom_debug_does_not_panic_on_a_nested_tree() {
        let d = Dom::create_div()
            .with_child(Dom::create_text_do_not_use_without_block_level_wrapper("日本語 🎉"))
            .with_child(Dom::create_div().with_child(Dom::create_br()));
        let s = format!("{d:?}");
        assert!(s.contains("Dom"));
        assert!(s.contains("estimated_total_children"));
    }
    // =====================================================================
    // DomId / DomNodeId
    // =====================================================================
    #[test]
    fn dom_id_root_is_zero_and_is_the_default() {
        assert_eq!(DomId::ROOT_ID.inner, 0);
        assert_eq!(DomId::default(), DomId::ROOT_ID);
        assert_eq!(format!("{}", DomId::ROOT_ID), "0");
        assert_eq!(format!("{}", DomId { inner: usize::MAX }), usize::MAX.to_string());
    }
    #[test]
    fn dom_node_id_root_points_at_the_root_dom_and_no_node() {
        assert_eq!(DomNodeId::ROOT.dom, DomId::ROOT_ID);
        assert_eq!(DomNodeId::ROOT.node, NodeHierarchyItemId::NONE);
    }
}