Skip to main content

azul_core/
dom.rs

1//! Defines the core Document Object Model (DOM) structures.
2//!
3//! This module is responsible for representing the UI as a tree of nodes,
4//! similar to the HTML DOM. It includes definitions for node types, event handling
5//! and the main `Dom` and `CompactDom` structures.
6
7#[cfg(not(feature = "std"))]
8use alloc::string::ToString;
9use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
10use core::{
11    fmt,
12    hash::{Hash, Hasher},
13    iter::FromIterator,
14    mem,
15    sync::atomic::{AtomicUsize, Ordering},
16};
17
18use azul_css::{
19    css::{BoxOrStatic, Css, NodeTypeTag},
20    format_rust_code::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
30pub use crate::a11y::*;
31pub use crate::events::{
32    ApplicationEventFilter, ComponentEventFilter, EventFilter, FocusEventFilter, HoverEventFilter,
33    WindowEventFilter,
34};
35pub use crate::id::{Node, NodeHierarchy, NodeId};
36use crate::{
37    callbacks::{
38        CoreCallback, CoreCallbackData, CoreCallbackDataVec, CoreCallbackType, VirtualViewCallback,
39        VirtualViewCallbackType,
40    },
41    geom::LogicalPosition,
42    id::{NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut},
43    menu::Menu,
44    prop_cache::{CssPropertyCache, CssPropertyCachePtr},
45    refany::{OptionRefAny, RefAny},
46    resources::{
47        image_ref_get_hash, CoreImageCallback, ImageMask, ImageRef, ImageRefHash, RendererResources,
48    },
49    styled_dom::{
50        CompactDom, NodeHierarchyItemId, StyleFontFamilyHash, StyledDom, StyledNode,
51        StyledNodeState,
52    },
53    window::OptionVirtualKeyCodeCombo,
54};
55pub use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
56
57static TAG_ID: AtomicUsize = AtomicUsize::new(1);
58
59/// Strongly-typed input element types for HTML `<input>` elements.
60#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub enum InputType {
63    /// Text input (default)
64    Text,
65    /// Button
66    Button,
67    /// Checkbox
68    Checkbox,
69    /// Color picker
70    Color,
71    /// Date picker
72    Date,
73    /// Date and time picker
74    Datetime,
75    /// Date and time picker (local)
76    DatetimeLocal,
77    /// Email address input
78    Email,
79    /// File upload
80    File,
81    /// Hidden input
82    Hidden,
83    /// Image button
84    Image,
85    /// Month picker
86    Month,
87    /// Number input
88    Number,
89    /// Password input
90    Password,
91    /// Radio button
92    Radio,
93    /// Range slider
94    Range,
95    /// Reset button
96    Reset,
97    /// Search input
98    Search,
99    /// Submit button
100    Submit,
101    /// Telephone number input
102    Tel,
103    /// Time picker
104    Time,
105    /// URL input
106    Url,
107    /// Week picker
108    Week,
109}
110
111impl InputType {
112    /// Returns the HTML attribute value for this input type
113    pub const fn as_str(&self) -> &'static str {
114        match self {
115            InputType::Text => "text",
116            InputType::Button => "button",
117            InputType::Checkbox => "checkbox",
118            InputType::Color => "color",
119            InputType::Date => "date",
120            InputType::Datetime => "datetime",
121            InputType::DatetimeLocal => "datetime-local",
122            InputType::Email => "email",
123            InputType::File => "file",
124            InputType::Hidden => "hidden",
125            InputType::Image => "image",
126            InputType::Month => "month",
127            InputType::Number => "number",
128            InputType::Password => "password",
129            InputType::Radio => "radio",
130            InputType::Range => "range",
131            InputType::Reset => "reset",
132            InputType::Search => "search",
133            InputType::Submit => "submit",
134            InputType::Tel => "tel",
135            InputType::Time => "time",
136            InputType::Url => "url",
137            InputType::Week => "week",
138        }
139    }
140}
141
142#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
143#[repr(C)]
144pub struct TagId {
145    pub inner: u64,
146}
147
148impl ::core::fmt::Display for TagId {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        f.debug_struct("TagId").field("inner", &self.inner).finish()
151    }
152}
153
154impl_option!(
155    TagId,
156    OptionTagId,
157    [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
158);
159
160impl TagId {
161    pub const fn into_crate_internal(&self) -> TagId {
162        TagId { inner: self.inner }
163    }
164    pub const fn from_crate_internal(t: TagId) -> Self {
165        TagId { inner: t.inner }
166    }
167
168    /// Creates a new, unique hit-testing tag ID.
169    /// Wraps around to 1 on overflow (0 is reserved for "no tag").
170    pub fn unique() -> Self {
171        loop {
172            let current = TAG_ID.load(Ordering::SeqCst);
173            let next = if current == usize::MAX { 1 } else { current + 1 };
174            if TAG_ID.compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
175                return TagId { inner: current as u64 };
176            }
177        }
178    }
179}
180
181/// Same as the `TagId`, but only for scrollable nodes.
182/// This provides a typed distinction for tags associated with scrolling containers.
183#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
184#[repr(C)]
185pub struct ScrollTagId {
186    pub inner: TagId,
187}
188
189impl ::core::fmt::Display for ScrollTagId {
190    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191        f.debug_struct("ScrollTagId")
192            .field("inner", &self.inner)
193            .finish()
194    }
195}
196
197impl ::core::fmt::Debug for ScrollTagId {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        write!(f, "{}", self)
200    }
201}
202
203impl ScrollTagId {
204    /// Creates a new, unique scroll tag ID. Note that this should not
205    /// be used for identifying nodes, use the `DomNodeHash` instead.
206    pub fn unique() -> ScrollTagId {
207        ScrollTagId {
208            inner: TagId::unique(),
209        }
210    }
211}
212
213/// Orientation of a scrollbar.
214#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215#[repr(C)]
216pub enum ScrollbarOrientation {
217    Horizontal,
218    Vertical,
219}
220
221/// Calculated hash of a DOM node, used for identifying identical DOM
222/// nodes across frames for efficient diffing and state preservation.
223#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
224#[repr(C)]
225pub struct DomNodeHash {
226    pub inner: u64,
227}
228
229impl ::core::fmt::Debug for DomNodeHash {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        write!(f, "DomNodeHash({})", self.inner)
232    }
233}
234
235/// List of core DOM node types built into `azul`.
236/// This enum defines the building blocks of the UI, similar to HTML tags.
237#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
238#[repr(C, u8)]
239pub enum NodeType {
240    // Root and container elements
241    /// Root HTML element.
242    Html,
243    /// Document head (metadata container).
244    Head,
245    /// Root element of the document body.
246    Body,
247    /// Generic block-level container.
248    Div,
249    /// Paragraph.
250    P,
251    /// Article content.
252    Article,
253    /// Section of a document.
254    Section,
255    /// Navigation links.
256    Nav,
257    /// Sidebar/tangential content.
258    Aside,
259    /// Header section.
260    Header,
261    /// Footer section.
262    Footer,
263    /// Main content.
264    Main,
265    /// Figure with optional caption.
266    Figure,
267    /// Caption for figure element.
268    FigCaption,
269    /// Headings.
270    H1,
271    H2,
272    H3,
273    H4,
274    H5,
275    H6,
276    /// Line break.
277    Br,
278    /// Horizontal rule.
279    Hr,
280    /// Preformatted text.
281    Pre,
282    /// Block quote.
283    BlockQuote,
284    /// Address.
285    Address,
286    /// Details disclosure widget.
287    Details,
288    /// Summary for details element.
289    Summary,
290    /// Dialog box or window.
291    Dialog,
292
293    // List elements
294    /// Unordered list.
295    Ul,
296    /// Ordered list.
297    Ol,
298    /// List item.
299    Li,
300    /// Definition list.
301    Dl,
302    /// Definition term.
303    Dt,
304    /// Definition description.
305    Dd,
306    /// Menu list.
307    Menu,
308    /// Menu item.
309    MenuItem,
310    /// Directory list (deprecated).
311    Dir,
312
313    // Table elements
314    /// Table container.
315    Table,
316    /// Table caption.
317    Caption,
318    /// Table header.
319    THead,
320    /// Table body.
321    TBody,
322    /// Table footer.
323    TFoot,
324    /// Table row.
325    Tr,
326    /// Table header cell.
327    Th,
328    /// Table data cell.
329    Td,
330    /// Table column group.
331    ColGroup,
332    /// Table column.
333    Col,
334
335    // Form elements
336    /// Form container.
337    Form,
338    /// Form fieldset.
339    FieldSet,
340    /// Fieldset legend.
341    Legend,
342    /// Label for form controls.
343    Label,
344    /// Input control.
345    Input,
346    /// Button control.
347    Button,
348    /// Select dropdown.
349    Select,
350    /// Option group.
351    OptGroup,
352    /// Select option.
353    SelectOption,
354    /// Multiline text input.
355    TextArea,
356    /// Form output element.
357    Output,
358    /// Progress indicator.
359    Progress,
360    /// Scalar measurement within a known range.
361    Meter,
362    /// List of predefined options for input.
363    DataList,
364
365    // Inline elements
366    /// Generic inline container.
367    Span,
368    /// Anchor/hyperlink.
369    A,
370    /// Emphasized text.
371    Em,
372    /// Strongly emphasized text.
373    Strong,
374    /// Bold text (deprecated - use `Dom::create_strong()` for semantic importance).
375    B,
376    /// Italic text (deprecated - use `Dom::create_em()` for emphasis or `Dom::create_cite()` for citations).
377    I,
378    /// Underline text.
379    U,
380    /// Strikethrough text.
381    S,
382    /// Marked/highlighted text.
383    Mark,
384    /// Deleted text.
385    Del,
386    /// Inserted text.
387    Ins,
388    /// Code.
389    Code,
390    /// Sample output.
391    Samp,
392    /// Keyboard input.
393    Kbd,
394    /// Variable.
395    Var,
396    /// Citation.
397    Cite,
398    /// Defining instance of a term.
399    Dfn,
400    /// Abbreviation.
401    Abbr,
402    /// Acronym.
403    Acronym,
404    /// Inline quotation.
405    Q,
406    /// Date/time.
407    Time,
408    /// Subscript.
409    Sub,
410    /// Superscript.
411    Sup,
412    /// Small text (deprecated - use CSS `font-size` instead).
413    Small,
414    /// Big text (deprecated - use CSS `font-size` instead).
415    Big,
416    /// Bi-directional override.
417    Bdo,
418    /// Bi-directional isolate.
419    Bdi,
420    /// Word break opportunity.
421    Wbr,
422    /// Ruby annotation.
423    Ruby,
424    /// Ruby text.
425    Rt,
426    /// Ruby text container.
427    Rtc,
428    /// Ruby parenthesis.
429    Rp,
430    /// Machine-readable data.
431    Data,
432
433    // Embedded content
434    /// Canvas for graphics.
435    Canvas,
436    /// Embedded object.
437    Object,
438    /// Embedded object parameter.
439    Param,
440    /// External resource embed.
441    Embed,
442    /// Audio content.
443    Audio,
444    /// Video content.
445    Video,
446    /// Media source.
447    Source,
448    /// Text track for media.
449    Track,
450    /// Image map.
451    Map,
452    /// Image map area.
453    Area,
454    // SVG elements — container
455    /// SVG `<svg>` root graphics container.
456    Svg,
457    /// SVG `<g>` group element.
458    SvgG,
459    /// SVG `<defs>` — reusable definitions (not rendered directly).
460    SvgDefs,
461    /// SVG `<symbol>` — like defs but with its own viewBox.
462    SvgSymbol,
463    /// SVG `<use>` — references and instantiates a defs element.
464    SvgUse,
465    /// SVG `<switch>` — conditional processing.
466    SvgSwitch,
467
468    // SVG elements — shape
469    /// SVG `<path>` element.
470    SvgPath,
471    /// SVG `<circle>` element.
472    SvgCircle,
473    /// SVG `<rect>` element.
474    SvgRect,
475    /// SVG `<ellipse>` element.
476    SvgEllipse,
477    /// SVG `<line>` element.
478    SvgLine,
479    /// SVG `<polygon>` element.
480    SvgPolygon,
481    /// SVG `<polyline>` element.
482    SvgPolyline,
483
484    // SVG elements — text
485    /// SVG `<text>` element.
486    SvgText(AzString),
487    /// SVG `<tspan>` element.
488    SvgTspan,
489    /// SVG `<textPath>` element.
490    SvgTextPath,
491
492    // SVG elements — paint servers
493    /// SVG `<linearGradient>` element.
494    SvgLinearGradient,
495    /// SVG `<radialGradient>` element.
496    SvgRadialGradient,
497    /// SVG `<stop>` gradient stop element.
498    SvgStop,
499    /// SVG `<pattern>` element.
500    SvgPattern,
501
502    // SVG elements — clipping / masking
503    /// SVG `<clipPath>` element.
504    SvgClipPathElement,
505    /// SVG `<mask>` element.
506    SvgMask,
507
508    // SVG elements — filter
509    /// SVG `<filter>` container element.
510    SvgFilter,
511    /// SVG `<feBlend>`.
512    SvgFeBlend,
513    /// SVG `<feColorMatrix>`.
514    SvgFeColorMatrix,
515    /// SVG `<feComponentTransfer>`.
516    SvgFeComponentTransfer,
517    /// SVG `<feComposite>`.
518    SvgFeComposite,
519    /// SVG `<feConvolveMatrix>`.
520    SvgFeConvolveMatrix,
521    /// SVG `<feDiffuseLighting>`.
522    SvgFeDiffuseLighting,
523    /// SVG `<feDisplacementMap>`.
524    SvgFeDisplacementMap,
525    /// SVG `<feDistantLight>`.
526    SvgFeDistantLight,
527    /// SVG `<feDropShadow>`.
528    SvgFeDropShadow,
529    /// SVG `<feFlood>`.
530    SvgFeFlood,
531    /// SVG `<feFuncR>`.
532    SvgFeFuncR,
533    /// SVG `<feFuncG>`.
534    SvgFeFuncG,
535    /// SVG `<feFuncB>`.
536    SvgFeFuncB,
537    /// SVG `<feFuncA>`.
538    SvgFeFuncA,
539    /// SVG `<feGaussianBlur>`.
540    SvgFeGaussianBlur,
541    /// SVG `<feImage>`.
542    SvgFeImage,
543    /// SVG `<feMerge>`.
544    SvgFeMerge,
545    /// SVG `<feMergeNode>`.
546    SvgFeMergeNode,
547    /// SVG `<feMorphology>`.
548    SvgFeMorphology,
549    /// SVG `<feOffset>`.
550    SvgFeOffset,
551    /// SVG `<fePointLight>`.
552    SvgFePointLight,
553    /// SVG `<feSpecularLighting>`.
554    SvgFeSpecularLighting,
555    /// SVG `<feSpotLight>`.
556    SvgFeSpotLight,
557    /// SVG `<feTile>`.
558    SvgFeTile,
559    /// SVG `<feTurbulence>`.
560    SvgFeTurbulence,
561
562    // SVG elements — marker / image / foreign
563    /// SVG `<marker>` element (not the CSS ::marker pseudo-element).
564    SvgMarker,
565    /// SVG `<image>` element (embedded raster image in SVG).
566    SvgImage(ImageRef),
567    /// SVG `<foreignObject>` element.
568    SvgForeignObject,
569
570    // SVG elements — descriptive / structural
571    /// SVG `<title>` element (distinct from HTML `<title>`).
572    SvgTitle,
573    /// SVG `<desc>` element.
574    SvgDesc,
575    /// SVG `<metadata>` element.
576    SvgMetadata,
577    /// SVG `<a>` hyperlink element (distinct from HTML `<a>`).
578    SvgA,
579    /// SVG `<view>` element.
580    SvgView,
581    /// SVG `<style>` element (distinct from HTML `<style>`).
582    SvgStyle,
583    /// SVG `<script>` element (distinct from HTML `<script>`).
584    SvgScript,
585
586    // SVG elements — animation
587    /// SVG `<animate>` element.
588    SvgAnimate,
589    /// SVG `<animateMotion>` element.
590    SvgAnimateMotion,
591    /// SVG `<animateTransform>` element.
592    SvgAnimateTransform,
593    /// SVG `<set>` element.
594    SvgSet,
595    /// SVG `<mpath>` element.
596    SvgMpath,
597
598    // Metadata elements
599    /// Document title.
600    Title,
601    /// Metadata.
602    Meta,
603    /// External resource link.
604    Link,
605    /// Embedded or referenced script.
606    Script,
607    /// Style information.
608    Style,
609    /// Base URL for relative URLs.
610    Base,
611
612    // Pseudo-elements (transformed into real elements)
613    /// ::before pseudo-element.
614    Before,
615    /// ::after pseudo-element.
616    After,
617    /// ::marker pseudo-element.
618    Marker,
619    /// ::placeholder pseudo-element.
620    Placeholder,
621
622    // Special content types
623    /// Text content, ::text.
624    /// Uses BoxOrStatic to keep NodeType small (~16B vs ~72B with inline AzString)
625    /// and to allow static text references in the future.
626    Text(BoxOrStatic<AzString>),
627    /// Image element, ::image.
628    /// Uses BoxOrStatic to keep NodeType small.
629    Image(BoxOrStatic<ImageRef>),
630    /// VirtualView (embedded content) - payload stored in NodeDataExt.virtual_view
631    VirtualView,
632    /// Icon element - resolved to actual content by IconProvider.
633    /// The string is the icon name (e.g., "home", "settings", "search").
634    /// Uses BoxOrStatic to keep NodeType small.
635    Icon(BoxOrStatic<AzString>),
636    /// Invisible probe node that signals "this subtree needs the user's
637    /// GPS / network location". Zero-size in layout, skipped in the
638    /// display list. The `GeolocationManager` walks the styled DOM for
639    /// these at end-of-layout and starts / stops the matching native
640    /// subscription. See `SUPER_PLAN_2.md` §1.5 + research/08.
641    GeolocationProbe(crate::geolocation::GeolocationProbeConfig),
642}
643
644/// Type alias: `BoxOrStatic<ImageRef>` — used by NodeType::Image for FFI monomorphization.
645pub type BoxOrStaticImageRef = BoxOrStatic<ImageRef>;
646
647impl_option!(NodeType, OptionNodeType, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
648
649impl NodeType {
650    fn into_library_owned_nodetype(&self) -> Self {
651        use self::NodeType::*;
652        match self {
653            Html => Html,
654            Head => Head,
655            Body => Body,
656            Div => Div,
657            P => P,
658            Article => Article,
659            Section => Section,
660            Nav => Nav,
661            Aside => Aside,
662            Header => Header,
663            Footer => Footer,
664            Main => Main,
665            Figure => Figure,
666            FigCaption => FigCaption,
667            H1 => H1,
668            H2 => H2,
669            H3 => H3,
670            H4 => H4,
671            H5 => H5,
672            H6 => H6,
673            Br => Br,
674            Hr => Hr,
675            Pre => Pre,
676            BlockQuote => BlockQuote,
677            Address => Address,
678            Details => Details,
679            Summary => Summary,
680            Dialog => Dialog,
681            Ul => Ul,
682            Ol => Ol,
683            Li => Li,
684            Dl => Dl,
685            Dt => Dt,
686            Dd => Dd,
687            Menu => Menu,
688            MenuItem => MenuItem,
689            Dir => Dir,
690            Table => Table,
691            Caption => Caption,
692            THead => THead,
693            TBody => TBody,
694            TFoot => TFoot,
695            Tr => Tr,
696            Th => Th,
697            Td => Td,
698            ColGroup => ColGroup,
699            Col => Col,
700            Form => Form,
701            FieldSet => FieldSet,
702            Legend => Legend,
703            Label => Label,
704            Input => Input,
705            Button => Button,
706            Select => Select,
707            OptGroup => OptGroup,
708            SelectOption => SelectOption,
709            TextArea => TextArea,
710            Output => Output,
711            Progress => Progress,
712            Meter => Meter,
713            DataList => DataList,
714            Span => Span,
715            A => A,
716            Em => Em,
717            Strong => Strong,
718            B => B,
719            I => I,
720            U => U,
721            S => S,
722            Mark => Mark,
723            Del => Del,
724            Ins => Ins,
725            Code => Code,
726            Samp => Samp,
727            Kbd => Kbd,
728            Var => Var,
729            Cite => Cite,
730            Dfn => Dfn,
731            Abbr => Abbr,
732            Acronym => Acronym,
733            Q => Q,
734            Time => Time,
735            Sub => Sub,
736            Sup => Sup,
737            Small => Small,
738            Big => Big,
739            Bdo => Bdo,
740            Bdi => Bdi,
741            Wbr => Wbr,
742            Ruby => Ruby,
743            Rt => Rt,
744            Rtc => Rtc,
745            Rp => Rp,
746            Data => Data,
747            Canvas => Canvas,
748            Object => Object,
749            Param => Param,
750            Embed => Embed,
751            Audio => Audio,
752            Video => Video,
753            Source => Source,
754            Track => Track,
755            Map => Map,
756            Area => Area,
757            // SVG container
758            Svg => Svg, SvgG => SvgG, SvgDefs => SvgDefs, SvgSymbol => SvgSymbol,
759            SvgUse => SvgUse, SvgSwitch => SvgSwitch,
760            // SVG shape
761            SvgPath => SvgPath, SvgCircle => SvgCircle, SvgRect => SvgRect,
762            SvgEllipse => SvgEllipse, SvgLine => SvgLine,
763            SvgPolygon => SvgPolygon, SvgPolyline => SvgPolyline,
764            // SVG text
765            SvgText(s) => SvgText(s.clone_self()),
766            SvgTspan => SvgTspan, SvgTextPath => SvgTextPath,
767            // SVG paint
768            SvgLinearGradient => SvgLinearGradient, SvgRadialGradient => SvgRadialGradient,
769            SvgStop => SvgStop, SvgPattern => SvgPattern,
770            // SVG clip/mask
771            SvgClipPathElement => SvgClipPathElement, SvgMask => SvgMask,
772            // SVG filter
773            SvgFilter => SvgFilter, SvgFeBlend => SvgFeBlend,
774            SvgFeColorMatrix => SvgFeColorMatrix,
775            SvgFeComponentTransfer => SvgFeComponentTransfer,
776            SvgFeComposite => SvgFeComposite, SvgFeConvolveMatrix => SvgFeConvolveMatrix,
777            SvgFeDiffuseLighting => SvgFeDiffuseLighting,
778            SvgFeDisplacementMap => SvgFeDisplacementMap,
779            SvgFeDistantLight => SvgFeDistantLight, SvgFeDropShadow => SvgFeDropShadow,
780            SvgFeFlood => SvgFeFlood,
781            SvgFeFuncR => SvgFeFuncR, SvgFeFuncG => SvgFeFuncG,
782            SvgFeFuncB => SvgFeFuncB, SvgFeFuncA => SvgFeFuncA,
783            SvgFeGaussianBlur => SvgFeGaussianBlur, SvgFeImage => SvgFeImage,
784            SvgFeMerge => SvgFeMerge, SvgFeMergeNode => SvgFeMergeNode,
785            SvgFeMorphology => SvgFeMorphology, SvgFeOffset => SvgFeOffset,
786            SvgFePointLight => SvgFePointLight,
787            SvgFeSpecularLighting => SvgFeSpecularLighting,
788            SvgFeSpotLight => SvgFeSpotLight,
789            SvgFeTile => SvgFeTile, SvgFeTurbulence => SvgFeTurbulence,
790            // SVG marker/image/foreign
791            SvgMarker => SvgMarker,
792            SvgImage(i) => SvgImage(i.clone()),
793            SvgForeignObject => SvgForeignObject,
794            // SVG descriptive/structural
795            SvgTitle => SvgTitle, SvgDesc => SvgDesc, SvgMetadata => SvgMetadata,
796            SvgA => SvgA, SvgView => SvgView,
797            SvgStyle => SvgStyle, SvgScript => SvgScript,
798            // SVG animation
799            SvgAnimate => SvgAnimate, SvgAnimateMotion => SvgAnimateMotion,
800            SvgAnimateTransform => SvgAnimateTransform,
801            SvgSet => SvgSet, SvgMpath => SvgMpath,
802            // HTML metadata
803            Title => Title,
804            Meta => Meta,
805            Link => Link,
806            Script => Script,
807            Style => Style,
808            Base => Base,
809            Before => Before,
810            After => After,
811            Marker => Marker,
812            Placeholder => Placeholder,
813
814            Text(s) => Text(BoxOrStatic::heap(s.clone_self())),
815            Image(i) => Image(i.clone()),
816            VirtualView => VirtualView,
817            Icon(s) => Icon(BoxOrStatic::heap(s.clone_self())),
818            GeolocationProbe(cfg) => GeolocationProbe(*cfg),
819        }
820    }
821
822    pub fn format(&self) -> Option<String> {
823        use self::NodeType::*;
824        match self {
825            Text(s) => Some(format!("{}", s)),
826            Image(id) => Some(format!("image({:?})", id)),
827            VirtualView => Some("virtualized-view".to_string()),
828            Icon(s) => Some(format!("icon({})", s)),
829            GeolocationProbe(cfg) => Some(format!(
830                "geolocation-probe(hi={}, bg={}, max={}m, every={}ms)",
831                cfg.high_accuracy, cfg.background, cfg.max_accuracy_m, cfg.min_interval_ms
832            )),
833            _ => None,
834        }
835    }
836
837    /// Returns the NodeTypeTag for CSS selector matching.
838    pub fn get_path(&self) -> NodeTypeTag {
839        match self {
840            Self::Html => NodeTypeTag::Html,
841            Self::Head => NodeTypeTag::Head,
842            Self::Body => NodeTypeTag::Body,
843            Self::Div => NodeTypeTag::Div,
844            Self::P => NodeTypeTag::P,
845            Self::Article => NodeTypeTag::Article,
846            Self::Section => NodeTypeTag::Section,
847            Self::Nav => NodeTypeTag::Nav,
848            Self::Aside => NodeTypeTag::Aside,
849            Self::Header => NodeTypeTag::Header,
850            Self::Footer => NodeTypeTag::Footer,
851            Self::Main => NodeTypeTag::Main,
852            Self::Figure => NodeTypeTag::Figure,
853            Self::FigCaption => NodeTypeTag::FigCaption,
854            Self::H1 => NodeTypeTag::H1,
855            Self::H2 => NodeTypeTag::H2,
856            Self::H3 => NodeTypeTag::H3,
857            Self::H4 => NodeTypeTag::H4,
858            Self::H5 => NodeTypeTag::H5,
859            Self::H6 => NodeTypeTag::H6,
860            Self::Br => NodeTypeTag::Br,
861            Self::Hr => NodeTypeTag::Hr,
862            Self::Pre => NodeTypeTag::Pre,
863            Self::BlockQuote => NodeTypeTag::BlockQuote,
864            Self::Address => NodeTypeTag::Address,
865            Self::Details => NodeTypeTag::Details,
866            Self::Summary => NodeTypeTag::Summary,
867            Self::Dialog => NodeTypeTag::Dialog,
868            Self::Ul => NodeTypeTag::Ul,
869            Self::Ol => NodeTypeTag::Ol,
870            Self::Li => NodeTypeTag::Li,
871            Self::Dl => NodeTypeTag::Dl,
872            Self::Dt => NodeTypeTag::Dt,
873            Self::Dd => NodeTypeTag::Dd,
874            Self::Menu => NodeTypeTag::Menu,
875            Self::MenuItem => NodeTypeTag::MenuItem,
876            Self::Dir => NodeTypeTag::Dir,
877            Self::Table => NodeTypeTag::Table,
878            Self::Caption => NodeTypeTag::Caption,
879            Self::THead => NodeTypeTag::THead,
880            Self::TBody => NodeTypeTag::TBody,
881            Self::TFoot => NodeTypeTag::TFoot,
882            Self::Tr => NodeTypeTag::Tr,
883            Self::Th => NodeTypeTag::Th,
884            Self::Td => NodeTypeTag::Td,
885            Self::ColGroup => NodeTypeTag::ColGroup,
886            Self::Col => NodeTypeTag::Col,
887            Self::Form => NodeTypeTag::Form,
888            Self::FieldSet => NodeTypeTag::FieldSet,
889            Self::Legend => NodeTypeTag::Legend,
890            Self::Label => NodeTypeTag::Label,
891            Self::Input => NodeTypeTag::Input,
892            Self::Button => NodeTypeTag::Button,
893            Self::Select => NodeTypeTag::Select,
894            Self::OptGroup => NodeTypeTag::OptGroup,
895            Self::SelectOption => NodeTypeTag::SelectOption,
896            Self::TextArea => NodeTypeTag::TextArea,
897            Self::Output => NodeTypeTag::Output,
898            Self::Progress => NodeTypeTag::Progress,
899            Self::Meter => NodeTypeTag::Meter,
900            Self::DataList => NodeTypeTag::DataList,
901            Self::Span => NodeTypeTag::Span,
902            Self::A => NodeTypeTag::A,
903            Self::Em => NodeTypeTag::Em,
904            Self::Strong => NodeTypeTag::Strong,
905            Self::B => NodeTypeTag::B,
906            Self::I => NodeTypeTag::I,
907            Self::U => NodeTypeTag::U,
908            Self::S => NodeTypeTag::S,
909            Self::Mark => NodeTypeTag::Mark,
910            Self::Del => NodeTypeTag::Del,
911            Self::Ins => NodeTypeTag::Ins,
912            Self::Code => NodeTypeTag::Code,
913            Self::Samp => NodeTypeTag::Samp,
914            Self::Kbd => NodeTypeTag::Kbd,
915            Self::Var => NodeTypeTag::Var,
916            Self::Cite => NodeTypeTag::Cite,
917            Self::Dfn => NodeTypeTag::Dfn,
918            Self::Abbr => NodeTypeTag::Abbr,
919            Self::Acronym => NodeTypeTag::Acronym,
920            Self::Q => NodeTypeTag::Q,
921            Self::Time => NodeTypeTag::Time,
922            Self::Sub => NodeTypeTag::Sub,
923            Self::Sup => NodeTypeTag::Sup,
924            Self::Small => NodeTypeTag::Small,
925            Self::Big => NodeTypeTag::Big,
926            Self::Bdo => NodeTypeTag::Bdo,
927            Self::Bdi => NodeTypeTag::Bdi,
928            Self::Wbr => NodeTypeTag::Wbr,
929            Self::Ruby => NodeTypeTag::Ruby,
930            Self::Rt => NodeTypeTag::Rt,
931            Self::Rtc => NodeTypeTag::Rtc,
932            Self::Rp => NodeTypeTag::Rp,
933            Self::Data => NodeTypeTag::Data,
934            Self::Canvas => NodeTypeTag::Canvas,
935            Self::Object => NodeTypeTag::Object,
936            Self::Param => NodeTypeTag::Param,
937            Self::Embed => NodeTypeTag::Embed,
938            Self::Audio => NodeTypeTag::Audio,
939            Self::Video => NodeTypeTag::Video,
940            Self::Source => NodeTypeTag::Source,
941            Self::Track => NodeTypeTag::Track,
942            Self::Map => NodeTypeTag::Map,
943            Self::Area => NodeTypeTag::Area,
944            // SVG — all variants map 1:1 to NodeTypeTag
945            Self::Svg => NodeTypeTag::Svg,
946            Self::SvgG => NodeTypeTag::SvgG,
947            Self::SvgDefs => NodeTypeTag::SvgDefs,
948            Self::SvgSymbol => NodeTypeTag::SvgSymbol,
949            Self::SvgUse => NodeTypeTag::SvgUse,
950            Self::SvgSwitch => NodeTypeTag::SvgSwitch,
951            Self::SvgPath => NodeTypeTag::SvgPath,
952            Self::SvgCircle => NodeTypeTag::SvgCircle,
953            Self::SvgRect => NodeTypeTag::SvgRect,
954            Self::SvgEllipse => NodeTypeTag::SvgEllipse,
955            Self::SvgLine => NodeTypeTag::SvgLine,
956            Self::SvgPolygon => NodeTypeTag::SvgPolygon,
957            Self::SvgPolyline => NodeTypeTag::SvgPolyline,
958            Self::SvgText(_) => NodeTypeTag::SvgText,
959            Self::SvgTspan => NodeTypeTag::SvgTspan,
960            Self::SvgTextPath => NodeTypeTag::SvgTextPath,
961            Self::SvgLinearGradient => NodeTypeTag::SvgLinearGradient,
962            Self::SvgRadialGradient => NodeTypeTag::SvgRadialGradient,
963            Self::SvgStop => NodeTypeTag::SvgStop,
964            Self::SvgPattern => NodeTypeTag::SvgPattern,
965            Self::SvgClipPathElement => NodeTypeTag::SvgClipPathElement,
966            Self::SvgMask => NodeTypeTag::SvgMask,
967            Self::SvgFilter => NodeTypeTag::SvgFilter,
968            Self::SvgFeBlend => NodeTypeTag::SvgFeBlend,
969            Self::SvgFeColorMatrix => NodeTypeTag::SvgFeColorMatrix,
970            Self::SvgFeComponentTransfer => NodeTypeTag::SvgFeComponentTransfer,
971            Self::SvgFeComposite => NodeTypeTag::SvgFeComposite,
972            Self::SvgFeConvolveMatrix => NodeTypeTag::SvgFeConvolveMatrix,
973            Self::SvgFeDiffuseLighting => NodeTypeTag::SvgFeDiffuseLighting,
974            Self::SvgFeDisplacementMap => NodeTypeTag::SvgFeDisplacementMap,
975            Self::SvgFeDistantLight => NodeTypeTag::SvgFeDistantLight,
976            Self::SvgFeDropShadow => NodeTypeTag::SvgFeDropShadow,
977            Self::SvgFeFlood => NodeTypeTag::SvgFeFlood,
978            Self::SvgFeFuncR => NodeTypeTag::SvgFeFuncR,
979            Self::SvgFeFuncG => NodeTypeTag::SvgFeFuncG,
980            Self::SvgFeFuncB => NodeTypeTag::SvgFeFuncB,
981            Self::SvgFeFuncA => NodeTypeTag::SvgFeFuncA,
982            Self::SvgFeGaussianBlur => NodeTypeTag::SvgFeGaussianBlur,
983            Self::SvgFeImage => NodeTypeTag::SvgFeImage,
984            Self::SvgFeMerge => NodeTypeTag::SvgFeMerge,
985            Self::SvgFeMergeNode => NodeTypeTag::SvgFeMergeNode,
986            Self::SvgFeMorphology => NodeTypeTag::SvgFeMorphology,
987            Self::SvgFeOffset => NodeTypeTag::SvgFeOffset,
988            Self::SvgFePointLight => NodeTypeTag::SvgFePointLight,
989            Self::SvgFeSpecularLighting => NodeTypeTag::SvgFeSpecularLighting,
990            Self::SvgFeSpotLight => NodeTypeTag::SvgFeSpotLight,
991            Self::SvgFeTile => NodeTypeTag::SvgFeTile,
992            Self::SvgFeTurbulence => NodeTypeTag::SvgFeTurbulence,
993            Self::SvgMarker => NodeTypeTag::SvgMarker,
994            Self::SvgImage(_) => NodeTypeTag::SvgImage,
995            Self::SvgForeignObject => NodeTypeTag::SvgForeignObject,
996            Self::SvgTitle => NodeTypeTag::SvgTitle,
997            Self::SvgDesc => NodeTypeTag::SvgDesc,
998            Self::SvgMetadata => NodeTypeTag::SvgMetadata,
999            Self::SvgA => NodeTypeTag::SvgA,
1000            Self::SvgView => NodeTypeTag::SvgView,
1001            Self::SvgStyle => NodeTypeTag::SvgStyle,
1002            Self::SvgScript => NodeTypeTag::SvgScript,
1003            Self::SvgAnimate => NodeTypeTag::SvgAnimate,
1004            Self::SvgAnimateMotion => NodeTypeTag::SvgAnimateMotion,
1005            Self::SvgAnimateTransform => NodeTypeTag::SvgAnimateTransform,
1006            Self::SvgSet => NodeTypeTag::SvgSet,
1007            Self::SvgMpath => NodeTypeTag::SvgMpath,
1008            // HTML metadata
1009            Self::Title => NodeTypeTag::Title,
1010            Self::Meta => NodeTypeTag::Meta,
1011            Self::Link => NodeTypeTag::Link,
1012            Self::Script => NodeTypeTag::Script,
1013            Self::Style => NodeTypeTag::Style,
1014            Self::Base => NodeTypeTag::Base,
1015            Self::Text(_) => NodeTypeTag::Text,
1016            Self::Image(_) => NodeTypeTag::Img,
1017            Self::VirtualView => NodeTypeTag::VirtualView,
1018            Self::Icon(_) => NodeTypeTag::Icon,
1019            Self::GeolocationProbe(_) => NodeTypeTag::GeolocationProbe,
1020            Self::Before => NodeTypeTag::Before,
1021            Self::After => NodeTypeTag::After,
1022            Self::Marker => NodeTypeTag::Marker,
1023            Self::Placeholder => NodeTypeTag::Placeholder,
1024        }
1025    }
1026
1027    /// Returns whether this node type is a semantic HTML element that should
1028    /// automatically generate an accessibility tree node.
1029    ///
1030    /// These are elements with inherent semantic meaning that assistive
1031    /// technologies should be aware of, even without explicit ARIA attributes.
1032    pub const fn is_semantic_for_accessibility(&self) -> bool {
1033        matches!(
1034            self,
1035            Self::Button
1036                | Self::Input
1037                | Self::TextArea
1038                | Self::Select
1039                | Self::A
1040                | Self::H1
1041                | Self::H2
1042                | Self::H3
1043                | Self::H4
1044                | Self::H5
1045                | Self::H6
1046                | Self::Article
1047                | Self::Section
1048                | Self::Nav
1049                | Self::Main
1050                | Self::Header
1051                | Self::Footer
1052                | Self::Aside
1053        )
1054    }
1055}
1056
1057/// Represents the CSS formatting context for an element
1058#[derive(Clone, PartialEq)]
1059// +spec:display-property:844893 - block-level box establishing a new formatting context (BFC) modeled here
1060pub enum FormattingContext {
1061    /// Block-level formatting context
1062    Block {
1063        /// Whether this element establishes a new block formatting context
1064        establishes_new_context: bool,
1065    },
1066    /// Inline-level formatting context
1067    Inline,
1068    /// Inline-block (participates in an IFC but creates a BFC)
1069    InlineBlock,
1070    /// Flex formatting context
1071    Flex,
1072    /// Float (left or right)
1073    Float(LayoutFloat),
1074    /// Absolutely positioned (out of flow)
1075    OutOfFlow(LayoutPosition),
1076    /// Table formatting context (container)
1077    Table,
1078    /// Table row group formatting context (thead, tbody, tfoot)
1079    TableRowGroup,
1080    /// Table row formatting context
1081    TableRow,
1082    /// Table cell formatting context (td, th)
1083    TableCell,
1084    /// Table column group formatting context
1085    TableColumnGroup,
1086    /// Table caption formatting context
1087    TableCaption,
1088    /// Grid formatting context
1089    Grid,
1090    /// display:contents - element generates no box, children promoted to parent
1091    Contents,
1092    /// No formatting context (display: none)
1093    None,
1094}
1095
1096impl fmt::Debug for FormattingContext {
1097    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1098        match self {
1099            FormattingContext::Block {
1100                establishes_new_context,
1101            } => write!(
1102                f,
1103                "Block {{ establishes_new_context: {establishes_new_context:?} }}"
1104            ),
1105            FormattingContext::Inline => write!(f, "Inline"),
1106            FormattingContext::InlineBlock => write!(f, "InlineBlock"),
1107            FormattingContext::Flex => write!(f, "Flex"),
1108            FormattingContext::Float(layout_float) => write!(f, "Float({layout_float:?})"),
1109            FormattingContext::OutOfFlow(layout_position) => {
1110                write!(f, "OutOfFlow({layout_position:?})")
1111            }
1112            FormattingContext::Grid => write!(f, "Grid"),
1113            FormattingContext::None => write!(f, "None"),
1114            FormattingContext::Table => write!(f, "Table"),
1115            FormattingContext::TableRowGroup => write!(f, "TableRowGroup"),
1116            FormattingContext::TableRow => write!(f, "TableRow"),
1117            FormattingContext::TableCell => write!(f, "TableCell"),
1118            FormattingContext::TableColumnGroup => write!(f, "TableColumnGroup"),
1119            FormattingContext::TableCaption => write!(f, "TableCaption"),
1120            FormattingContext::Contents => write!(f, "Contents"),
1121        }
1122    }
1123}
1124
1125impl Default for FormattingContext {
1126    fn default() -> Self {
1127        FormattingContext::Block {
1128            establishes_new_context: false,
1129        }
1130    }
1131}
1132
1133/// Defines the type of event that can trigger a callback action.
1134#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1135#[repr(C)]
1136pub enum On {
1137    /// Mouse cursor is hovering over the element.
1138    MouseOver,
1139    /// Mouse cursor has is over element and is pressed
1140    /// (not good for "click" events - use `MouseUp` instead).
1141    MouseDown,
1142    /// (Specialization of `MouseDown`). Fires only if the left mouse button
1143    /// has been pressed while cursor was over the element.
1144    LeftMouseDown,
1145    /// (Specialization of `MouseDown`). Fires only if the middle mouse button
1146    /// has been pressed while cursor was over the element.
1147    MiddleMouseDown,
1148    /// (Specialization of `MouseDown`). Fires only if the right mouse button
1149    /// has been pressed while cursor was over the element.
1150    RightMouseDown,
1151    /// Mouse button has been released while cursor was over the element.
1152    MouseUp,
1153    /// (Specialization of `MouseUp`). Fires only if the left mouse button has
1154    /// been released while cursor was over the element.
1155    LeftMouseUp,
1156    /// (Specialization of `MouseUp`). Fires only if the middle mouse button has
1157    /// been released while cursor was over the element.
1158    MiddleMouseUp,
1159    /// (Specialization of `MouseUp`). Fires only if the right mouse button has
1160    /// been released while cursor was over the element.
1161    RightMouseUp,
1162    /// Mouse cursor has entered the element.
1163    MouseEnter,
1164    /// Mouse cursor has left the element.
1165    MouseLeave,
1166    /// Mousewheel / touchpad scrolling.
1167    Scroll,
1168    /// The window received a unicode character (also respects the system locale).
1169    /// Check `keyboard_state.current_char` to get the current pressed character.
1170    TextInput,
1171    /// A **virtual keycode** was pressed. Note: This is only the virtual keycode,
1172    /// not the actual char. If you want to get the character, use `TextInput` instead.
1173    /// A virtual key does not have to map to a printable character.
1174    ///
1175    /// You can get all currently pressed virtual keycodes in the
1176    /// `keyboard_state.current_virtual_keycodes` and / or just the last keycode in the
1177    /// `keyboard_state.latest_virtual_keycode`.
1178    VirtualKeyDown,
1179    /// A **virtual keycode** was release. See `VirtualKeyDown` for more info.
1180    VirtualKeyUp,
1181    /// A file has been dropped on the element.
1182    HoveredFile,
1183    /// A file is being hovered on the element.
1184    DroppedFile,
1185    /// A file was hovered, but has exited the window.
1186    HoveredFileCancelled,
1187    /// Equivalent to `onfocus`.
1188    FocusReceived,
1189    /// Equivalent to `onblur`.
1190    FocusLost,
1191
1192    // Accessibility-specific events
1193    /// Default action triggered by screen reader (usually same as click/activate)
1194    Default,
1195    /// Element should collapse (e.g., accordion panel, tree node)
1196    Collapse,
1197    /// Element should expand (e.g., accordion panel, tree node)
1198    Expand,
1199    /// Increment value (e.g., number input, slider)
1200    Increment,
1201    /// Decrement value (e.g., number input, slider)
1202    Decrement,
1203}
1204
1205/// Contains the necessary information to render an embedded `VirtualView` node.
1206#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1207#[repr(C)]
1208pub struct VirtualViewNode {
1209    /// The callback function that returns the DOM for the virtualized view's content.
1210    pub callback: VirtualViewCallback,
1211    /// The application data passed to the virtualized view's layout callback.
1212    pub refany: RefAny,
1213}
1214
1215/// An enum that holds either a CSS ID or a class name as a string.
1216#[repr(C, u8)]
1217#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1218pub enum IdOrClass {
1219    Id(AzString),
1220    Class(AzString),
1221}
1222
1223impl_option!(
1224    IdOrClass,
1225    OptionIdOrClass,
1226    copy = false,
1227    [Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord]
1228);
1229
1230impl_vec!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor, IdOrClassVecDestructorType, IdOrClassVecSlice, OptionIdOrClass);
1231impl_vec_debug!(IdOrClass, IdOrClassVec);
1232impl_vec_partialord!(IdOrClass, IdOrClassVec);
1233impl_vec_ord!(IdOrClass, IdOrClassVec);
1234impl_vec_clone!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor);
1235impl_vec_partialeq!(IdOrClass, IdOrClassVec);
1236impl_vec_eq!(IdOrClass, IdOrClassVec);
1237impl_vec_hash!(IdOrClass, IdOrClassVec);
1238
1239impl IdOrClass {
1240    pub fn as_id(&self) -> Option<&str> {
1241        match self {
1242            IdOrClass::Id(s) => Some(s.as_str()),
1243            IdOrClass::Class(_) => None,
1244        }
1245    }
1246    pub fn as_class(&self) -> Option<&str> {
1247        match self {
1248            IdOrClass::Class(s) => Some(s.as_str()),
1249            IdOrClass::Id(_) => None,
1250        }
1251    }
1252}
1253
1254/// Name-value pair for custom attributes (data-*, aria-*, etc.)
1255#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1256#[repr(C)]
1257pub struct AttributeNameValue {
1258    pub attr_name: AzString,
1259    pub value: AzString,
1260}
1261
1262/// Strongly-typed HTML attribute with type-safe values.
1263///
1264/// This enum provides a type-safe way to represent HTML attributes, ensuring that
1265/// values are validated at compile-time and properly converted to their string
1266/// representations at runtime.
1267#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1268#[repr(C, u8)]
1269pub enum AttributeType {
1270    /// Element ID attribute (`id="..."`)
1271    Id(AzString),
1272    /// CSS class attribute (`class="..."`)
1273    Class(AzString),
1274    /// Accessible name/label (`aria-label="..."`)
1275    AriaLabel(AzString),
1276    /// Element that labels this one (`aria-labelledby="..."`)
1277    AriaLabelledBy(AzString),
1278    /// Element that describes this one (`aria-describedby="..."`)
1279    AriaDescribedBy(AzString),
1280    /// Role for accessibility (`role="..."`)
1281    AriaRole(AzString),
1282    /// Current state of an element (`aria-checked`, `aria-selected`, etc.)
1283    AriaState(AttributeNameValue),
1284    /// ARIA property (`aria-*`)
1285    AriaProperty(AttributeNameValue),
1286
1287    /// Hyperlink target URL (`href="..."`)
1288    Href(AzString),
1289    /// Link relationship (`rel="..."`)
1290    Rel(AzString),
1291    /// Link target frame (`target="..."`)
1292    Target(AzString),
1293
1294    /// Image source URL (`src="..."`)
1295    Src(AzString),
1296    /// Alternative text for images (`alt="..."`)
1297    Alt(AzString),
1298    /// Image title (tooltip) (`title="..."`)
1299    Title(AzString),
1300
1301    /// Form input name (`name="..."`)
1302    Name(AzString),
1303    /// Form input value (`value="..."`)
1304    Value(AzString),
1305    /// Input type (`type="text|password|email|..."`)
1306    InputType(AzString),
1307    /// Placeholder text (`placeholder="..."`)
1308    Placeholder(AzString),
1309    /// Input is required (`required`)
1310    Required,
1311    /// Input is disabled (`disabled`)
1312    Disabled,
1313    /// Input is readonly (`readonly`)
1314    Readonly,
1315    /// Input is checked (checkbox/radio) (`checked`)
1316    CheckedTrue,
1317    /// Input is unchecked (checkbox/radio)
1318    CheckedFalse,
1319    /// Input is selected (option) (`selected`)
1320    Selected,
1321    /// Maximum value for number inputs (`max="..."`)
1322    Max(AzString),
1323    /// Minimum value for number inputs (`min="..."`)
1324    Min(AzString),
1325    /// Step value for number inputs (`step="..."`)
1326    Step(AzString),
1327    /// Input pattern for validation (`pattern="..."`)
1328    Pattern(AzString),
1329    /// Minimum length (`minlength="..."`)
1330    MinLength(i32),
1331    /// Maximum length (`maxlength="..."`)
1332    MaxLength(i32),
1333    /// Autocomplete behavior (`autocomplete="on|off|..."`)
1334    Autocomplete(AzString),
1335
1336    /// Table header scope (`scope="row|col|rowgroup|colgroup"`)
1337    Scope(AzString),
1338    /// Number of columns to span (`colspan="..."`)
1339    ColSpan(i32),
1340    /// Number of rows to span (`rowspan="..."`)
1341    RowSpan(i32),
1342
1343    /// Tab index for keyboard navigation (`tabindex="..."`)
1344    TabIndex(i32),
1345    /// Element can receive focus (`tabindex="0"` equivalent)
1346    Focusable,
1347
1348    /// Language code (`lang="..."`)
1349    Lang(AzString),
1350    /// Text direction (`dir="ltr|rtl|auto"`)
1351    Dir(AzString),
1352
1353    /// Content is editable (`contenteditable="true|false"`)
1354    ContentEditable(bool),
1355    /// Element is draggable (`draggable="true|false"`)
1356    Draggable(bool),
1357    /// Element is hidden (`hidden`)
1358    Hidden,
1359
1360    /// Generic data attribute (`data-*="..."`)
1361    Data(AttributeNameValue),
1362    /// Generic custom attribute (for future extensibility)
1363    Custom(AttributeNameValue),
1364}
1365
1366impl_option!(
1367    AttributeType,
1368    OptionAttributeType,
1369    copy = false,
1370    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1371);
1372
1373impl_vec!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor, AttributeTypeVecDestructorType, AttributeTypeVecSlice, OptionAttributeType);
1374impl_vec_debug!(AttributeType, AttributeTypeVec);
1375impl_vec_partialord!(AttributeType, AttributeTypeVec);
1376impl_vec_ord!(AttributeType, AttributeTypeVec);
1377impl_vec_clone!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor);
1378impl_vec_partialeq!(AttributeType, AttributeTypeVec);
1379impl_vec_eq!(AttributeType, AttributeTypeVec);
1380impl_vec_hash!(AttributeType, AttributeTypeVec);
1381
1382impl AttributeType {
1383    /// Returns the id string if this is an `Id` attribute, `None` otherwise.
1384    pub fn as_id(&self) -> Option<&str> {
1385        match self {
1386            AttributeType::Id(s) => Some(s.as_str()),
1387            _ => None,
1388        }
1389    }
1390    /// Returns the class string if this is a `Class` attribute, `None` otherwise.
1391    pub fn as_class(&self) -> Option<&str> {
1392        match self {
1393            AttributeType::Class(s) => Some(s.as_str()),
1394            _ => None,
1395        }
1396    }
1397    /// Get the attribute name (e.g., "href", "aria-label", "data-foo")
1398    pub fn name(&self) -> &str {
1399        match self {
1400            AttributeType::Id(_) => "id",
1401            AttributeType::Class(_) => "class",
1402            AttributeType::AriaLabel(_) => "aria-label",
1403            AttributeType::AriaLabelledBy(_) => "aria-labelledby",
1404            AttributeType::AriaDescribedBy(_) => "aria-describedby",
1405            AttributeType::AriaRole(_) => "role",
1406            AttributeType::AriaState(nv) => nv.attr_name.as_str(),
1407            AttributeType::AriaProperty(nv) => nv.attr_name.as_str(),
1408            AttributeType::Href(_) => "href",
1409            AttributeType::Rel(_) => "rel",
1410            AttributeType::Target(_) => "target",
1411            AttributeType::Src(_) => "src",
1412            AttributeType::Alt(_) => "alt",
1413            AttributeType::Title(_) => "title",
1414            AttributeType::Name(_) => "name",
1415            AttributeType::Value(_) => "value",
1416            AttributeType::InputType(_) => "type",
1417            AttributeType::Placeholder(_) => "placeholder",
1418            AttributeType::Required => "required",
1419            AttributeType::Disabled => "disabled",
1420            AttributeType::Readonly => "readonly",
1421            AttributeType::CheckedTrue => "checked",
1422            AttributeType::CheckedFalse => "checked",
1423            AttributeType::Selected => "selected",
1424            AttributeType::Max(_) => "max",
1425            AttributeType::Min(_) => "min",
1426            AttributeType::Step(_) => "step",
1427            AttributeType::Pattern(_) => "pattern",
1428            AttributeType::MinLength(_) => "minlength",
1429            AttributeType::MaxLength(_) => "maxlength",
1430            AttributeType::Autocomplete(_) => "autocomplete",
1431            AttributeType::Scope(_) => "scope",
1432            AttributeType::ColSpan(_) => "colspan",
1433            AttributeType::RowSpan(_) => "rowspan",
1434            AttributeType::TabIndex(_) => "tabindex",
1435            AttributeType::Focusable => "tabindex",
1436            AttributeType::Lang(_) => "lang",
1437            AttributeType::Dir(_) => "dir",
1438            AttributeType::ContentEditable(_) => "contenteditable",
1439            AttributeType::Draggable(_) => "draggable",
1440            AttributeType::Hidden => "hidden",
1441            AttributeType::Data(nv) => nv.attr_name.as_str(),
1442            AttributeType::Custom(nv) => nv.attr_name.as_str(),
1443        }
1444    }
1445
1446    /// Get the attribute value as a string
1447    pub fn value(&self) -> AzString {
1448        match self {
1449            AttributeType::Id(v)
1450            | AttributeType::Class(v)
1451            | AttributeType::AriaLabel(v)
1452            | AttributeType::AriaLabelledBy(v)
1453            | AttributeType::AriaDescribedBy(v)
1454            | AttributeType::AriaRole(v)
1455            | AttributeType::Href(v)
1456            | AttributeType::Rel(v)
1457            | AttributeType::Target(v)
1458            | AttributeType::Src(v)
1459            | AttributeType::Alt(v)
1460            | AttributeType::Title(v)
1461            | AttributeType::Name(v)
1462            | AttributeType::Value(v)
1463            | AttributeType::InputType(v)
1464            | AttributeType::Placeholder(v)
1465            | AttributeType::Max(v)
1466            | AttributeType::Min(v)
1467            | AttributeType::Step(v)
1468            | AttributeType::Pattern(v)
1469            | AttributeType::Autocomplete(v)
1470            | AttributeType::Scope(v)
1471            | AttributeType::Lang(v)
1472            | AttributeType::Dir(v) => v.clone(),
1473
1474            AttributeType::AriaState(nv)
1475            | AttributeType::AriaProperty(nv)
1476            | AttributeType::Data(nv)
1477            | AttributeType::Custom(nv) => nv.value.clone(),
1478
1479            AttributeType::MinLength(n)
1480            | AttributeType::MaxLength(n)
1481            | AttributeType::ColSpan(n)
1482            | AttributeType::RowSpan(n)
1483            | AttributeType::TabIndex(n) => n.to_string().into(),
1484
1485            AttributeType::Focusable => "0".into(),
1486            AttributeType::ContentEditable(b) | AttributeType::Draggable(b) => {
1487                if *b {
1488                    "true".into()
1489                } else {
1490                    "false".into()
1491                }
1492            }
1493
1494            AttributeType::Required
1495            | AttributeType::Disabled
1496            | AttributeType::Readonly
1497            | AttributeType::CheckedTrue
1498                | AttributeType::CheckedFalse
1499            | AttributeType::Selected
1500            | AttributeType::Hidden => "".into(), // Boolean attributes
1501        }
1502    }
1503
1504    /// Check if this is a boolean attribute (present = true, absent = false)
1505    pub fn is_boolean(&self) -> bool {
1506        matches!(
1507            self,
1508            AttributeType::Required
1509                | AttributeType::Disabled
1510                | AttributeType::Readonly
1511                | AttributeType::CheckedTrue
1512                | AttributeType::CheckedFalse
1513                | AttributeType::Selected
1514                | AttributeType::Hidden
1515        )
1516    }
1517}
1518
1519/// Represents all data associated with a single DOM node, such as its type,
1520/// classes, IDs, callbacks, and inline styles.
1521#[repr(C)]
1522#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1523pub struct NodeData {
1524    /// `div`, `p`, `img`, etc.
1525    pub node_type: NodeType,
1526    /// Callbacks attached to this node:
1527    ///
1528    /// `On::MouseUp` -> `Callback(my_button_click_handler)`
1529    pub callbacks: CoreCallbackDataVec,
1530    /// Inline style: a `Css` value that applies only to this node (implicit `:scope`).
1531    /// Each rule carries conditions (@media/@os/:hover/...) and declarations; rules
1532    /// produced by parsing inline strings are tagged `rule_priority::INLINE`, while
1533    /// widget defaults pushed via `with_css_props` keep the same INLINE priority so
1534    /// they override author CSS — preserving the cascade priority that the previous
1535    /// per-property `css_props` field had.
1536    pub style: azul_css::css::Css,
1537    /// Packed flags: tab_index + contenteditable + is_anonymous.
1538    pub flags: NodeFlags,
1539    /// Optional extra accessibility information about this DOM node (MSAA, AT-SPI, UA).
1540    /// 8 bytes (Option<Box<T>> is pointer-sized).
1541    pub accessibility: Option<Box<AccessibilityInfo>>,
1542    /// Stores "extra", not commonly used data of the node: clip-mask, menus, etc.
1543    ///
1544    /// SHOULD NOT EXPOSED IN THE API - necessary to retroactively add functionality
1545    /// to the node without breaking the ABI.
1546    extra: Option<Box<NodeDataExt>>,
1547}
1548
1549impl_option!(
1550    NodeData,
1551    OptionNodeData,
1552    copy = false,
1553    [Debug, PartialEq, Eq, PartialOrd, Ord]
1554);
1555
1556impl Hash for NodeData {
1557    fn hash<H: Hasher>(&self, state: &mut H) {
1558        self.node_type.hash(state);
1559        self.attributes().as_ref().hash(state);
1560        self.flags.hash(state);
1561
1562        // NOTE: callbacks are NOT hashed regularly, otherwise
1563        // they'd cause inconsistencies because of the scroll callback
1564        for callback in self.callbacks.as_ref().iter() {
1565            callback.event.hash(state);
1566            callback.callback.hash(state);
1567            callback.refany.get_type_id().hash(state);
1568        }
1569
1570        // Hash inline CSS properties (Static declarations only — same set the
1571        // legacy `css_props` field hashed). Conditions are intentionally
1572        // skipped to match the previous behaviour.
1573        for (prop, _conds) in self.style.iter_inline_properties() {
1574            core::mem::discriminant(prop).hash(state);
1575        }
1576        if let Some(ext) = self.extra.as_ref() {
1577            if let Some(ds) = ext.dataset.as_ref() {
1578                ds.hash(state);
1579            }
1580            if let Some(c) = ext.svg_data.as_ref() {
1581                c.hash(state);
1582            }
1583            if let Some(c) = ext.menu_bar.as_ref() {
1584                c.hash(state);
1585            }
1586            if let Some(c) = ext.context_menu.as_ref() {
1587                c.hash(state);
1588            }
1589            if let Some(vv) = ext.virtual_view.as_ref() {
1590                vv.hash(state);
1591            }
1592        }
1593    }
1594}
1595
1596/// Tracks which component rendered a DOM subtree.
1597///
1598/// When a component's `render_fn` returns a `StyledDom`, the framework stamps the
1599/// root node(s) of the output with a `ComponentOrigin`. This enables:
1600/// - The debugger to show a "Component Tree" alongside the DOM tree
1601/// - Code generation roundtrips (rendered DOM → component invocations → code)
1602/// - Clicking a DOM node to navigate to the component that produced it
1603#[derive(Debug, Clone, PartialEq)]
1604pub struct ComponentOrigin {
1605    /// Qualified component name, e.g. "shadcn:card", "builtin:div"
1606    pub component_id: AzString,
1607    /// Snapshot of the data model at render time, stored as a JSON value.
1608    /// The debug server can inspect typed values; the frontend serializes
1609    /// them back to JSON for display and editing.
1610    pub data_model_json: crate::json::Json,
1611}
1612
1613// Manual impls because Json contains f64 (no Eq/Ord/Hash derive),
1614// but we need them for NodeDataExt. We compare on the Display string.
1615impl Eq for ComponentOrigin {}
1616
1617impl PartialOrd for ComponentOrigin {
1618    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1619        Some(self.cmp(other))
1620    }
1621}
1622
1623impl Ord for ComponentOrigin {
1624    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1625        self.component_id.cmp(&other.component_id)
1626            .then_with(|| {
1627                let a = alloc::format!("{}", self.data_model_json);
1628                let b = alloc::format!("{}", other.data_model_json);
1629                a.cmp(&b)
1630            })
1631    }
1632}
1633
1634impl core::hash::Hash for ComponentOrigin {
1635    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
1636        self.component_id.hash(state);
1637        let s = alloc::format!("{}", self.data_model_json);
1638        s.hash(state);
1639    }
1640}
1641
1642impl Default for ComponentOrigin {
1643    fn default() -> Self {
1644        Self {
1645            component_id: AzString::from_const_str(""),
1646            data_model_json: crate::json::Json::null(),
1647        }
1648    }
1649}
1650
1651/// SVG-specific data stored on a DOM node.
1652///
1653/// Each SVG element type stores its parsed attribute data here.
1654/// Also used for raster image clip masks (legacy C API).
1655#[derive(Debug, Clone, PartialEq, PartialOrd)]
1656pub enum SvgNodeData {
1657    /// Raster R8 image clip mask (legacy C API for chart.c style manual masks).
1658    ImageClipMask(ImageMask),
1659    /// `<path d="...">` — resolved path geometry.
1660    Path(crate::svg::SvgMultiPolygon),
1661    /// `<circle cx="" cy="" r="">`.
1662    Circle { cx: f32, cy: f32, r: f32 },
1663    /// `<rect x="" y="" width="" height="" rx="" ry="">`.
1664    Rect { x: f32, y: f32, width: f32, height: f32, rx: f32, ry: f32 },
1665    /// `<ellipse cx="" cy="" rx="" ry="">`.
1666    Ellipse { cx: f32, cy: f32, rx: f32, ry: f32 },
1667    /// `<line x1="" y1="" x2="" y2="">`.
1668    Line { x1: f32, y1: f32, x2: f32, y2: f32 },
1669    /// `<polygon points="">` / `<polyline points="">` — parsed point list.
1670    PointsList { points: alloc::vec::Vec<azul_css::props::basic::SvgPoint>, closed: bool },
1671    /// `<svg viewBox="" width="" height="">` — viewport attributes.
1672    ViewBox { min_x: f32, min_y: f32, width: f32, height: f32 },
1673    /// `<linearGradient>` attributes.
1674    LinearGradient { x1: f32, y1: f32, x2: f32, y2: f32 },
1675    /// `<radialGradient>` attributes.
1676    RadialGradient { cx: f32, cy: f32, r: f32, fx: f32, fy: f32 },
1677    /// `<stop offset="" stop-color="" stop-opacity="">`.
1678    GradientStop { offset: f32 },
1679    /// `<use href="" x="" y="">`.
1680    Use { href: AzString, x: f32, y: f32 },
1681    /// `<image href="" x="" y="" width="" height="">`.
1682    SvgImageData { href: AzString, x: f32, y: f32, width: f32, height: f32 },
1683}
1684
1685impl Eq for SvgNodeData {}
1686
1687impl Ord for SvgNodeData {
1688    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1689        self.partial_cmp(other).unwrap_or(core::cmp::Ordering::Equal)
1690    }
1691}
1692
1693impl Hash for SvgNodeData {
1694    fn hash<H: Hasher>(&self, state: &mut H) {
1695        core::mem::discriminant(self).hash(state);
1696        match self {
1697            SvgNodeData::ImageClipMask(m) => m.hash(state),
1698            SvgNodeData::Path(mp) => {
1699                for ring in mp.rings.as_ref().iter() {
1700                    for item in ring.items.as_ref().iter() {
1701                        match item {
1702                            crate::svg::SvgPathElement::Line(l) => {
1703                                0u8.hash(state);
1704                                l.start.x.to_bits().hash(state);
1705                                l.start.y.to_bits().hash(state);
1706                                l.end.x.to_bits().hash(state);
1707                                l.end.y.to_bits().hash(state);
1708                            }
1709                            crate::svg::SvgPathElement::QuadraticCurve(q) => {
1710                                1u8.hash(state);
1711                                q.start.x.to_bits().hash(state);
1712                                q.start.y.to_bits().hash(state);
1713                                q.ctrl.x.to_bits().hash(state);
1714                                q.ctrl.y.to_bits().hash(state);
1715                                q.end.x.to_bits().hash(state);
1716                                q.end.y.to_bits().hash(state);
1717                            }
1718                            crate::svg::SvgPathElement::CubicCurve(c) => {
1719                                2u8.hash(state);
1720                                c.start.x.to_bits().hash(state);
1721                                c.start.y.to_bits().hash(state);
1722                                c.ctrl_1.x.to_bits().hash(state);
1723                                c.ctrl_1.y.to_bits().hash(state);
1724                                c.ctrl_2.x.to_bits().hash(state);
1725                                c.ctrl_2.y.to_bits().hash(state);
1726                                c.end.x.to_bits().hash(state);
1727                                c.end.y.to_bits().hash(state);
1728                            }
1729                        }
1730                    }
1731                }
1732            }
1733            SvgNodeData::Circle { cx, cy, r } => {
1734                cx.to_bits().hash(state); cy.to_bits().hash(state); r.to_bits().hash(state);
1735            }
1736            SvgNodeData::Rect { x, y, width, height, rx, ry } => {
1737                x.to_bits().hash(state); y.to_bits().hash(state);
1738                width.to_bits().hash(state); height.to_bits().hash(state);
1739                rx.to_bits().hash(state); ry.to_bits().hash(state);
1740            }
1741            SvgNodeData::Ellipse { cx, cy, rx, ry } => {
1742                cx.to_bits().hash(state); cy.to_bits().hash(state);
1743                rx.to_bits().hash(state); ry.to_bits().hash(state);
1744            }
1745            SvgNodeData::Line { x1, y1, x2, y2 } => {
1746                x1.to_bits().hash(state); y1.to_bits().hash(state);
1747                x2.to_bits().hash(state); y2.to_bits().hash(state);
1748            }
1749            SvgNodeData::PointsList { points, closed } => {
1750                for p in points.iter() {
1751                    p.x.to_bits().hash(state); p.y.to_bits().hash(state);
1752                }
1753                closed.hash(state);
1754            }
1755            SvgNodeData::ViewBox { min_x, min_y, width, height } => {
1756                min_x.to_bits().hash(state); min_y.to_bits().hash(state);
1757                width.to_bits().hash(state); height.to_bits().hash(state);
1758            }
1759            SvgNodeData::LinearGradient { x1, y1, x2, y2 } => {
1760                x1.to_bits().hash(state); y1.to_bits().hash(state);
1761                x2.to_bits().hash(state); y2.to_bits().hash(state);
1762            }
1763            SvgNodeData::RadialGradient { cx, cy, r, fx, fy } => {
1764                cx.to_bits().hash(state); cy.to_bits().hash(state);
1765                r.to_bits().hash(state); fx.to_bits().hash(state);
1766                fy.to_bits().hash(state);
1767            }
1768            SvgNodeData::GradientStop { offset } => {
1769                offset.to_bits().hash(state);
1770            }
1771            SvgNodeData::Use { href, x, y } => {
1772                href.hash(state);
1773                x.to_bits().hash(state); y.to_bits().hash(state);
1774            }
1775            SvgNodeData::SvgImageData { href, x, y, width, height } => {
1776                href.hash(state);
1777                x.to_bits().hash(state); y.to_bits().hash(state);
1778                width.to_bits().hash(state); height.to_bits().hash(state);
1779            }
1780        }
1781    }
1782}
1783
1784/// NOTE: NOT EXPOSED IN THE API! Stores extra,
1785/// not commonly used information for the NodeData.
1786/// This helps keep the primary `NodeData` struct smaller for common cases.
1787#[repr(C)]
1788#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1789pub struct NodeDataExt {
1790    /// Strongly-typed HTML attributes (aria-*, href, alt, etc.)
1791    /// IDs and classes are stored as `AttributeType::Id` and `AttributeType::Class` entries.
1792    /// Moved from NodeData to save 48B for the ~95% of nodes with no attributes.
1793    pub attributes: AttributeTypeVec,
1794    /// VirtualView callback data, only set when node_type == NodeType::VirtualView.
1795    pub virtual_view: Option<VirtualViewNode>,
1796    /// `data-*` attributes for this node, useful to store UI-related data on the node itself.
1797    pub dataset: Option<RefAny>,
1798    /// SVG-specific data or raster clip mask for this DOM node.
1799    pub svg_data: Option<SvgNodeData>,
1800    /// Menu bar that should be displayed at the top of this nodes rect.
1801    pub menu_bar: Option<Box<Menu>>,
1802    /// Context menu that should be opened when the item is left-clicked.
1803    pub context_menu: Option<Box<Menu>>,
1804    /// Stable key for reconciliation. If provided, allows the framework to track
1805    /// this node across frames even if its position in the array changes.
1806    /// This is crucial for correct lifecycle events when lists are reordered.
1807    pub key: Option<u64>,
1808    /// Callback to merge dataset state from a previous frame's node into the current node.
1809    /// This enables heavy resource preservation (video decoders, GL textures) across frames.
1810    pub dataset_merge_callback: Option<DatasetMergeCallback>,
1811    /// Tracks which component rendered this DOM subtree.
1812    /// Set by the framework during component rendering — the root node(s) of a
1813    /// component's output DOM get stamped with the component's qualified name.
1814    /// Enables the debugger to reconstruct the component invocation tree from the
1815    /// flat rendered DOM, and enables code generation roundtrips.
1816    pub component_origin: Option<ComponentOrigin>,
1817}
1818
1819/// A callback function used to merge the state of an old dataset into a new one.
1820///
1821/// This enables components with heavy internal state (video players, WebGL contexts)
1822/// to preserve their resources across frames, while the DOM tree is recreated.
1823///
1824/// The callback receives both the old and new datasets as `RefAny` (cheap shallow clones)
1825/// and returns the dataset that should be used for the new node.
1826///
1827/// # Example
1828///
1829/// ```rust,ignore
1830/// fn merge_video_state(new_data: RefAny, old_data: RefAny) -> RefAny {
1831///     // Transfer heavy resources from old to new
1832///     if let (Some(mut new), Some(old)) = (
1833///         new_data.downcast_mut::<VideoState>(),
1834///         old_data.downcast_ref::<VideoState>()
1835///     ) {
1836///         new.decoder = old.decoder.take();
1837///         new.gl_texture = old.gl_texture.take();
1838///     }
1839///     new_data // Return the merged state
1840/// }
1841/// ```
1842#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1843#[repr(C)]
1844pub struct DatasetMergeCallback {
1845    /// The function pointer that performs the merge.
1846    /// Signature: `fn(new_data: RefAny, old_data: RefAny) -> RefAny`
1847    pub cb: DatasetMergeCallbackType,
1848    /// Optional callable for FFI language bindings (Python, etc.)
1849    /// When set, the FFI layer can invoke this instead of `cb`.
1850    pub callable: OptionRefAny,
1851}
1852
1853impl core::fmt::Debug for DatasetMergeCallback {
1854    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1855        f.debug_struct("DatasetMergeCallback")
1856            .field("cb", &(self.cb as usize))
1857            .field("callable", &self.callable)
1858            .finish()
1859    }
1860}
1861
1862/// Allow creating DatasetMergeCallback from a raw function pointer.
1863/// This enables the `Into<DatasetMergeCallback>` pattern for Python bindings.
1864impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
1865    fn from(cb: DatasetMergeCallbackType) -> Self {
1866        DatasetMergeCallback {
1867            cb,
1868            callable: OptionRefAny::None,
1869        }
1870    }
1871}
1872
1873impl_option!(
1874    DatasetMergeCallback,
1875    OptionDatasetMergeCallback,
1876    copy = false,
1877    [Debug, Clone]
1878);
1879
1880/// Function pointer type for dataset merge callbacks.
1881///
1882/// Arguments:
1883/// - `new_data`: The new node's dataset (shallow clone, cheap)
1884/// - `old_data`: The old node's dataset (shallow clone, cheap)
1885///
1886/// Returns:
1887/// - The `RefAny` that should be used as the dataset for the new node
1888pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
1889
1890impl Clone for NodeData {
1891    #[inline]
1892    fn clone(&self) -> Self {
1893        Self {
1894            node_type: self.node_type.into_library_owned_nodetype(),
1895            style: self.style.clone(),
1896            callbacks: self.callbacks.clone(),
1897            flags: self.flags,
1898            accessibility: self.accessibility.clone(),
1899            extra: self.extra.clone(),
1900        }
1901    }
1902}
1903
1904// Clone, PartialEq, Eq, Hash, PartialOrd, Ord
1905impl_vec!(NodeData, NodeDataVec, NodeDataVecDestructor, NodeDataVecDestructorType, NodeDataVecSlice, OptionNodeData);
1906impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
1907impl_vec_mut!(NodeData, NodeDataVec);
1908impl_vec_debug!(NodeData, NodeDataVec);
1909impl_vec_partialord!(NodeData, NodeDataVec);
1910impl_vec_ord!(NodeData, NodeDataVec);
1911impl_vec_partialeq!(NodeData, NodeDataVec);
1912impl_vec_eq!(NodeData, NodeDataVec);
1913impl_vec_hash!(NodeData, NodeDataVec);
1914
1915impl NodeDataVec {
1916    #[inline]
1917    pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, NodeData> {
1918        NodeDataContainerRef {
1919            internal: self.as_ref(),
1920        }
1921    }
1922    #[inline]
1923    pub fn as_container_mut<'a>(&'a mut self) -> NodeDataContainerRefMut<'a, NodeData> {
1924        NodeDataContainerRefMut {
1925            internal: self.as_mut(),
1926        }
1927    }
1928}
1929
1930// SAFETY: All fields in NodeData are either Send (NodeType, NodeFlags, CssPropertyWithConditionsVec),
1931// Arc-wrapped (RefAny), or plain data (Box<AccessibilityInfo>, Box<NodeDataExt>).
1932// Function pointers (callbacks) are inherently Send. The RefAny uses atomic reference counting.
1933unsafe impl Send for NodeData {}
1934
1935/// Determines the behavior of an element in sequential focus navigation
1936// (e.g., using the Tab key).
1937#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1938#[repr(C, u8)]
1939pub enum TabIndex {
1940    /// Automatic tab index, similar to simply setting `focusable = "true"` or `tabindex = 0`
1941    /// (both have the effect of making the element focusable).
1942    ///
1943    /// Sidenote: See https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
1944    /// for interesting notes on tabindex and accessibility
1945    Auto,
1946    /// Set the tab index in relation to its parent element. I.e. if you have a list of elements,
1947    /// the focusing order is restricted to the current parent.
1948    ///
1949    /// When pressing tab repeatedly, the focusing order will be
1950    /// determined by OverrideInParent elements taking precedence among global order.
1951    OverrideInParent(u32),
1952    /// Elements can be focused in callbacks, but are not accessible via
1953    /// keyboard / tab navigation (-1).
1954    NoKeyboardFocus,
1955}
1956
1957impl_option!(
1958    TabIndex,
1959    OptionTabIndex,
1960    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1961);
1962
1963impl TabIndex {
1964    /// Returns the HTML-compatible number of the `tabindex` element.
1965    pub fn get_index(&self) -> isize {
1966        use self::TabIndex::*;
1967        match self {
1968            Auto => 0,
1969            OverrideInParent(x) => *x as isize,
1970            NoKeyboardFocus => -1,
1971        }
1972    }
1973}
1974
1975impl Default for TabIndex {
1976    fn default() -> Self {
1977        TabIndex::Auto
1978    }
1979}
1980
1981/// Packed representation of tab index + contenteditable flag.
1982///
1983/// Bit layout (32 bits):
1984///   [31]     contenteditable flag (1 = true)
1985///   [30:29]  tab_index variant:
1986///              00 = None (no tab index set)
1987///              01 = Auto
1988///              10 = OverrideInParent (value in bits [28:0])
1989///              11 = NoKeyboardFocus
1990///   [28]     is_anonymous (1 = anonymous box for table layout)
1991///   [27:0]   OverrideInParent value (max ~268 million)
1992#[repr(C)]
1993#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1994pub struct NodeFlags {
1995    pub inner: u32,
1996}
1997
1998impl Default for NodeFlags {
1999    fn default() -> Self {
2000        NodeFlags { inner: 0 }
2001    }
2002}
2003
2004impl NodeFlags {
2005    const CONTENTEDITABLE_BIT: u32 = 1 << 31;
2006    const TAB_INDEX_MASK: u32      = 0b11 << 29;
2007    const ANONYMOUS_BIT: u32       = 1 << 28;
2008    const TAB_VALUE_MASK: u32      = (1 << 28) - 1;
2009
2010    const TAB_NONE: u32            = 0b00 << 29;
2011    const TAB_AUTO: u32            = 0b01 << 29;
2012    const TAB_OVERRIDE: u32        = 0b10 << 29;
2013    const TAB_NO_KEYBOARD: u32     = 0b11 << 29;
2014
2015    pub const fn new() -> Self {
2016        NodeFlags { inner: 0 }
2017    }
2018
2019    pub const fn is_contenteditable(&self) -> bool {
2020        (self.inner & Self::CONTENTEDITABLE_BIT) != 0
2021    }
2022
2023    pub const fn set_contenteditable(mut self, v: bool) -> Self {
2024        if v {
2025            self.inner |= Self::CONTENTEDITABLE_BIT;
2026        } else {
2027            self.inner &= !Self::CONTENTEDITABLE_BIT;
2028        }
2029        self
2030    }
2031
2032    pub fn set_contenteditable_mut(&mut self, v: bool) {
2033        if v {
2034            self.inner |= Self::CONTENTEDITABLE_BIT;
2035        } else {
2036            self.inner &= !Self::CONTENTEDITABLE_BIT;
2037        }
2038    }
2039
2040    pub fn get_tab_index(&self) -> Option<TabIndex> {
2041        match self.inner & Self::TAB_INDEX_MASK {
2042            x if x == Self::TAB_NONE => None,
2043            x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
2044            x if x == Self::TAB_OVERRIDE => {
2045                let val = self.inner & Self::TAB_VALUE_MASK;
2046                Some(TabIndex::OverrideInParent(val))
2047            }
2048            x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
2049            _ => None,
2050        }
2051    }
2052
2053    /// Returns whether this node is an anonymous box generated for table layout.
2054    pub const fn is_anonymous(&self) -> bool {
2055        (self.inner & Self::ANONYMOUS_BIT) != 0
2056    }
2057
2058    pub fn set_anonymous(&mut self, v: bool) {
2059        if v {
2060            self.inner |= Self::ANONYMOUS_BIT;
2061        } else {
2062            self.inner &= !Self::ANONYMOUS_BIT;
2063        }
2064    }
2065
2066    pub fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
2067        // Clear tab index bits (bits 29-30) and value bits (bits 0-27)
2068        // keep contenteditable bit (31) and anonymous bit (28)
2069        self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
2070        match tab_index {
2071            None => { /* TAB_NONE = 0, already cleared */ }
2072            Some(TabIndex::Auto) => {
2073                self.inner |= Self::TAB_AUTO;
2074            }
2075            Some(TabIndex::OverrideInParent(val)) => {
2076                self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
2077            }
2078            Some(TabIndex::NoKeyboardFocus) => {
2079                self.inner |= Self::TAB_NO_KEYBOARD;
2080            }
2081        }
2082    }
2083}
2084
2085impl Default for NodeData {
2086    fn default() -> Self {
2087        NodeData::create_node(NodeType::Div)
2088    }
2089}
2090
2091impl fmt::Display for NodeData {
2092    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2093        let html_type = self.node_type.get_path();
2094        let attributes_string = node_data_to_string(&self);
2095
2096        match self.node_type.format() {
2097            Some(content) => write!(
2098                f,
2099                "<{}{}>{}</{}>",
2100                html_type, attributes_string, content, html_type
2101            ),
2102            None => write!(f, "<{}{}/>", html_type, attributes_string),
2103        }
2104    }
2105}
2106
2107fn node_data_to_string(node_data: &NodeData) -> String {
2108    let mut id_string = String::new();
2109    let ids = node_data
2110        .attributes()
2111        .as_ref()
2112        .iter()
2113        .filter_map(|s| s.as_id())
2114        .collect::<Vec<_>>()
2115        .join(" ");
2116
2117    if !ids.is_empty() {
2118        id_string = format!(" id=\"{}\" ", ids);
2119    }
2120
2121    let mut class_string = String::new();
2122    let classes = node_data
2123        .attributes()
2124        .as_ref()
2125        .iter()
2126        .filter_map(|s| s.as_class())
2127        .collect::<Vec<_>>()
2128        .join(" ");
2129
2130    if !classes.is_empty() {
2131        class_string = format!(" class=\"{}\" ", classes);
2132    }
2133
2134    let mut tabindex_string = String::new();
2135    if let Some(tab_index) = node_data.get_tab_index() {
2136        tabindex_string = format!(" tabindex=\"{}\" ", tab_index.get_index());
2137    };
2138
2139    format!("{}{}{}", id_string, class_string, tabindex_string)
2140}
2141
2142impl NodeData {
2143    /// Creates a new `NodeData` instance from a given `NodeType`.
2144    #[inline]
2145    pub const fn create_node(node_type: NodeType) -> Self {
2146        Self {
2147            node_type,
2148            callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2149            style: azul_css::css::Css {
2150                rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2151            },
2152            flags: NodeFlags::new(),
2153            accessibility: None,
2154            extra: None,
2155        }
2156    }
2157
2158    /// Returns a reference to the node's attributes (from NodeDataExt).
2159    /// Returns an empty slice if no attributes have been set.
2160    #[inline]
2161    pub fn attributes(&self) -> &AttributeTypeVec {
2162        static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
2163        match &self.extra {
2164            Some(ext) => &ext.attributes,
2165            None => &EMPTY,
2166        }
2167    }
2168
2169    /// Returns a mutable reference to the node's attributes,
2170    /// lazily allocating NodeDataExt if needed.
2171    #[inline]
2172    pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
2173        &mut self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes
2174    }
2175
2176    /// Sets the node's attributes, replacing any existing ones.
2177    #[inline]
2178    pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
2179        self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes = attrs;
2180    }
2181
2182    /// Shorthand for `NodeData::create_node(NodeType::Body)`.
2183    #[inline(always)]
2184    pub const fn create_body() -> Self {
2185        Self::create_node(NodeType::Body)
2186    }
2187
2188    /// Shorthand for `NodeData::create_node(NodeType::Div)`.
2189    #[inline(always)]
2190    pub const fn create_div() -> Self {
2191        Self::create_node(NodeType::Div)
2192    }
2193
2194    /// Shorthand for `NodeData::create_node(NodeType::Br)`.
2195    #[inline(always)]
2196    pub const fn create_br() -> Self {
2197        Self::create_node(NodeType::Br)
2198    }
2199
2200    /// Shorthand for `NodeData::create_node(NodeType::Text(value.into()))`.
2201    #[inline(always)]
2202    pub fn create_text<S: Into<AzString>>(value: S) -> Self {
2203        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
2204    }
2205
2206    /// Shorthand for `NodeData::create_node(NodeType::Image(image_id))`.
2207    #[inline(always)]
2208    pub fn create_image(image: ImageRef) -> Self {
2209        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
2210    }
2211
2212    #[inline(always)]
2213    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
2214        let mut nd = Self::create_node(NodeType::VirtualView);
2215        let ext = nd.extra.get_or_insert_with(|| Box::new(NodeDataExt::default()));
2216        ext.virtual_view = Some(VirtualViewNode {
2217            callback: callback.into(),
2218            refany: data,
2219        });
2220        nd
2221    }
2222
2223    // -- Accessibility-aware NodeData constructors --
2224    // Each a11y-able element has two constructors: the canonical one takes a
2225    // `SmallAriaInfo` so the caller must opt in to an accessible name, and the
2226    // `*_no_a11y` variant is a deliberate escape hatch with a longer name.
2227
2228    fn with_attribute(mut self, attr: AttributeType) -> Self {
2229        let mut v = self.attributes().clone().into_library_owned_vec();
2230        v.push(attr);
2231        self.set_attributes(v.into());
2232        self
2233    }
2234
2235    /// Creates a button `NodeData` with accessibility information.
2236    #[inline]
2237    pub fn create_button(aria: SmallAriaInfo) -> Self {
2238        let mut nd = Self::create_node(NodeType::Button);
2239        nd.set_accessibility_info(aria.to_full_info());
2240        nd
2241    }
2242
2243    /// Creates a button `NodeData` without accessibility information.
2244    #[inline]
2245    pub fn create_button_no_a11y() -> Self {
2246        Self::create_node(NodeType::Button)
2247    }
2248
2249    /// Creates an anchor `NodeData` with an href and accessibility information.
2250    #[inline]
2251    pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2252        let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2253        nd.set_accessibility_info(aria.to_full_info());
2254        nd
2255    }
2256
2257    /// Creates an anchor `NodeData` with an href but no accessibility information.
2258    #[inline]
2259    pub fn create_a_no_a11y(href: AzString) -> Self {
2260        Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
2261    }
2262
2263    /// Creates an input `NodeData` with accessibility information.
2264    #[inline]
2265    pub fn create_input(
2266        input_type: AzString,
2267        name: AzString,
2268        label: AzString,
2269        aria: SmallAriaInfo,
2270    ) -> Self {
2271        let mut nd = Self::create_node(NodeType::Input)
2272            .with_attribute(AttributeType::InputType(input_type))
2273            .with_attribute(AttributeType::Name(name))
2274            .with_attribute(AttributeType::AriaLabel(label));
2275        nd.set_accessibility_info(aria.to_full_info());
2276        nd
2277    }
2278
2279    /// Creates an input `NodeData` without accessibility information.
2280    #[inline]
2281    pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2282        Self::create_node(NodeType::Input)
2283            .with_attribute(AttributeType::InputType(input_type))
2284            .with_attribute(AttributeType::Name(name))
2285            .with_attribute(AttributeType::AriaLabel(label))
2286    }
2287
2288    /// Creates a textarea `NodeData` with accessibility information.
2289    #[inline]
2290    pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2291        let mut nd = Self::create_node(NodeType::TextArea)
2292            .with_attribute(AttributeType::Name(name))
2293            .with_attribute(AttributeType::AriaLabel(label));
2294        nd.set_accessibility_info(aria.to_full_info());
2295        nd
2296    }
2297
2298    /// Creates a textarea `NodeData` without accessibility information.
2299    #[inline]
2300    pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2301        Self::create_node(NodeType::TextArea)
2302            .with_attribute(AttributeType::Name(name))
2303            .with_attribute(AttributeType::AriaLabel(label))
2304    }
2305
2306    /// Creates a select `NodeData` with accessibility information.
2307    #[inline]
2308    pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2309        let mut nd = Self::create_node(NodeType::Select)
2310            .with_attribute(AttributeType::Name(name))
2311            .with_attribute(AttributeType::AriaLabel(label));
2312        nd.set_accessibility_info(aria.to_full_info());
2313        nd
2314    }
2315
2316    /// Creates a select `NodeData` without accessibility information.
2317    #[inline]
2318    pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2319        Self::create_node(NodeType::Select)
2320            .with_attribute(AttributeType::Name(name))
2321            .with_attribute(AttributeType::AriaLabel(label))
2322    }
2323
2324    /// Creates a table `NodeData` with accessibility information.
2325    #[inline]
2326    pub fn create_table(aria: SmallAriaInfo) -> Self {
2327        let mut nd = Self::create_node(NodeType::Table);
2328        nd.set_accessibility_info(aria.to_full_info());
2329        nd
2330    }
2331
2332    /// Creates a table `NodeData` without accessibility information.
2333    #[inline]
2334    pub fn create_table_no_a11y() -> Self {
2335        Self::create_node(NodeType::Table)
2336    }
2337
2338    /// Creates a label `NodeData` with an associated control ID and accessibility
2339    /// information.
2340    #[inline]
2341    pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
2342        let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2343            AttributeNameValue {
2344                attr_name: "for".into(),
2345                value: for_id,
2346            },
2347        ));
2348        nd.set_accessibility_info(aria.to_full_info());
2349        nd
2350    }
2351
2352    /// Creates a label `NodeData` with an associated control ID but no
2353    /// accessibility information.
2354    #[inline]
2355    pub fn create_label_no_a11y(for_id: AzString) -> Self {
2356        Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(AttributeNameValue {
2357            attr_name: "for".into(),
2358            value: for_id,
2359        }))
2360    }
2361
2362    /// Checks whether this node is of the given node type (div, image, text).
2363    #[inline]
2364    pub fn is_node_type(&self, searched_type: NodeType) -> bool {
2365        self.node_type == searched_type
2366    }
2367
2368    /// Checks whether this node has the searched ID attached.
2369    pub fn has_id(&self, id: &str) -> bool {
2370        self.attributes()
2371            .iter()
2372            .any(|attr| attr.as_id() == Some(id))
2373    }
2374
2375    /// Checks whether this node has the searched class attached.
2376    pub fn has_class(&self, class: &str) -> bool {
2377        self.attributes()
2378            .iter()
2379            .any(|attr| attr.as_class() == Some(class))
2380    }
2381
2382    pub fn has_context_menu(&self) -> bool {
2383        self.extra
2384            .as_ref()
2385            .map(|m| m.context_menu.is_some())
2386            .unwrap_or(false)
2387    }
2388
2389    pub fn is_text_node(&self) -> bool {
2390        matches!(self.node_type, NodeType::Text(_))
2391    }
2392
2393    pub fn is_virtual_view_node(&self) -> bool {
2394        matches!(self.node_type, NodeType::VirtualView)
2395    }
2396
2397    // NOTE: Getters are used here in order to allow changing the memory allocator for the NodeData
2398    // in the future (which is why the fields are all private).
2399
2400    #[inline(always)]
2401    pub const fn get_node_type(&self) -> &NodeType {
2402        &self.node_type
2403    }
2404    #[inline]
2405    pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
2406        self.extra.as_mut().and_then(|e| e.dataset.as_mut())
2407    }
2408    #[inline]
2409    pub fn get_dataset(&self) -> Option<&RefAny> {
2410        self.extra.as_ref().and_then(|e| e.dataset.as_ref())
2411    }
2412    /// Take the dataset out of the node, replacing it with None.
2413    pub fn take_dataset(&mut self) -> Option<RefAny> {
2414        self.extra.as_mut().and_then(|e| e.dataset.take())
2415    }
2416    /// Returns IDs and classes as a computed `IdOrClassVec`.
2417    /// Note: this allocates a new vec each time, prefer `has_id()`/`has_class()` for checks.
2418    #[inline]
2419    pub fn get_ids_and_classes(&self) -> IdOrClassVec {
2420        let v: Vec<IdOrClass> = self.attributes().as_ref().iter().filter_map(|attr| {
2421            match attr {
2422                AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
2423                AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
2424                _ => None,
2425            }
2426        }).collect();
2427        v.into()
2428    }
2429    #[inline(always)]
2430    pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
2431        &self.callbacks
2432    }
2433    #[inline(always)]
2434    pub const fn get_style(&self) -> &azul_css::css::Css {
2435        &self.style
2436    }
2437
2438    #[inline]
2439    pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
2440        self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
2441    }
2442
2443    /// Legacy accessor for raster clip mask. Returns `Some` only for `SvgNodeData::ImageClipMask`.
2444    #[inline]
2445    pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
2446        match self.get_svg_data()? {
2447            SvgNodeData::ImageClipMask(m) => Some(m),
2448            _ => None,
2449        }
2450    }
2451    #[inline]
2452    pub fn get_tab_index(&self) -> Option<TabIndex> {
2453        self.flags.get_tab_index()
2454    }
2455    #[inline]
2456    pub fn get_accessibility_info(&self) -> Option<&Box<AccessibilityInfo>> {
2457        self.accessibility.as_ref()
2458    }
2459    #[inline]
2460    pub fn get_menu_bar(&self) -> Option<&Box<Menu>> {
2461        self.extra.as_ref().and_then(|e| e.menu_bar.as_ref())
2462    }
2463    #[inline]
2464    pub fn get_context_menu(&self) -> Option<&Box<Menu>> {
2465        self.extra.as_ref().and_then(|e| e.context_menu.as_ref())
2466    }
2467
2468    /// Returns whether this node is an anonymous box generated for table layout.
2469    #[inline]
2470    pub fn is_anonymous(&self) -> bool {
2471        self.flags.is_anonymous()
2472    }
2473
2474    #[inline(always)]
2475    pub fn set_node_type(&mut self, node_type: NodeType) {
2476        self.node_type = node_type;
2477    }
2478    #[inline]
2479    pub fn set_dataset(&mut self, data: OptionRefAny) {
2480        match data {
2481            OptionRefAny::None => {
2482                if let Some(ext) = self.extra.as_mut() {
2483                    ext.dataset = None;
2484                }
2485            }
2486            OptionRefAny::Some(r) => {
2487                self.extra
2488                    .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2489                    .dataset = Some(r);
2490            }
2491        }
2492    }
2493    /// Sets the IDs and classes by converting `IdOrClassVec` entries into
2494    /// `AttributeType::Id`/`AttributeType::Class` and merging them into `self.attributes`.
2495    /// Any existing Id/Class attributes are removed first.
2496    #[inline]
2497    pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
2498        // Remove existing Id/Class from attributes
2499        let mut v: AttributeTypeVec = Vec::new().into();
2500        mem::swap(&mut v, self.attributes_mut());
2501        let mut v = v.into_library_owned_vec();
2502        v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
2503        // Convert and append
2504        for ioc in ids_and_classes.as_ref().iter() {
2505            match ioc {
2506                IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
2507                IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
2508            }
2509        }
2510        self.set_attributes(v.into());
2511    }
2512    #[inline(always)]
2513    pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
2514        self.callbacks = callbacks;
2515    }
2516    /// Legacy: replace this node's inline style with a flat list of property+conditions.
2517    /// Each entry becomes a single-declaration rule at `rule_priority::INLINE`. Prefer
2518    /// `set_style` (or `with_style` / `with_css(&str)`) for new code.
2519    #[inline(always)]
2520    pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
2521        self.style = css_props.into();
2522    }
2523    /// Replace this node's inline style with a `Css` value. The Css's rules apply only
2524    /// to this node (implicit `:scope`).
2525    #[inline(always)]
2526    pub fn set_style(&mut self, style: azul_css::css::Css) {
2527        self.style = style;
2528    }
2529    #[inline]
2530    pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
2531        self.extra
2532            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2533            .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
2534    }
2535    #[inline]
2536    pub fn set_svg_data(&mut self, data: SvgNodeData) {
2537        self.extra
2538            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2539            .svg_data = Some(data);
2540    }
2541    #[inline]
2542    pub fn set_tab_index(&mut self, tab_index: TabIndex) {
2543        self.flags.set_tab_index(Some(tab_index));
2544    }
2545    #[inline]
2546    pub fn set_contenteditable(&mut self, contenteditable: bool) {
2547        self.flags.set_contenteditable_mut(contenteditable);
2548    }
2549    #[inline]
2550    pub fn is_contenteditable(&self) -> bool {
2551        self.flags.is_contenteditable()
2552    }
2553    #[inline]
2554    pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
2555        self.accessibility = Some(Box::new(accessibility_info));
2556    }
2557
2558    /// Marks this node as an anonymous box (generated for table layout).
2559    #[inline]
2560    pub fn set_anonymous(&mut self, is_anonymous: bool) {
2561        self.flags.set_anonymous(is_anonymous);
2562    }
2563    #[inline]
2564    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
2565        self.extra
2566            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2567            .menu_bar = Some(Box::new(menu_bar));
2568    }
2569    #[inline]
2570    pub fn set_context_menu(&mut self, context_menu: Menu) {
2571        self.extra
2572            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2573            .context_menu = Some(Box::new(context_menu));
2574    }
2575
2576    /// Sets a stable key for this node used in reconciliation.
2577    ///
2578    /// This key is used to track node identity across DOM updates, enabling
2579    /// the framework to distinguish between "moving" a node and "destroying/creating" one.
2580    /// This is crucial for correct lifecycle events when lists are reordered.
2581    ///
2582    /// # Example
2583    /// ```rust
2584    /// # use azul_core::dom::NodeData;
2585    /// # let mut node_data = NodeData::create_div();
2586    /// node_data.set_key("user-123");
2587    /// ```
2588    #[inline]
2589    pub fn set_key<K: core::hash::Hash>(&mut self, key: K) {
2590        use std::hash::Hasher;
2591        let mut hasher = std::hash::DefaultHasher::new();
2592        key.hash(&mut hasher);
2593        self.extra
2594            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2595            .key = Some(hasher.finish());
2596    }
2597
2598    /// Gets the key for this node, if set.
2599    #[inline]
2600    pub fn get_key(&self) -> Option<u64> {
2601        self.extra.as_ref().and_then(|ext| ext.key)
2602    }
2603
2604    /// Sets a dataset merge callback for this node.
2605    ///
2606    /// The merge callback is invoked during reconciliation when a node from the
2607    /// previous frame is matched with a node in the new frame. It allows heavy
2608    /// resources (video decoders, GL textures, network connections) to be
2609    /// transferred from the old node to the new node instead of being destroyed.
2610    ///
2611    /// # Type Safety
2612    ///
2613    /// The callback stores the `TypeId` of `T`. During execution, both the old
2614    /// and new datasets must match this type, otherwise the merge is skipped.
2615    ///
2616    /// # Example
2617    /// ```rust,ignore
2618    /// struct VideoPlayer {
2619    ///     url: String,
2620    ///     decoder: Option<DecoderHandle>,
2621    /// }
2622    ///
2623    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
2624    ///     // Transfer the heavy decoder handle from old to new
2625    ///     if let (Some(mut new), Some(old)) = (
2626    ///         new_data.downcast_mut::<VideoPlayer>(),
2627    ///         old_data.downcast_ref::<VideoPlayer>()
2628    ///     ) {
2629    ///         new.decoder = old.decoder.take();
2630    ///     }
2631    ///     new_data
2632    /// }
2633    ///
2634    /// node_data.set_merge_callback(merge_video);
2635    /// ```
2636    #[inline]
2637    pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
2638        self.extra
2639            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2640            .dataset_merge_callback = Some(callback.into());
2641    }
2642
2643    /// Gets the merge callback for this node, if set.
2644    #[inline]
2645    pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
2646        self.extra.as_ref().and_then(|ext| ext.dataset_merge_callback.clone())
2647    }
2648
2649    /// Sets the component origin for this node.
2650    ///
2651    /// This stamps the node with information about which component rendered it,
2652    /// enabling the debugger to reconstruct the component invocation tree.
2653    #[inline]
2654    pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
2655        self.extra
2656            .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2657            .component_origin = Some(origin);
2658    }
2659
2660    /// Gets the component origin for this node, if set.
2661    #[inline]
2662    pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
2663        self.extra.as_ref().and_then(|ext| ext.component_origin.as_ref())
2664    }
2665
2666    #[inline]
2667    pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
2668        self.set_menu_bar(menu_bar);
2669        self
2670    }
2671
2672    #[inline]
2673    pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
2674        self.set_context_menu(context_menu);
2675        self
2676    }
2677
2678    #[inline]
2679    pub fn add_callback<C: Into<CoreCallback>>(
2680        &mut self,
2681        event: EventFilter,
2682        data: RefAny,
2683        callback: C,
2684    ) {
2685        let callback = callback.into();
2686        let mut v: CoreCallbackDataVec = Vec::new().into();
2687        mem::swap(&mut v, &mut self.callbacks);
2688        let mut v = v.into_library_owned_vec();
2689        v.push(CoreCallbackData {
2690            event,
2691            refany: data,
2692            callback,
2693        });
2694        self.callbacks = v.into();
2695    }
2696
2697    #[inline]
2698    pub fn add_id(&mut self, s: AzString) {
2699        let mut v: AttributeTypeVec = Vec::new().into();
2700        mem::swap(&mut v, self.attributes_mut());
2701        let mut v = v.into_library_owned_vec();
2702        v.push(AttributeType::Id(s));
2703        self.set_attributes(v.into());
2704    }
2705    #[inline]
2706    pub fn add_class(&mut self, s: AzString) {
2707        let mut v: AttributeTypeVec = Vec::new().into();
2708        mem::swap(&mut v, self.attributes_mut());
2709        let mut v = v.into_library_owned_vec();
2710        v.push(AttributeType::Class(s));
2711        self.set_attributes(v.into());
2712    }
2713
2714    /// Add a CSS property with optional conditions (hover, focus, active, etc.).
2715    ///
2716    /// Wraps the property in a single-declaration rule at `rule_priority::INLINE`
2717    /// and appends it to this node's inline style.
2718    #[inline]
2719    pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
2720        use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
2721        let rule = CssRuleBlock {
2722            path: CssPath { selectors: Vec::new().into() },
2723            declarations: vec![CssDeclaration::Static(p.property)].into(),
2724            conditions: p.apply_if,
2725            priority: rule_priority::INLINE,
2726        };
2727        let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
2728        mem::swap(&mut v, &mut self.style.rules);
2729        let mut v = v.into_library_owned_vec();
2730        v.push(rule);
2731        self.style.rules = v.into();
2732    }
2733
2734    /// Calculates a deterministic node hash for this node.
2735    pub fn calculate_node_data_hash(&self) -> DomNodeHash {
2736        use std::hash::Hasher;
2737        let mut hasher = std::hash::DefaultHasher::new();
2738        self.hash(&mut hasher);
2739        let h = hasher.finish();
2740        DomNodeHash { inner: h }
2741    }
2742
2743    /// Calculates a structural hash for DOM reconciliation that ignores text content.
2744    ///
2745    /// This hash is used for matching nodes across DOM frames where the text content
2746    /// may have changed (e.g., contenteditable text being edited). It hashes:
2747    /// - Node type discriminant (but NOT the text content for Text nodes)
2748    /// - IDs and classes
2749    /// - Attributes (but NOT contenteditable state which may change with focus)
2750    /// - Callback events and types
2751    ///
2752    /// This allows a Text("Hello") node to match Text("Hello World") during reconciliation,
2753    /// preserving cursor position and selection state.
2754    pub fn calculate_structural_hash(&self) -> DomNodeHash {
2755        use std::hash::Hasher;
2756        use core::hash::Hasher as StdHasher;
2757
2758        let mut hasher = std::hash::DefaultHasher::new();
2759
2760        // Hash node type discriminant only, not content
2761        // This means Text("A") and Text("B") have the same structural hash
2762        core::mem::discriminant(&self.node_type).hash(&mut hasher);
2763
2764        // For VirtualView nodes, hash the callback to distinguish different virtualized views
2765        if let NodeType::VirtualView = self.node_type {
2766            if let Some(ext) = self.extra.as_ref() {
2767                if let Some(vv) = ext.virtual_view.as_ref() {
2768                    vv.hash(&mut hasher);
2769                }
2770            }
2771        }
2772
2773        // For Image nodes, hash the image reference to distinguish different images.
2774        // For callback images, hash the callback function pointer and RefAny type ID
2775        // instead of the heap pointer, so that the same callback produces the same
2776        // structural hash across frames (the heap pointer differs each frame because
2777        // ImageRef::new() does Box::into_raw(Box::new(...))).
2778        if let NodeType::Image(ref img_ref) = self.node_type {
2779            match img_ref.get_data() {
2780                crate::resources::DecodedImage::Callback(cb) => {
2781                    // Hash callback function pointer (stable across frames)
2782                    cb.callback.cb.hash(&mut hasher);
2783                    // Hash RefAny type ID (not instance pointer)
2784                    cb.refany.get_type_id().hash(&mut hasher);
2785                }
2786                _ => {
2787                    // Raw images / GL textures: hash normally (pointer identity)
2788                    img_ref.hash(&mut hasher);
2789                }
2790            }
2791        }
2792
2793        // Hash IDs and classes - these are structural and shouldn't change
2794        // (They are now stored as AttributeType::Id / AttributeType::Class in attributes)
2795        for attr in self.attributes().as_ref().iter() {
2796            match attr {
2797                AttributeType::Id(s) => { 0u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2798                AttributeType::Class(s) => { 1u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2799                _ => {}
2800            }
2801        }
2802
2803        // Hash other attributes - but skip contenteditable since that might change
2804        // Also skip Id/Class since they were already hashed above
2805        for attr in self.attributes().as_ref().iter() {
2806            if !matches!(attr, AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)) {
2807                attr.hash(&mut hasher);
2808            }
2809        }
2810
2811        // Hash callback events (not the actual callback function pointers)
2812        for callback in self.callbacks.as_ref().iter() {
2813            callback.event.hash(&mut hasher);
2814        }
2815
2816        let h = hasher.finish();
2817        DomNodeHash { inner: h }
2818    }
2819
2820    #[inline(always)]
2821    pub fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
2822        self.set_tab_index(tab_index);
2823        self
2824    }
2825    #[inline(always)]
2826    pub fn with_contenteditable(mut self, contenteditable: bool) -> Self {
2827        self.set_contenteditable(contenteditable);
2828        self
2829    }
2830    #[inline(always)]
2831    pub fn with_node_type(mut self, node_type: NodeType) -> Self {
2832        self.set_node_type(node_type);
2833        self
2834    }
2835    #[inline(always)]
2836    pub fn with_callback<C: Into<CoreCallback>>(
2837        mut self,
2838        event: EventFilter,
2839        data: RefAny,
2840        callback: C,
2841    ) -> Self {
2842        self.add_callback(event, data, callback);
2843        self
2844    }
2845    #[inline(always)]
2846    pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
2847        self.set_dataset(data);
2848        self
2849    }
2850    #[inline(always)]
2851    pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
2852        self.set_ids_and_classes(ids_and_classes);
2853        self
2854    }
2855    #[inline(always)]
2856    pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
2857        self.callbacks = callbacks;
2858        self
2859    }
2860    /// Legacy: builder-form of `set_css_props`. Each `CssPropertyWithConditions`
2861    /// becomes a single-declaration rule at `rule_priority::INLINE`.
2862    /// Prefer `with_style(Css)` for new code.
2863    #[inline(always)]
2864    pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
2865        self.style = css_props.into();
2866        self
2867    }
2868    /// Builder-form of `set_style`.
2869    #[inline(always)]
2870    pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
2871        self.style = style;
2872        self
2873    }
2874
2875    /// Assigns a stable key to this node for reconciliation.
2876    ///
2877    /// This is crucial for performance and correct state preservation when
2878    /// lists of items change order or items are inserted/removed. Without keys,
2879    /// the reconciliation algorithm falls back to hash-based matching.
2880    ///
2881    /// # Example
2882    /// ```rust
2883    /// # use azul_core::dom::NodeData;
2884    /// NodeData::create_div()
2885    ///     .with_key("user-avatar-123");
2886    /// ```
2887    #[inline]
2888    pub fn with_key<K: core::hash::Hash>(mut self, key: K) -> Self {
2889        self.set_key(key);
2890        self
2891    }
2892
2893    /// Registers a callback to merge dataset state from the previous frame.
2894    ///
2895    /// This is used for components that maintain heavy internal state (video players,
2896    /// WebGL contexts, network connections) that should not be destroyed and recreated
2897    /// on every render frame.
2898    ///
2899    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
2900    /// returns the `RefAny` that should be used for the new node.
2901    ///
2902    /// # Example
2903    /// ```rust,ignore
2904    /// struct VideoPlayer {
2905    ///     url: String,
2906    ///     decoder_handle: Option<DecoderHandle>,
2907    /// }
2908    ///
2909    /// extern "C" fn merge_video(new_data: RefAny, old_data: RefAny) -> RefAny {
2910    ///     if let (Some(mut new), Some(old)) = (
2911    ///         new_data.downcast_mut::<VideoPlayer>(),
2912    ///         old_data.downcast_ref::<VideoPlayer>()
2913    ///     ) {
2914    ///         new.decoder_handle = old.decoder_handle.take();
2915    ///     }
2916    ///     new_data
2917    /// }
2918    ///
2919    /// NodeData::create_div()
2920    ///     .with_dataset(RefAny::new(VideoPlayer::new("movie.mp4")).into())
2921    ///     .with_merge_callback(merge_video)
2922    /// ```
2923    #[inline]
2924    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
2925        self.set_merge_callback(callback);
2926        self
2927    }
2928
2929    /// Parse and set CSS styles with full selector support.
2930    ///
2931    /// This is the unified API for setting inline CSS on a node. It supports:
2932    /// - Simple properties: `color: red; font-size: 14px;`
2933    /// - Pseudo-selectors: `:hover { background: blue; }`
2934    /// - @-rules: `@os linux { font-size: 14px; }`
2935    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
2936    ///
2937    /// # Examples
2938    /// ```rust
2939    /// # use azul_core::dom::NodeData;
2940    /// NodeData::create_div().with_css("
2941    ///     color: blue;
2942    ///     :hover { color: red; }
2943    ///     @os linux { font-size: 14px; }
2944    /// ");
2945    /// ```
2946    pub fn set_css(&mut self, style: &str) {
2947        // Parse via Css::parse_inline so the inline path goes through the same
2948        // selector + nesting machinery as author CSS. Rules are tagged
2949        // `rule_priority::INLINE` and appended to whatever this node already has.
2950        let parsed = azul_css::css::Css::parse_inline(style);
2951        let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
2952        mem::swap(&mut current, &mut self.style.rules);
2953        let mut v = current.into_library_owned_vec();
2954        v.extend(parsed.rules.into_library_owned_vec());
2955        self.style.rules = v.into();
2956    }
2957
2958    /// Builder method for `set_css`
2959    pub fn with_css(mut self, style: &str) -> Self {
2960        self.set_css(style);
2961        self
2962    }
2963
2964    #[inline(always)]
2965    pub fn swap_with_default(&mut self) -> Self {
2966        let mut s = NodeData::create_div();
2967        mem::swap(&mut s, self);
2968        s
2969    }
2970
2971    #[inline]
2972    pub fn copy_special(&self) -> Self {
2973        Self {
2974            node_type: self.node_type.into_library_owned_nodetype(),
2975            style: self.style.clone(),
2976            callbacks: self.callbacks.clone(),
2977            flags: self.flags,
2978            accessibility: self.accessibility.clone(),
2979            extra: self.extra.clone(),
2980        }
2981    }
2982
2983    pub fn is_focusable(&self) -> bool {
2984        // Inherently focusable elements per HTML spec
2985        if matches!(self.node_type,
2986            NodeType::A | NodeType::Button | NodeType::Input
2987            | NodeType::Select | NodeType::TextArea
2988        ) {
2989            return true;
2990        }
2991        // Contenteditable elements are implicitly focusable (W3C spec)
2992        if self.is_contenteditable() {
2993            return true;
2994        }
2995        // Element is focusable if it has a tab index or any focus-related callback
2996        self.get_tab_index().is_some()
2997            || self
2998                .get_callbacks()
2999                .iter()
3000                .any(|cb| cb.event.is_focus_callback())
3001    }
3002
3003    /// Returns true if this element has "activation behavior" per HTML5 spec.
3004    ///
3005    /// Elements with activation behavior can be activated via Enter or Space key
3006    /// when focused, which generates a synthetic click event.
3007    ///
3008    /// Per HTML5 spec, elements with activation behavior include:
3009    /// - Button elements
3010    /// - Input elements (submit, button, reset, checkbox, radio)
3011    /// - Anchor elements with href
3012    /// - Any element with a click callback (implicit activation)
3013    ///
3014    /// See: https://html.spec.whatwg.org/multipage/interaction.html#activation-behavior
3015    pub fn has_activation_behavior(&self) -> bool {
3016        // Inherently activatable elements per HTML spec
3017        if matches!(self.node_type, NodeType::A | NodeType::Button) {
3018            return true;
3019        }
3020
3021        use crate::events::{EventFilter, HoverEventFilter};
3022
3023        // Check for click callback (most common case for Azul)
3024        // In Azul, "click" is typically LeftMouseUp
3025        let has_click_callback = self
3026            .get_callbacks()
3027            .iter()
3028            .any(|cb| matches!(
3029                cb.event,
3030                EventFilter::Hover(HoverEventFilter::MouseUp)
3031                | EventFilter::Hover(HoverEventFilter::LeftMouseUp)
3032            ));
3033
3034        if has_click_callback {
3035            return true;
3036        }
3037
3038        // Check accessibility role for button-like elements
3039        if let Some(ref accessibility) = self.accessibility {
3040            use crate::a11y::AccessibilityRole;
3041            match accessibility.role {
3042                AccessibilityRole::PushButton  // Button
3043                | AccessibilityRole::Link
3044                | AccessibilityRole::CheckButton  // Checkbox
3045                | AccessibilityRole::RadioButton  // Radio
3046                | AccessibilityRole::MenuItem
3047                | AccessibilityRole::PageTab  // Tab
3048                => return true,
3049                _ => {}
3050            }
3051        }
3052
3053        false
3054    }
3055
3056    /// Returns true if this element is currently activatable.
3057    ///
3058    /// An element is activatable if it has activation behavior AND is not disabled.
3059    /// This checks for common disability patterns (aria-disabled, disabled attribute).
3060    pub fn is_activatable(&self) -> bool {
3061        if !self.has_activation_behavior() {
3062            return false;
3063        }
3064
3065        // Check for disabled state in accessibility info
3066        if let Some(ref accessibility) = self.accessibility {
3067            // Check if explicitly marked as unavailable
3068            if accessibility
3069                .states
3070                .as_ref()
3071                .iter()
3072                .any(|s| matches!(s, AccessibilityState::Unavailable))
3073            {
3074                return false;
3075            }
3076        }
3077
3078        // Not disabled, so activatable
3079        true
3080    }
3081
3082    /// Returns the tab index for this element.
3083    ///
3084    /// Tab index determines keyboard navigation order:
3085    /// - `None`: Not in tab order (unless naturally focusable)
3086    /// - `Some(-1)`: Focusable programmatically but not via Tab
3087    /// - `Some(0)`: In natural tab order
3088    /// - `Some(n > 0)`: In tab order with priority n (higher = later)
3089    pub fn get_effective_tabindex(&self) -> Option<i32> {
3090        match self.flags.get_tab_index() {
3091            None => {
3092                // Check if naturally focusable (has focus callback)
3093                if self.get_callbacks().iter().any(|cb| cb.event.is_focus_callback()) {
3094                    Some(0)
3095                } else {
3096                    None
3097                }
3098            }
3099            Some(tab_idx) => {
3100                match tab_idx {
3101                    TabIndex::Auto => Some(0),
3102                    TabIndex::OverrideInParent(n) => Some(n as i32),
3103                    TabIndex::NoKeyboardFocus => Some(-1),
3104                }
3105            }
3106        }
3107    }
3108
3109    /// Returns the accessible label for this node.
3110    ///
3111    /// Priority: `aria-label` attribute > `alt` attribute > `title` attribute > None.
3112    /// Does NOT include child text — the caller should collect that separately
3113    /// using the DOM hierarchy.
3114    pub fn get_accessible_label(&self) -> Option<&str> {
3115        for attr in self.attributes().as_ref() {
3116            match attr {
3117                AttributeType::AriaLabel(s) => return Some(s.as_str()),
3118                _ => {}
3119            }
3120        }
3121        for attr in self.attributes().as_ref() {
3122            match attr {
3123                AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
3124                _ => {}
3125            }
3126        }
3127        None
3128    }
3129
3130    /// Returns the accessible value for this node.
3131    ///
3132    /// Priority: `value` attribute > None.
3133    /// For text inputs, this is the input's current value.
3134    pub fn get_accessible_value(&self) -> Option<&str> {
3135        for attr in self.attributes().as_ref() {
3136            if let AttributeType::Value(s) = attr {
3137                return Some(s.as_str());
3138            }
3139        }
3140        None
3141    }
3142
3143    /// Returns the placeholder text for this node.
3144    pub fn get_placeholder(&self) -> Option<&str> {
3145        for attr in self.attributes().as_ref() {
3146            if let AttributeType::Placeholder(s) = attr {
3147                return Some(s.as_str());
3148            }
3149        }
3150        None
3151    }
3152
3153    pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
3154        self.extra.as_mut()?.virtual_view.as_mut()
3155    }
3156
3157    pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
3158        self.extra.as_ref()?.virtual_view.as_ref()
3159    }
3160
3161    pub fn get_render_image_callback_node<'a>(
3162        &'a mut self,
3163    ) -> Option<(&'a mut CoreImageCallback, ImageRefHash)> {
3164        match &mut self.node_type {
3165            NodeType::Image(ref mut img) => {
3166                let hash = image_ref_get_hash(img.as_ref());
3167                img.as_mut().get_image_callback_mut().map(|r| (r, hash))
3168            }
3169            _ => None,
3170        }
3171    }
3172
3173    pub fn debug_print_start(
3174        &self,
3175        css_cache: &CssPropertyCache,
3176        node_id: &NodeId,
3177        node_state: &StyledNodeState,
3178    ) -> String {
3179        let html_type = self.node_type.get_path();
3180        let attributes_string = node_data_to_string(&self);
3181        let style = css_cache.get_computed_css_style_string(&self, node_id, node_state);
3182        format!(
3183            "<{} data-az-node-id=\"{}\" {} {style}>",
3184            html_type,
3185            node_id.index(),
3186            attributes_string,
3187            style = if style.trim().is_empty() {
3188                String::new()
3189            } else {
3190                format!("style=\"{style}\"")
3191            }
3192        )
3193    }
3194
3195    pub fn debug_print_end(&self) -> String {
3196        let html_type = self.node_type.get_path();
3197        format!("</{}>", html_type)
3198    }
3199}
3200
3201impl crate::events::ActivationBehavior for NodeData {
3202    fn has_activation_behavior(&self) -> bool {
3203        NodeData::has_activation_behavior(self)
3204    }
3205
3206    fn is_activatable(&self) -> bool {
3207        NodeData::is_activatable(self)
3208    }
3209}
3210
3211impl crate::events::Focusable for NodeData {
3212    fn get_tabindex(&self) -> Option<i32> {
3213        self.get_effective_tabindex()
3214    }
3215
3216    fn is_focusable(&self) -> bool {
3217        NodeData::is_focusable(self)
3218    }
3219
3220    fn is_naturally_focusable(&self) -> bool {
3221        matches!(
3222            self.node_type,
3223            NodeType::A
3224                | NodeType::Button
3225                | NodeType::Input
3226                | NodeType::Select
3227                | NodeType::TextArea
3228        )
3229    }
3230}
3231
3232/// A unique, runtime-generated identifier for a single `Dom` instance.
3233#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3234#[repr(C)]
3235pub struct DomId {
3236    pub inner: usize,
3237}
3238
3239impl fmt::Display for DomId {
3240    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3241        write!(f, "{}", self.inner)
3242    }
3243}
3244
3245impl DomId {
3246    pub const ROOT_ID: DomId = DomId { inner: 0 };
3247}
3248
3249impl Default for DomId {
3250    fn default() -> DomId {
3251        DomId::ROOT_ID
3252    }
3253}
3254
3255impl_option!(
3256    DomId,
3257    OptionDomId,
3258    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3259);
3260
3261impl_vec!(DomId, DomIdVec, DomIdVecDestructor, DomIdVecDestructorType, DomIdVecSlice, OptionDomId);
3262impl_vec_debug!(DomId, DomIdVec);
3263impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
3264impl_vec_partialeq!(DomId, DomIdVec);
3265impl_vec_partialord!(DomId, DomIdVec);
3266
3267/// A UUID for a DOM node within a `LayoutWindow`.
3268#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3269#[repr(C)]
3270pub struct DomNodeId {
3271    /// The ID of the `Dom` this node belongs to.
3272    pub dom: DomId,
3273    /// The hierarchical ID of the node within its `Dom`.
3274    pub node: NodeHierarchyItemId,
3275}
3276
3277impl_option!(
3278    DomNodeId,
3279    OptionDomNodeId,
3280    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3281);
3282
3283impl DomNodeId {
3284    pub const ROOT: DomNodeId = DomNodeId {
3285        dom: DomId::ROOT_ID,
3286        node: NodeHierarchyItemId::NONE,
3287    };
3288}
3289
3290/// The document model, similar to HTML. This is a create-only structure, you don't actually read
3291/// anything back from it. It's designed for ease of construction.
3292///
3293/// This is the "slow" tree-based DOM. For bulk construction (XML parsing),
3294/// use `FastDom` which builds flat arenas directly and skips the tree→arena conversion.
3295#[repr(C)]
3296#[derive(PartialEq, Clone, PartialOrd)]
3297pub struct Dom {
3298    /// The data for the root node of this DOM (or sub-DOM).
3299    pub root: NodeData,
3300    /// The children of this DOM node.
3301    pub children: DomVec,
3302    /// Ordered list of CSS stylesheets to apply to this DOM subtree.
3303    /// Stylesheets are applied in push order during the single deferred cascade pass.
3304    /// Later entries override earlier ones (higher cascade priority).
3305    pub css: azul_css::css::CssVec,
3306    // Tracks the number of sub-children of the current children, so that
3307    // the `Dom` can be converted into a `CompactDom`.
3308    pub estimated_total_children: usize,
3309}
3310
3311/// CSS stylesheet associated with a specific node ID in the flat arena.
3312/// In the tree DOM, each node carries its own `css` field. In the flat arena,
3313/// we record which node a stylesheet scopes to (e.g. for `<style>` tags
3314/// in different parts of the document).
3315#[repr(C)]
3316#[derive(Debug, Clone, PartialEq, PartialOrd)]
3317pub struct CssWithNodeId {
3318    /// 1-based encoded NodeId (0 = root / global scope).
3319    pub node_id: usize,
3320    /// The CSS stylesheet.
3321    pub css: azul_css::css::Css,
3322}
3323
3324impl_vec!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor, CssWithNodeIdVecDestructorType, CssWithNodeIdVecSlice, OptionCssWithNodeId);
3325impl_option!(CssWithNodeId, OptionCssWithNodeId, copy = false, [Debug, Clone, PartialEq, PartialOrd]);
3326impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
3327impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
3328impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
3329impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
3330impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
3331
3332/// Arena-based DOM for bulk construction (e.g. XML/XHTML parsing).
3333/// The hierarchy and node data are stored in two parallel flat vectors,
3334/// skipping the tree→arena conversion step entirely.
3335///
3336/// Use `FastDom::into_dom()` to convert to a tree-based `Dom` if needed.
3337/// `StyledDom::create_from_fast_dom()` consumes this directly without conversion.
3338#[repr(C)]
3339#[derive(Debug, Clone, PartialEq, PartialOrd)]
3340pub struct FastDom {
3341    /// Flat arena of parent/child/sibling relationships.
3342    pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
3343    /// Flat arena of node data, parallel to `node_hierarchy`.
3344    pub node_data: NodeDataVec,
3345    /// CSS stylesheets with the node ID they scope to.
3346    pub css: CssWithNodeIdVec,
3347}
3348
3349// Manual Eq/Hash/Ord impls that skip the transient `css` field,
3350// since CssVec does not implement Eq/Hash/Ord.
3351impl Eq for Dom {}
3352
3353impl core::hash::Hash for Dom {
3354    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
3355        self.root.hash(state);
3356        self.children.hash(state);
3357        self.estimated_total_children.hash(state);
3358    }
3359}
3360
3361impl Ord for Dom {
3362    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3363        self.root.cmp(&other.root)
3364            .then_with(|| self.children.cmp(&other.children))
3365            .then_with(|| self.estimated_total_children.cmp(&other.estimated_total_children))
3366    }
3367}
3368
3369impl_option!(
3370    Dom,
3371    OptionDom,
3372    copy = false,
3373    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3374);
3375
3376impl_vec!(Dom, DomVec, DomVecDestructor, DomVecDestructorType, DomVecSlice, OptionDom);
3377impl_vec_clone!(Dom, DomVec, DomVecDestructor);
3378impl_vec_mut!(Dom, DomVec);
3379impl_vec_debug!(Dom, DomVec);
3380impl_vec_partialord!(Dom, DomVec);
3381impl_vec_ord!(Dom, DomVec);
3382impl_vec_partialeq!(Dom, DomVec);
3383impl_vec_eq!(Dom, DomVec);
3384impl_vec_hash!(Dom, DomVec);
3385
3386impl Dom {
3387    // ----- DOM CONSTRUCTORS
3388
3389    /// Creates an empty DOM with a give `NodeType`. Note: This is a `const fn` and
3390    /// doesn't allocate, it only allocates once you add at least one child node.
3391    #[inline(always)]
3392    pub fn create_node(node_type: NodeType) -> Self {
3393        Self {
3394            root: NodeData::create_node(node_type),
3395            children: Vec::new().into(),
3396            css: Vec::new().into(),
3397            estimated_total_children: 0,
3398        }
3399    }
3400    #[inline(always)]
3401    pub fn create_from_data(node_data: NodeData) -> Self {
3402        Self {
3403            root: node_data,
3404            children: Vec::new().into(),
3405            css: Vec::new().into(),
3406            estimated_total_children: 0,
3407        }
3408    }
3409
3410    // Document Structure Elements
3411
3412    /// Creates the root HTML element.
3413    ///
3414    /// **Accessibility**: The `<html>` element is the root of an HTML document and should have a
3415    /// `lang` attribute.
3416    #[inline(always)]
3417    pub const fn create_html() -> Self {
3418        Self {
3419            root: NodeData::create_node(NodeType::Html),
3420            children: DomVec::from_const_slice(&[]),
3421            css: azul_css::css::CssVec::from_const_slice(&[]),
3422            estimated_total_children: 0,
3423        }
3424    }
3425
3426    /// Creates the document head element.
3427    ///
3428    /// **Accessibility**: The `<head>` contains metadata. Use `<title>` for page titles.
3429    #[inline(always)]
3430    pub const fn create_head() -> Self {
3431        Self {
3432            root: NodeData::create_node(NodeType::Head),
3433            children: DomVec::from_const_slice(&[]),
3434            css: azul_css::css::CssVec::from_const_slice(&[]),
3435            estimated_total_children: 0,
3436        }
3437    }
3438
3439    #[inline(always)]
3440    pub const fn create_body() -> Self {
3441        Self {
3442            root: NodeData::create_node(NodeType::Body),
3443            children: DomVec::from_const_slice(&[]),
3444            css: azul_css::css::CssVec::from_const_slice(&[]),
3445            estimated_total_children: 0,
3446        }
3447    }
3448
3449    /// Creates a generic block-level container.
3450    ///
3451    /// **Accessibility**: Prefer semantic elements like `<article>`, `<section>`, `<nav>` when
3452    /// applicable.
3453    #[inline(always)]
3454    pub const fn create_div() -> Self {
3455        Self {
3456            root: NodeData::create_node(NodeType::Div),
3457            children: DomVec::from_const_slice(&[]),
3458            css: azul_css::css::CssVec::from_const_slice(&[]),
3459            estimated_total_children: 0,
3460        }
3461    }
3462
3463    // Semantic Structure Elements
3464
3465    /// Creates an article element.
3466    ///
3467    /// **Accessibility**: Represents self-contained content that could be distributed
3468    /// independently. Screen readers can navigate by articles. Consider adding aria-label for
3469    /// multiple articles.
3470    #[inline(always)]
3471    pub const fn create_article() -> Self {
3472        Self {
3473            root: NodeData::create_node(NodeType::Article),
3474            children: DomVec::from_const_slice(&[]),
3475            css: azul_css::css::CssVec::from_const_slice(&[]),
3476            estimated_total_children: 0,
3477        }
3478    }
3479
3480    /// Creates a section element.
3481    ///
3482    /// **Accessibility**: Represents a thematic grouping of content with a heading.
3483    /// Should typically have a heading (h1-h6) as a child. Consider aria-labelledby.
3484    #[inline(always)]
3485    pub const fn create_section() -> Self {
3486        Self {
3487            root: NodeData::create_node(NodeType::Section),
3488            children: DomVec::from_const_slice(&[]),
3489            css: azul_css::css::CssVec::from_const_slice(&[]),
3490            estimated_total_children: 0,
3491        }
3492    }
3493
3494    /// Creates a navigation element.
3495    ///
3496    /// **Accessibility**: Represents navigation links. Screen readers can jump to navigation.
3497    /// Use aria-label to distinguish multiple nav elements (e.g., "Main navigation", "Footer
3498    /// links").
3499    #[inline(always)]
3500    pub const fn create_nav() -> Self {
3501        Self {
3502            root: NodeData::create_node(NodeType::Nav),
3503            children: DomVec::from_const_slice(&[]),
3504            css: azul_css::css::CssVec::from_const_slice(&[]),
3505            estimated_total_children: 0,
3506        }
3507    }
3508
3509    /// Creates an aside element.
3510    ///
3511    /// **Accessibility**: Represents content tangentially related to main content (sidebars,
3512    /// callouts). Screen readers announce this as complementary content.
3513    #[inline(always)]
3514    pub const fn create_aside() -> Self {
3515        Self {
3516            root: NodeData::create_node(NodeType::Aside),
3517            children: DomVec::from_const_slice(&[]),
3518            css: azul_css::css::CssVec::from_const_slice(&[]),
3519            estimated_total_children: 0,
3520        }
3521    }
3522
3523    /// Creates a header element.
3524    ///
3525    /// **Accessibility**: Represents introductory content or navigational aids.
3526    /// Can be used for page headers or section headers.
3527    #[inline(always)]
3528    pub const fn create_header() -> Self {
3529        Self {
3530            root: NodeData::create_node(NodeType::Header),
3531            children: DomVec::from_const_slice(&[]),
3532            css: azul_css::css::CssVec::from_const_slice(&[]),
3533            estimated_total_children: 0,
3534        }
3535    }
3536
3537    /// Creates a footer element.
3538    ///
3539    /// **Accessibility**: Represents footer for nearest section or page.
3540    /// Typically contains copyright, author info, or related links.
3541    #[inline(always)]
3542    pub const fn create_footer() -> Self {
3543        Self {
3544            root: NodeData::create_node(NodeType::Footer),
3545            children: DomVec::from_const_slice(&[]),
3546            css: azul_css::css::CssVec::from_const_slice(&[]),
3547            estimated_total_children: 0,
3548        }
3549    }
3550
3551    /// Creates a main content element.
3552    ///
3553    /// **Accessibility**: Represents the dominant content. There should be only ONE main per page.
3554    /// Screen readers can jump directly to main content. Do not nest inside
3555    /// article/aside/footer/header/nav.
3556    #[inline(always)]
3557    pub const fn create_main() -> Self {
3558        Self {
3559            root: NodeData::create_node(NodeType::Main),
3560            children: DomVec::from_const_slice(&[]),
3561            css: azul_css::css::CssVec::from_const_slice(&[]),
3562            estimated_total_children: 0,
3563        }
3564    }
3565
3566    /// Creates a figure element.
3567    ///
3568    /// **Accessibility**: Represents self-contained content like diagrams, photos, code listings.
3569    /// Use with `<figcaption>` to provide a caption. Screen readers associate caption with figure.
3570    #[inline(always)]
3571    pub const fn create_figure() -> Self {
3572        Self {
3573            root: NodeData::create_node(NodeType::Figure),
3574            children: DomVec::from_const_slice(&[]),
3575            css: azul_css::css::CssVec::from_const_slice(&[]),
3576            estimated_total_children: 0,
3577        }
3578    }
3579
3580    /// Creates a figure caption element.
3581    ///
3582    /// **Accessibility**: Provides a caption for `<figure>`. Screen readers announce this as the
3583    /// figure description.
3584    #[inline(always)]
3585    pub const fn create_figcaption() -> Self {
3586        Self {
3587            root: NodeData::create_node(NodeType::FigCaption),
3588            children: DomVec::from_const_slice(&[]),
3589            css: azul_css::css::CssVec::from_const_slice(&[]),
3590            estimated_total_children: 0,
3591        }
3592    }
3593
3594    // Interactive Elements
3595
3596    /// Creates a details disclosure element without accessibility information.
3597    ///
3598    /// Prefer [`Dom::create_details`] so that screen readers announce the
3599    /// disclosure widget's purpose.
3600    #[inline(always)]
3601    pub const fn create_details_no_a11y() -> Self {
3602        Self {
3603            root: NodeData::create_node(NodeType::Details),
3604            children: DomVec::from_const_slice(&[]),
3605            css: azul_css::css::CssVec::from_const_slice(&[]),
3606            estimated_total_children: 0,
3607        }
3608    }
3609
3610    /// Creates a details disclosure element with accessibility information.
3611    ///
3612    /// **Accessibility**: Creates a disclosure widget. Screen readers announce expanded/collapsed
3613    /// state. Must contain a `<summary>` element. Keyboard accessible by default.
3614    ///
3615    /// Use [`Dom::create_details_no_a11y`] only as a deliberate escape hatch.
3616    #[inline]
3617    pub fn create_details(aria: SmallAriaInfo) -> Self {
3618        Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
3619    }
3620
3621    /// Creates an empty summary element for details without accessibility information.
3622    ///
3623    /// Prefer [`Dom::create_summary`] so that screen readers can announce the
3624    /// disclosure heading.
3625    #[inline(always)]
3626    pub const fn create_summary_no_a11y() -> Self {
3627        Self {
3628            root: NodeData::create_node(NodeType::Summary),
3629            children: DomVec::from_const_slice(&[]),
3630            css: azul_css::css::CssVec::from_const_slice(&[]),
3631            estimated_total_children: 0,
3632        }
3633    }
3634
3635    /// Creates an empty summary element for details with accessibility information.
3636    ///
3637    /// **Accessibility**: The visible heading/label for `<details>`.
3638    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
3639    ///
3640    /// Use [`Dom::create_summary_no_a11y`] only as a deliberate escape hatch.
3641    #[inline]
3642    pub fn create_summary(aria: SmallAriaInfo) -> Self {
3643        Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
3644    }
3645
3646    /// Creates a summary element with text without accessibility information.
3647    ///
3648    /// Prefer [`Dom::create_summary_with_text`] so that screen readers
3649    /// announce the disclosure heading.
3650    #[inline]
3651    pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
3652        Self::create_summary_no_a11y().with_child(Self::create_text(text))
3653    }
3654
3655    /// Creates a summary element with text and accessibility information for details.
3656    ///
3657    /// **Accessibility**: The visible heading/label for `<details>`.
3658    /// Must be the first child of details. Keyboard accessible (Enter/Space to toggle).
3659    ///
3660    /// Use [`Dom::create_summary_with_text_no_a11y`] only as a deliberate escape hatch.
3661    #[inline]
3662    pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
3663        Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
3664    }
3665
3666    /// Creates a dialog element without accessibility information.
3667    ///
3668    /// Prefer [`Dom::create_dialog`] so that the dialog's purpose, modality,
3669    /// and described-by relationship are surfaced to assistive technologies.
3670    #[inline(always)]
3671    pub const fn create_dialog_no_a11y() -> Self {
3672        Self {
3673            root: NodeData::create_node(NodeType::Dialog),
3674            children: DomVec::from_const_slice(&[]),
3675            css: azul_css::css::CssVec::from_const_slice(&[]),
3676            estimated_total_children: 0,
3677        }
3678    }
3679
3680    /// Creates a dialog element with accessibility information.
3681    ///
3682    /// **Accessibility**: Represents a modal or non-modal dialog.
3683    /// When opened as modal, focus is trapped. Use aria-label or aria-labelledby.
3684    /// Escape key should close modal dialogs.
3685    ///
3686    /// Use [`Dom::create_dialog_no_a11y`] only as a deliberate escape hatch.
3687    #[inline]
3688    pub fn create_dialog(aria: DialogAriaInfo) -> Self {
3689        Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
3690    }
3691
3692    // Basic Structural Elements
3693
3694    #[inline(always)]
3695    pub const fn create_br() -> Self {
3696        Self {
3697            root: NodeData::create_node(NodeType::Br),
3698            children: DomVec::from_const_slice(&[]),
3699            css: azul_css::css::CssVec::from_const_slice(&[]),
3700            estimated_total_children: 0,
3701        }
3702    }
3703    #[inline(always)]
3704    pub fn create_text<S: Into<AzString>>(value: S) -> Self {
3705        Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
3706    }
3707    #[inline(always)]
3708    pub fn create_image(image: ImageRef) -> Self {
3709        Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
3710    }
3711    /// Creates an icon node with the given icon name.
3712    ///
3713    /// The icon name should match names from the icon provider (e.g., "home", "settings", "search").
3714    /// Icons are resolved to actual content (font glyph, image, etc.) during StyledDom creation
3715    /// based on the configured IconProvider.
3716    ///
3717    /// # Example
3718    /// ```rust,ignore
3719    /// Dom::create_icon("home")
3720    ///     .with_class("nav-icon")
3721    /// ```
3722    #[inline(always)]
3723    pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
3724        Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
3725    }
3726
3727    #[inline(always)]
3728    pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
3729        Self::create_from_data(NodeData::create_virtual_view(data, callback))
3730    }
3731
3732    /// Creates an invisible `NodeType::GeolocationProbe` node that
3733    /// signals "this subtree needs the user's location". Lays out as
3734    /// zero-size and is skipped in the display list - the framework
3735    /// scans for it at end-of-layout and starts / stops the native
3736    /// `CLLocationManager` / `LocationManager` / `geoclue`
3737    /// subscription. See `SUPER_PLAN_2.md` section 1.5.
3738    #[inline(always)]
3739    pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
3740        Self::create_node(NodeType::GeolocationProbe(config))
3741    }
3742
3743    // Semantic HTML Elements with Accessibility Guidance
3744
3745    /// Creates a paragraph element.
3746    ///
3747    /// **Accessibility**: Paragraphs provide semantic structure for screen readers.
3748    #[inline(always)]
3749    pub const fn create_p() -> Self {
3750        Self {
3751            root: NodeData::create_node(NodeType::P),
3752            children: DomVec::from_const_slice(&[]),
3753            css: azul_css::css::CssVec::from_const_slice(&[]),
3754            estimated_total_children: 0,
3755        }
3756    }
3757
3758    /// Creates an empty heading level 1 element.
3759    ///
3760    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
3761    /// per page.
3762    #[inline(always)]
3763    pub const fn create_h1() -> Self {
3764        Self {
3765            root: NodeData::create_node(NodeType::H1),
3766            children: DomVec::from_const_slice(&[]),
3767            css: azul_css::css::CssVec::from_const_slice(&[]),
3768            estimated_total_children: 0,
3769        }
3770    }
3771
3772    /// Creates a heading level 1 element with text.
3773    ///
3774    /// **Accessibility**: Use `h1` for the main page title. There should typically be only one `h1`
3775    /// per page.
3776    ///
3777    /// **Parameters:**
3778    /// - `text`: Heading text
3779    #[inline]
3780    pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
3781        Self::create_h1().with_child(Self::create_text(text))
3782    }
3783
3784    /// Creates an empty heading level 2 element.
3785    ///
3786    /// **Accessibility**: Use `h2` for major section headings under `h1`.
3787    #[inline(always)]
3788    pub const fn create_h2() -> Self {
3789        Self {
3790            root: NodeData::create_node(NodeType::H2),
3791            children: DomVec::from_const_slice(&[]),
3792            css: azul_css::css::CssVec::from_const_slice(&[]),
3793            estimated_total_children: 0,
3794        }
3795    }
3796
3797    /// Creates a heading level 2 element with text.
3798    ///
3799    /// **Accessibility**: Use `h2` for major section headings under `h1`.
3800    ///
3801    /// **Parameters:**
3802    /// - `text`: Heading text
3803    #[inline]
3804    pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
3805        Self::create_h2().with_child(Self::create_text(text))
3806    }
3807
3808    /// Creates an empty heading level 3 element.
3809    ///
3810    /// **Accessibility**: Use `h3` for subsections under `h2`.
3811    #[inline(always)]
3812    pub const fn create_h3() -> Self {
3813        Self {
3814            root: NodeData::create_node(NodeType::H3),
3815            children: DomVec::from_const_slice(&[]),
3816            css: azul_css::css::CssVec::from_const_slice(&[]),
3817            estimated_total_children: 0,
3818        }
3819    }
3820
3821    /// Creates a heading level 3 element with text.
3822    ///
3823    /// **Accessibility**: Use `h3` for subsections under `h2`.
3824    ///
3825    /// **Parameters:**
3826    /// - `text`: Heading text
3827    #[inline]
3828    pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
3829        Self::create_h3().with_child(Self::create_text(text))
3830    }
3831
3832    /// Creates an empty heading level 4 element.
3833    #[inline(always)]
3834    pub const fn create_h4() -> Self {
3835        Self {
3836            root: NodeData::create_node(NodeType::H4),
3837            children: DomVec::from_const_slice(&[]),
3838            css: azul_css::css::CssVec::from_const_slice(&[]),
3839            estimated_total_children: 0,
3840        }
3841    }
3842
3843    /// Creates a heading level 4 element with text.
3844    ///
3845    /// **Parameters:**
3846    /// - `text`: Heading text
3847    #[inline]
3848    pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
3849        Self::create_h4().with_child(Self::create_text(text))
3850    }
3851
3852    /// Creates an empty heading level 5 element.
3853    #[inline(always)]
3854    pub const fn create_h5() -> Self {
3855        Self {
3856            root: NodeData::create_node(NodeType::H5),
3857            children: DomVec::from_const_slice(&[]),
3858            css: azul_css::css::CssVec::from_const_slice(&[]),
3859            estimated_total_children: 0,
3860        }
3861    }
3862
3863    /// Creates a heading level 5 element with text.
3864    ///
3865    /// **Parameters:**
3866    /// - `text`: Heading text
3867    #[inline]
3868    pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
3869        Self::create_h5().with_child(Self::create_text(text))
3870    }
3871
3872    /// Creates an empty heading level 6 element.
3873    #[inline(always)]
3874    pub const fn create_h6() -> Self {
3875        Self {
3876            root: NodeData::create_node(NodeType::H6),
3877            children: DomVec::from_const_slice(&[]),
3878            css: azul_css::css::CssVec::from_const_slice(&[]),
3879            estimated_total_children: 0,
3880        }
3881    }
3882
3883    /// Creates a heading level 6 element with text.
3884    ///
3885    /// **Parameters:**
3886    /// - `text`: Heading text
3887    #[inline]
3888    pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
3889        Self::create_h6().with_child(Self::create_text(text))
3890    }
3891
3892    /// Creates an empty generic inline container (span).
3893    ///
3894    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
3895    /// applicable.
3896    #[inline(always)]
3897    pub const fn create_span() -> Self {
3898        Self {
3899            root: NodeData::create_node(NodeType::Span),
3900            children: DomVec::from_const_slice(&[]),
3901            css: azul_css::css::CssVec::from_const_slice(&[]),
3902            estimated_total_children: 0,
3903        }
3904    }
3905
3906    /// Creates a generic inline container (span) with text.
3907    ///
3908    /// **Accessibility**: Prefer semantic elements like `strong`, `em`, `code`, etc. when
3909    /// applicable.
3910    ///
3911    /// **Parameters:**
3912    /// - `text`: Span content
3913    #[inline]
3914    pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
3915        Self::create_span().with_child(Self::create_text(text))
3916    }
3917
3918    /// Creates an empty strong importance element.
3919    ///
3920    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning.
3921    #[inline(always)]
3922    pub const fn create_strong() -> Self {
3923        Self {
3924            root: NodeData::create_node(NodeType::Strong),
3925            children: DomVec::from_const_slice(&[]),
3926            css: azul_css::css::CssVec::from_const_slice(&[]),
3927            estimated_total_children: 0,
3928        }
3929    }
3930
3931    /// Creates a strongly emphasized text element with text (strong importance).
3932    ///
3933    /// **Accessibility**: Use `strong` instead of `b` for semantic meaning. Screen readers can
3934    /// convey the importance. Use for text that has strong importance, seriousness, or urgency.
3935    ///
3936    /// **Parameters:**
3937    /// - `text`: Text to emphasize
3938    #[inline]
3939    pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
3940        Self::create_strong().with_child(Self::create_text(text))
3941    }
3942
3943    /// Creates an empty emphasis element (stress emphasis).
3944    ///
3945    /// **Accessibility**: Use `em` instead of `i` for semantic meaning.
3946    #[inline(always)]
3947    pub const fn create_em() -> Self {
3948        Self {
3949            root: NodeData::create_node(NodeType::Em),
3950            children: DomVec::from_const_slice(&[]),
3951            css: azul_css::css::CssVec::from_const_slice(&[]),
3952            estimated_total_children: 0,
3953        }
3954    }
3955
3956    /// Creates an emphasized text element with text (stress emphasis).
3957    ///
3958    /// **Accessibility**: Use `em` instead of `i` for semantic meaning. Screen readers can
3959    /// convey the emphasis. Use for text that has stress emphasis.
3960    ///
3961    /// **Parameters:**
3962    /// - `text`: Text to emphasize
3963    #[inline]
3964    pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
3965        Self::create_em().with_child(Self::create_text(text))
3966    }
3967
3968    /// Creates an empty code element.
3969    ///
3970    /// **Accessibility**: Represents a fragment of computer code.
3971    #[inline(always)]
3972    pub fn create_code() -> Self {
3973        Self::create_node(NodeType::Code)
3974    }
3975
3976    /// Creates a code/computer code element with text.
3977    ///
3978    /// **Accessibility**: Represents a fragment of computer code. Screen readers can identify
3979    /// this as code content.
3980    ///
3981    /// **Parameters:**
3982    /// - `code`: Code content
3983    #[inline]
3984    pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
3985        Self::create_code().with_child(Self::create_text(code))
3986    }
3987
3988    /// Creates an empty preformatted text element.
3989    ///
3990    /// **Accessibility**: Preserves whitespace and line breaks.
3991    #[inline(always)]
3992    pub fn create_pre() -> Self {
3993        Self::create_node(NodeType::Pre)
3994    }
3995
3996    /// Creates a preformatted text element with text.
3997    ///
3998    /// **Accessibility**: Preserves whitespace and line breaks. Useful for code blocks or
3999    /// ASCII art. Screen readers will read the content as-is.
4000    ///
4001    /// **Parameters:**
4002    /// - `text`: Preformatted content
4003    #[inline]
4004    pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
4005        Self::create_pre().with_child(Self::create_text(text))
4006    }
4007
4008    /// Creates an empty blockquote element.
4009    ///
4010    /// **Accessibility**: Represents a section quoted from another source.
4011    #[inline(always)]
4012    pub fn create_blockquote() -> Self {
4013        Self::create_node(NodeType::BlockQuote)
4014    }
4015
4016    /// Creates a blockquote element with text.
4017    ///
4018    /// **Accessibility**: Represents a section quoted from another source. Screen readers
4019    /// can identify quoted content. Consider adding a `cite` attribute.
4020    ///
4021    /// **Parameters:**
4022    /// - `text`: Quote content
4023    #[inline]
4024    pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
4025        Self::create_blockquote().with_child(Self::create_text(text))
4026    }
4027
4028    /// Creates an empty citation element.
4029    ///
4030    /// **Accessibility**: Represents a reference to a creative work.
4031    #[inline(always)]
4032    pub fn create_cite() -> Self {
4033        Self::create_node(NodeType::Cite)
4034    }
4035
4036    /// Creates a citation element with text.
4037    ///
4038    /// **Accessibility**: Represents a reference to a creative work. Screen readers can
4039    /// identify citations.
4040    ///
4041    /// **Parameters:**
4042    /// - `text`: Citation text
4043    #[inline]
4044    pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
4045        Self::create_cite().with_child(Self::create_text(text))
4046    }
4047
4048    /// Creates an empty abbreviation element.
4049    ///
4050    /// **Accessibility**: Represents an abbreviation or acronym. Use with a `title` attribute
4051    /// to provide the full expansion for screen readers.
4052    #[inline(always)]
4053    pub fn create_abbr() -> Self {
4054        Self::create_node(NodeType::Abbr)
4055    }
4056
4057    /// Creates an abbreviation element with abbreviated text and a `title` expansion.
4058    ///
4059    /// **Accessibility**: Represents an abbreviation or acronym. The `title` attribute
4060    /// provides the full expansion for screen readers.
4061    ///
4062    /// **Parameters:**
4063    /// - `abbr_text`: Abbreviated text
4064    /// - `title`: Full expansion
4065    #[inline]
4066    pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
4067        Self::create_node(NodeType::Abbr)
4068            .with_attribute(AttributeType::Title(title))
4069            .with_child(Self::create_text(abbr_text))
4070    }
4071
4072    /// Creates an empty keyboard input element.
4073    ///
4074    /// **Accessibility**: Represents keyboard input or key combinations.
4075    #[inline(always)]
4076    pub fn create_kbd() -> Self {
4077        Self::create_node(NodeType::Kbd)
4078    }
4079
4080    /// Creates a keyboard input element with text.
4081    ///
4082    /// **Accessibility**: Represents keyboard input or key combinations. Screen readers can
4083    /// identify keyboard instructions.
4084    ///
4085    /// **Parameters:**
4086    /// - `text`: Keyboard instruction
4087    #[inline]
4088    pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
4089        Self::create_kbd().with_child(Self::create_text(text))
4090    }
4091
4092    /// Creates an empty sample output element.
4093    ///
4094    /// **Accessibility**: Represents sample output from a program or computing system.
4095    #[inline(always)]
4096    pub fn create_samp() -> Self {
4097        Self::create_node(NodeType::Samp)
4098    }
4099
4100    /// Creates a sample output element with text.
4101    ///
4102    /// **Accessibility**: Represents sample output from a program or computing system.
4103    ///
4104    /// **Parameters:**
4105    /// - `text`: Sample text
4106    #[inline]
4107    pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
4108        Self::create_samp().with_child(Self::create_text(text))
4109    }
4110
4111    /// Creates an empty variable element.
4112    ///
4113    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
4114    #[inline(always)]
4115    pub fn create_var() -> Self {
4116        Self::create_node(NodeType::Var)
4117    }
4118
4119    /// Creates a variable element with text.
4120    ///
4121    /// **Accessibility**: Represents a variable in mathematical expressions or programming.
4122    ///
4123    /// **Parameters:**
4124    /// - `text`: Variable name
4125    #[inline]
4126    pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
4127        Self::create_var().with_child(Self::create_text(text))
4128    }
4129
4130    /// Creates an empty subscript element.
4131    #[inline(always)]
4132    pub fn create_sub() -> Self {
4133        Self::create_node(NodeType::Sub)
4134    }
4135
4136    /// Creates a subscript element with text.
4137    ///
4138    /// **Accessibility**: Screen readers may announce subscript formatting.
4139    ///
4140    /// **Parameters:**
4141    /// - `text`: Subscript content
4142    #[inline]
4143    pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
4144        Self::create_sub().with_child(Self::create_text(text))
4145    }
4146
4147    /// Creates an empty superscript element.
4148    #[inline(always)]
4149    pub fn create_sup() -> Self {
4150        Self::create_node(NodeType::Sup)
4151    }
4152
4153    /// Creates a superscript element with text.
4154    ///
4155    /// **Accessibility**: Screen readers may announce superscript formatting.
4156    ///
4157    /// **Parameters:**
4158    /// - `text`: Superscript content
4159    #[inline]
4160    pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
4161        Self::create_sup().with_child(Self::create_text(text))
4162    }
4163
4164    /// Creates an empty underline element.
4165    #[inline(always)]
4166    pub fn create_u() -> Self {
4167        Self::create_node(NodeType::U)
4168    }
4169
4170    /// Creates an underline text element with text.
4171    ///
4172    /// **Accessibility**: Screen readers typically don't announce underline formatting.
4173    /// Use semantic elements when possible (e.g., `<em>` for emphasis).
4174    #[inline]
4175    pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
4176        Self::create_u().with_child(Self::create_text(text))
4177    }
4178
4179    /// Creates an empty strikethrough element.
4180    #[inline(always)]
4181    pub fn create_s() -> Self {
4182        Self::create_node(NodeType::S)
4183    }
4184
4185    /// Creates a strikethrough text element with text.
4186    ///
4187    /// **Accessibility**: Represents text that is no longer accurate or relevant.
4188    /// Consider using `<del>` for deleted content with datetime attribute.
4189    #[inline]
4190    pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
4191        Self::create_s().with_child(Self::create_text(text))
4192    }
4193
4194    /// Creates an empty mark element.
4195    #[inline(always)]
4196    pub fn create_mark() -> Self {
4197        Self::create_node(NodeType::Mark)
4198    }
4199
4200    /// Creates a marked/highlighted text element with text.
4201    ///
4202    /// **Accessibility**: Represents text marked for reference or notation purposes.
4203    /// Screen readers may announce this as "highlighted".
4204    #[inline]
4205    pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
4206        Self::create_mark().with_child(Self::create_text(text))
4207    }
4208
4209    /// Creates an empty deleted text element.
4210    #[inline(always)]
4211    pub fn create_del() -> Self {
4212        Self::create_node(NodeType::Del)
4213    }
4214
4215    /// Creates a deleted text element with text.
4216    ///
4217    /// **Accessibility**: Represents deleted content in document edits.
4218    /// Use with `datetime` and `cite` attributes for edit tracking.
4219    #[inline]
4220    pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
4221        Self::create_del().with_child(Self::create_text(text))
4222    }
4223
4224    /// Creates an empty inserted text element.
4225    #[inline(always)]
4226    pub fn create_ins() -> Self {
4227        Self::create_node(NodeType::Ins)
4228    }
4229
4230    /// Creates an inserted text element with text.
4231    ///
4232    /// **Accessibility**: Represents inserted content in document edits.
4233    /// Use with `datetime` and `cite` attributes for edit tracking.
4234    #[inline]
4235    pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
4236        Self::create_ins().with_child(Self::create_text(text))
4237    }
4238
4239    /// Creates an empty definition element.
4240    #[inline(always)]
4241    pub fn create_dfn() -> Self {
4242        Self::create_node(NodeType::Dfn)
4243    }
4244
4245    /// Creates a definition element with text.
4246    ///
4247    /// **Accessibility**: Represents the defining instance of a term.
4248    /// Often used within a definition list or with `<abbr>`.
4249    #[inline]
4250    pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
4251        Self::create_dfn().with_child(Self::create_text(text))
4252    }
4253
4254    /// Creates a time element.
4255    ///
4256    /// **Accessibility**: Represents a specific time or date.
4257    /// Use `datetime` attribute for machine-readable format.
4258    ///
4259    /// **Parameters:**
4260    /// - `text`: Human-readable time/date
4261    /// - `datetime`: Optional machine-readable datetime
4262    #[inline]
4263    pub fn create_time(text: AzString, datetime: OptionString) -> Self {
4264        let mut element = Self::create_node(NodeType::Time).with_child(Self::create_text(text));
4265        if let OptionString::Some(dt) = datetime {
4266            element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
4267                attr_name: "datetime".into(),
4268                value: dt,
4269            }));
4270        }
4271        element
4272    }
4273
4274    /// Creates an empty bi-directional override element.
4275    ///
4276    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
4277    #[inline(always)]
4278    pub fn create_bdo() -> Self {
4279        Self::create_node(NodeType::Bdo)
4280    }
4281
4282    /// Creates a bi-directional override element with text.
4283    ///
4284    /// **Accessibility**: Overrides text direction. Use `dir` attribute (ltr/rtl).
4285    #[inline]
4286    pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
4287        Self::create_bdo().with_child(Self::create_text(text))
4288    }
4289
4290    // Additional inline / text-level elements
4291
4292    /// Creates an empty bold element.
4293    ///
4294    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
4295    #[inline(always)]
4296    pub fn create_b() -> Self {
4297        Self::create_node(NodeType::B)
4298    }
4299
4300    /// Creates a bold element with text.
4301    ///
4302    /// **Accessibility**: Prefer `<strong>` for semantic emphasis. `<b>` is purely stylistic.
4303    ///
4304    /// **Parameters:**
4305    /// - `text`: Bold text content
4306    #[inline]
4307    pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
4308        Self::create_b().with_child(Self::create_text(text))
4309    }
4310
4311    /// Creates an empty italic element.
4312    ///
4313    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
4314    #[inline(always)]
4315    pub fn create_i() -> Self {
4316        Self::create_node(NodeType::I)
4317    }
4318
4319    /// Creates an italic element with text.
4320    ///
4321    /// **Accessibility**: Prefer `<em>` for stress emphasis. `<i>` is purely stylistic.
4322    ///
4323    /// **Parameters:**
4324    /// - `text`: Italic text content
4325    #[inline]
4326    pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
4327        Self::create_i().with_child(Self::create_text(text))
4328    }
4329
4330    /// Creates an empty small text element.
4331    ///
4332    /// **Accessibility**: Represents side-comments and small print like copyright/legal text.
4333    #[inline(always)]
4334    pub fn create_small() -> Self {
4335        Self::create_node(NodeType::Small)
4336    }
4337
4338    /// Creates a small text element with text.
4339    ///
4340    /// **Parameters:**
4341    /// - `text`: Small text content
4342    #[inline]
4343    pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
4344        Self::create_small().with_child(Self::create_text(text))
4345    }
4346
4347    /// Creates an empty `<big>` element.
4348    ///
4349    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
4350    #[inline(always)]
4351    pub fn create_big() -> Self {
4352        Self::create_node(NodeType::Big)
4353    }
4354
4355    /// Creates a `<big>` element with text.
4356    ///
4357    /// **Note**: Deprecated in HTML5. Prefer CSS `font-size`.
4358    #[inline]
4359    pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
4360        Self::create_big().with_child(Self::create_text(text))
4361    }
4362
4363    /// Creates an empty bi-directional isolate element.
4364    ///
4365    /// **Accessibility**: Used to isolate text whose direction is unknown,
4366    /// keeping it from affecting surrounding bidi layout.
4367    #[inline(always)]
4368    pub fn create_bdi() -> Self {
4369        Self::create_node(NodeType::Bdi)
4370    }
4371
4372    /// Creates a bi-directional isolate element with text.
4373    ///
4374    /// **Accessibility**: Used to isolate text whose direction is unknown,
4375    /// keeping it from affecting surrounding bidi layout.
4376    #[inline]
4377    pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
4378        Self::create_bdi().with_child(Self::create_text(text))
4379    }
4380
4381    /// Creates an empty word break opportunity element.
4382    ///
4383    /// **Note**: `<wbr>` is a self-closing element that suggests a line-break opportunity.
4384    /// It does not take text content.
4385    #[inline(always)]
4386    pub fn create_wbr() -> Self {
4387        Self::create_node(NodeType::Wbr)
4388    }
4389
4390    /// Creates an empty ruby annotation element.
4391    ///
4392    /// **Accessibility**: Used for East Asian typography to provide
4393    /// pronunciation/translation annotations. Wraps `<rt>`/`<rp>` children.
4394    #[inline(always)]
4395    pub fn create_ruby() -> Self {
4396        Self::create_node(NodeType::Ruby)
4397    }
4398
4399    /// Creates an empty ruby text element.
4400    ///
4401    /// **Accessibility**: Pronunciation/translation annotation inside `<ruby>`.
4402    #[inline(always)]
4403    pub fn create_rt() -> Self {
4404        Self::create_node(NodeType::Rt)
4405    }
4406
4407    /// Creates a ruby text element with text.
4408    ///
4409    /// **Parameters:**
4410    /// - `text`: Ruby annotation content
4411    #[inline]
4412    pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
4413        Self::create_rt().with_child(Self::create_text(text))
4414    }
4415
4416    /// Creates an empty ruby text container element.
4417    ///
4418    /// **Accessibility**: Container for ruby text annotations.
4419    #[inline(always)]
4420    pub fn create_rtc() -> Self {
4421        Self::create_node(NodeType::Rtc)
4422    }
4423
4424    /// Creates an empty ruby fallback parenthesis element.
4425    ///
4426    /// **Accessibility**: Provides parentheses around `<rt>` for browsers without ruby support.
4427    #[inline(always)]
4428    pub fn create_rp() -> Self {
4429        Self::create_node(NodeType::Rp)
4430    }
4431
4432    /// Creates a ruby fallback parenthesis element with text.
4433    ///
4434    /// **Parameters:**
4435    /// - `text`: Parenthesis text (typically "(" or ")")
4436    #[inline]
4437    pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
4438        Self::create_rp().with_child(Self::create_text(text))
4439    }
4440
4441    /// Creates a `<data>` element binding a machine-readable value to its content.
4442    ///
4443    /// **Parameters:**
4444    /// - `value`: Machine-readable value for the `value` attribute.
4445    #[inline]
4446    pub fn create_data(value: AzString) -> Self {
4447        Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
4448    }
4449
4450    /// Creates a `<data>` element with both a machine-readable value and visible text.
4451    ///
4452    /// **Parameters:**
4453    /// - `value`: Machine-readable value for the `value` attribute.
4454    /// - `text`: Human-readable text content.
4455    #[inline]
4456    pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
4457        Self::create_data(value).with_child(Self::create_text(text))
4458    }
4459
4460    /// Creates an empty directory list element.
4461    ///
4462    /// **Note**: Deprecated in HTML5. Use `<ul>` instead.
4463    #[inline(always)]
4464    pub fn create_dir() -> Self {
4465        Self::create_node(NodeType::Dir)
4466    }
4467
4468    /// Creates an empty SVG container element.
4469    ///
4470    /// **Accessibility**: Provide `aria-label` or `<title>` child for assistive tech.
4471    #[inline(always)]
4472    pub fn create_svg() -> Self {
4473        Self::create_node(NodeType::Svg)
4474    }
4475
4476    /// Creates an anchor/hyperlink element without accessibility information.
4477    ///
4478    /// Prefer [`Dom::create_a`] so that screen readers get a meaningful label.
4479    ///
4480    /// **Parameters:**
4481    /// - `href`: Link destination URL
4482    /// - `label`: Link text (pass `None` for image-only links with alt text)
4483    #[inline]
4484    pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
4485        let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
4486        if let OptionString::Some(text) = label {
4487            link = link.with_child(Self::create_text(text));
4488        }
4489        link
4490    }
4491
4492    /// Creates a button element without accessibility information.
4493    ///
4494    /// Prefer [`Dom::create_button`] so that the element has a meaningful accessible
4495    /// name for screen readers.
4496    ///
4497    /// **Parameters:**
4498    /// - `text`: Button label text
4499    #[inline]
4500    pub fn create_button_no_a11y(text: AzString) -> Self {
4501        Self::create_node(NodeType::Button).with_child(Self::create_text(text))
4502    }
4503
4504    /// Creates a label element for form controls without accessibility information.
4505    ///
4506    /// Prefer [`Dom::create_label`] so that screen readers get a descriptive label.
4507    ///
4508    /// **Parameters:**
4509    /// - `for_id`: ID of the associated form control
4510    /// - `text`: Label text
4511    #[inline]
4512    pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
4513        Self::create_node(NodeType::Label)
4514            .with_attribute(AttributeType::Custom(AttributeNameValue {
4515                attr_name: "for".into(),
4516                value: for_id,
4517            }))
4518            .with_child(Self::create_text(text))
4519    }
4520
4521    /// Creates an input element without accessibility information.
4522    ///
4523    /// Prefer [`Dom::create_input`] so that screen readers get a descriptive label
4524    /// beyond the HTML `aria-label` attribute.
4525    ///
4526    /// **Parameters:**
4527    /// - `input_type`: Input type (text, password, email, etc.)
4528    /// - `name`: Form field name
4529    /// - `label`: Accessibility label (required)
4530    #[inline]
4531    pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
4532        Self::create_node(NodeType::Input)
4533            .with_attribute(AttributeType::InputType(input_type))
4534            .with_attribute(AttributeType::Name(name))
4535            .with_attribute(AttributeType::AriaLabel(label))
4536    }
4537
4538    /// Creates a textarea element without accessibility information.
4539    ///
4540    /// Prefer [`Dom::create_textarea`] so that screen readers get an accurate
4541    /// description of the control.
4542    ///
4543    /// **Parameters:**
4544    /// - `name`: Form field name
4545    /// - `label`: Accessibility label (required)
4546    #[inline]
4547    pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
4548        Self::create_node(NodeType::TextArea)
4549            .with_attribute(AttributeType::Name(name))
4550            .with_attribute(AttributeType::AriaLabel(label))
4551    }
4552
4553    /// Creates a select dropdown element without accessibility information.
4554    ///
4555    /// Prefer [`Dom::create_select`] so that screen readers announce the control
4556    /// appropriately.
4557    ///
4558    /// **Parameters:**
4559    /// - `name`: Form field name
4560    /// - `label`: Accessibility label (required)
4561    #[inline]
4562    pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
4563        Self::create_node(NodeType::Select)
4564            .with_attribute(AttributeType::Name(name))
4565            .with_attribute(AttributeType::AriaLabel(label))
4566    }
4567
4568    /// Creates an option element for select dropdowns.
4569    ///
4570    /// **Parameters:**
4571    /// - `value`: Option value
4572    /// - `text`: Display text
4573    #[inline]
4574    pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
4575        Self::create_node(NodeType::SelectOption)
4576            .with_attribute(AttributeType::Value(value))
4577            .with_child(Self::create_text(text))
4578    }
4579
4580    /// Creates an option element for select dropdowns with accessibility information.
4581    ///
4582    /// **Parameters:**
4583    /// - `value`: Option value
4584    /// - `text`: Display text
4585    /// - `aria`: Accessibility information (description, etc.)
4586    ///
4587    /// Use [`Dom::create_option_no_a11y`] only as a deliberate escape hatch.
4588    #[inline]
4589    pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
4590        Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
4591    }
4592
4593    /// Creates an unordered list element.
4594    ///
4595    /// **Accessibility**: Screen readers announce lists and item counts, helping users
4596    /// understand content structure.
4597    #[inline(always)]
4598    pub fn create_ul() -> Self {
4599        Self::create_node(NodeType::Ul)
4600    }
4601
4602    /// Creates an ordered list element.
4603    ///
4604    /// **Accessibility**: Screen readers announce lists and item counts, helping users
4605    /// understand content structure and numbering.
4606    #[inline(always)]
4607    pub fn create_ol() -> Self {
4608        Self::create_node(NodeType::Ol)
4609    }
4610
4611    /// Creates a list item element.
4612    ///
4613    /// **Accessibility**: Must be a child of `ul`, `ol`, or `menu`. Screen readers announce
4614    /// list item position (e.g., "2 of 5").
4615    #[inline(always)]
4616    pub fn create_li() -> Self {
4617        Self::create_node(NodeType::Li)
4618    }
4619
4620    /// Creates a table element without accessibility information.
4621    ///
4622    /// Prefer [`Dom::create_table`] so that screen readers can announce the table's
4623    /// purpose alongside its caption.
4624    #[inline(always)]
4625    pub fn create_table_no_a11y() -> Self {
4626        Self::create_node(NodeType::Table)
4627    }
4628
4629    /// Creates a table caption element.
4630    ///
4631    /// **Accessibility**: Describes the purpose of the table. Screen readers announce this first.
4632    #[inline(always)]
4633    pub fn create_caption() -> Self {
4634        Self::create_node(NodeType::Caption)
4635    }
4636
4637    /// Creates a table header element.
4638    ///
4639    /// **Accessibility**: Groups header rows. Screen readers can navigate table structure.
4640    #[inline(always)]
4641    pub fn create_thead() -> Self {
4642        Self::create_node(NodeType::THead)
4643    }
4644
4645    /// Creates a table body element.
4646    ///
4647    /// **Accessibility**: Groups body rows. Screen readers can navigate table structure.
4648    #[inline(always)]
4649    pub fn create_tbody() -> Self {
4650        Self::create_node(NodeType::TBody)
4651    }
4652
4653    /// Creates a table footer element.
4654    ///
4655    /// **Accessibility**: Groups footer rows. Screen readers can navigate table structure.
4656    #[inline(always)]
4657    pub fn create_tfoot() -> Self {
4658        Self::create_node(NodeType::TFoot)
4659    }
4660
4661    /// Creates a table row element.
4662    #[inline(always)]
4663    pub fn create_tr() -> Self {
4664        Self::create_node(NodeType::Tr)
4665    }
4666
4667    /// Creates a table header cell element.
4668    ///
4669    /// **Accessibility**: Use `scope` attribute ("col" or "row") to associate headers with
4670    /// data cells. Screen readers use this to announce cell context.
4671    #[inline(always)]
4672    pub fn create_th() -> Self {
4673        Self::create_node(NodeType::Th)
4674    }
4675
4676    /// Creates a table data cell element.
4677    #[inline(always)]
4678    pub fn create_td() -> Self {
4679        Self::create_node(NodeType::Td)
4680    }
4681
4682    /// Creates a form element without accessibility information.
4683    ///
4684    /// Prefer [`Dom::create_form`] so that screen readers can announce the form's purpose.
4685    #[inline(always)]
4686    pub fn create_form_no_a11y() -> Self {
4687        Self::create_node(NodeType::Form)
4688    }
4689
4690    /// Creates a form element with accessibility information.
4691    ///
4692    /// **Accessibility**: Group related form controls with `fieldset` and `legend`.
4693    /// Provide clear labels for all inputs. Consider `aria-describedby` for instructions.
4694    ///
4695    /// Use [`Dom::create_form_no_a11y`] only as a deliberate escape hatch.
4696    #[inline]
4697    pub fn create_form(aria: SmallAriaInfo) -> Self {
4698        Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
4699    }
4700
4701    /// Creates a fieldset element for grouping form controls without accessibility info.
4702    ///
4703    /// Prefer [`Dom::create_fieldset`] so that screen readers can announce the group's purpose.
4704    #[inline(always)]
4705    pub fn create_fieldset_no_a11y() -> Self {
4706        Self::create_node(NodeType::FieldSet)
4707    }
4708
4709    /// Creates a fieldset element with accessibility information.
4710    ///
4711    /// **Accessibility**: Groups related form controls. Always include a `legend` as the
4712    /// first child to describe the group. Screen readers announce the legend when entering
4713    /// the fieldset.
4714    ///
4715    /// Use [`Dom::create_fieldset_no_a11y`] only as a deliberate escape hatch.
4716    #[inline]
4717    pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
4718        Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
4719    }
4720
4721    /// Creates a legend element without accessibility information.
4722    ///
4723    /// Prefer [`Dom::create_legend`] so that the legend's accessible name is explicit.
4724    #[inline(always)]
4725    pub fn create_legend_no_a11y() -> Self {
4726        Self::create_node(NodeType::Legend)
4727    }
4728
4729    /// Creates a legend element with accessibility information.
4730    ///
4731    /// **Accessibility**: Describes the purpose of a fieldset. Must be the first child of
4732    /// a fieldset. Screen readers announce this when entering the fieldset.
4733    ///
4734    /// Use [`Dom::create_legend_no_a11y`] only as a deliberate escape hatch.
4735    #[inline]
4736    pub fn create_legend(aria: SmallAriaInfo) -> Self {
4737        Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
4738    }
4739
4740    /// Creates a horizontal rule element.
4741    ///
4742    /// **Accessibility**: Represents a thematic break. Screen readers may announce this as
4743    /// a separator. Consider using CSS borders for purely decorative lines.
4744    #[inline(always)]
4745    pub fn create_hr() -> Self {
4746        Self::create_node(NodeType::Hr)
4747    }
4748
4749    // Additional Element Constructors
4750
4751    /// Creates an address element.
4752    ///
4753    /// **Accessibility**: Represents contact information. Screen readers identify this
4754    /// as address content.
4755    #[inline(always)]
4756    pub const fn create_address() -> Self {
4757        Self {
4758            root: NodeData::create_node(NodeType::Address),
4759            children: DomVec::from_const_slice(&[]),
4760            css: azul_css::css::CssVec::from_const_slice(&[]),
4761            estimated_total_children: 0,
4762        }
4763    }
4764
4765    /// Creates a definition list element.
4766    ///
4767    /// **Accessibility**: Screen readers announce definition lists and their structure.
4768    #[inline(always)]
4769    pub const fn create_dl() -> Self {
4770        Self {
4771            root: NodeData::create_node(NodeType::Dl),
4772            children: DomVec::from_const_slice(&[]),
4773            css: azul_css::css::CssVec::from_const_slice(&[]),
4774            estimated_total_children: 0,
4775        }
4776    }
4777
4778    /// Creates a definition term element.
4779    ///
4780    /// **Accessibility**: Must be a child of `dl`. Represents the term being defined.
4781    #[inline(always)]
4782    pub const fn create_dt() -> Self {
4783        Self {
4784            root: NodeData::create_node(NodeType::Dt),
4785            children: DomVec::from_const_slice(&[]),
4786            css: azul_css::css::CssVec::from_const_slice(&[]),
4787            estimated_total_children: 0,
4788        }
4789    }
4790
4791    /// Creates a definition description element.
4792    ///
4793    /// **Accessibility**: Must be a child of `dl`. Provides the definition for the term.
4794    #[inline(always)]
4795    pub const fn create_dd() -> Self {
4796        Self {
4797            root: NodeData::create_node(NodeType::Dd),
4798            children: DomVec::from_const_slice(&[]),
4799            css: azul_css::css::CssVec::from_const_slice(&[]),
4800            estimated_total_children: 0,
4801        }
4802    }
4803
4804    /// Creates a table column group element.
4805    #[inline(always)]
4806    pub const fn create_colgroup() -> Self {
4807        Self {
4808            root: NodeData::create_node(NodeType::ColGroup),
4809            children: DomVec::from_const_slice(&[]),
4810            css: azul_css::css::CssVec::from_const_slice(&[]),
4811            estimated_total_children: 0,
4812        }
4813    }
4814
4815    /// Creates a table column element.
4816    #[inline]
4817    pub fn create_col(span: i32) -> Self {
4818        Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
4819    }
4820
4821    /// Creates an optgroup element for grouping select options without accessibility info.
4822    ///
4823    /// Prefer [`Dom::create_optgroup`] so that screen readers can announce the group's purpose.
4824    ///
4825    /// **Parameters:**
4826    /// - `label`: Label for the option group
4827    #[inline]
4828    pub fn create_optgroup_no_a11y(label: AzString) -> Self {
4829        Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
4830    }
4831
4832    /// Creates an optgroup element for grouping select options with accessibility information.
4833    ///
4834    /// **Parameters:**
4835    /// - `label`: Label for the option group (visible)
4836    /// - `aria`: Additional accessibility information (description, etc.)
4837    ///
4838    /// Use [`Dom::create_optgroup_no_a11y`] only as a deliberate escape hatch.
4839    #[inline]
4840    pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
4841        Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
4842    }
4843
4844    /// Creates a quotation element.
4845    ///
4846    /// **Accessibility**: Represents an inline quotation.
4847    #[inline(always)]
4848    pub const fn create_q() -> Self {
4849        Self {
4850            root: NodeData::create_node(NodeType::Q),
4851            children: DomVec::from_const_slice(&[]),
4852            css: azul_css::css::CssVec::from_const_slice(&[]),
4853            estimated_total_children: 0,
4854        }
4855    }
4856
4857    /// Creates an empty acronym element.
4858    ///
4859    /// **Note**: Deprecated in HTML5. Consider using `create_abbr()` instead.
4860    #[inline(always)]
4861    pub const fn create_acronym() -> Self {
4862        Self {
4863            root: NodeData::create_node(NodeType::Acronym),
4864            children: DomVec::from_const_slice(&[]),
4865            css: azul_css::css::CssVec::from_const_slice(&[]),
4866            estimated_total_children: 0,
4867        }
4868    }
4869
4870    /// Creates an acronym element with text.
4871    ///
4872    /// **Note**: Deprecated in HTML5. Consider using `create_abbr_with_title()` instead.
4873    #[inline]
4874    pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
4875        Self::create_acronym().with_child(Self::create_text(text))
4876    }
4877
4878    /// Creates a menu element without accessibility information.
4879    ///
4880    /// Prefer [`Dom::create_menu`] so that the menu's purpose is announced.
4881    #[inline(always)]
4882    pub const fn create_menu_no_a11y() -> Self {
4883        Self {
4884            root: NodeData::create_node(NodeType::Menu),
4885            children: DomVec::from_const_slice(&[]),
4886            css: azul_css::css::CssVec::from_const_slice(&[]),
4887            estimated_total_children: 0,
4888        }
4889    }
4890
4891    /// Creates a menu element with accessibility information.
4892    ///
4893    /// **Accessibility**: Represents a list of commands. Similar to `<ul>` but semantic for
4894    /// toolbars/menus.
4895    ///
4896    /// Use [`Dom::create_menu_no_a11y`] only as a deliberate escape hatch.
4897    #[inline]
4898    pub fn create_menu(aria: SmallAriaInfo) -> Self {
4899        Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
4900    }
4901
4902    /// Creates an empty menu item element without accessibility information.
4903    ///
4904    /// Prefer [`Dom::create_menuitem`] so that the menu item's purpose is announced.
4905    #[inline(always)]
4906    pub const fn create_menuitem_no_a11y() -> Self {
4907        Self {
4908            root: NodeData::create_node(NodeType::MenuItem),
4909            children: DomVec::from_const_slice(&[]),
4910            css: azul_css::css::CssVec::from_const_slice(&[]),
4911            estimated_total_children: 0,
4912        }
4913    }
4914
4915    /// Creates an empty menu item element with accessibility information.
4916    ///
4917    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
4918    /// attributes.
4919    ///
4920    /// Use [`Dom::create_menuitem_no_a11y`] only as a deliberate escape hatch.
4921    #[inline]
4922    pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
4923        Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
4924    }
4925
4926    /// Creates a menu item element with text but without accessibility information.
4927    ///
4928    /// Prefer [`Dom::create_menuitem_with_text`] so that screen readers get a
4929    /// distinct accessible name in addition to the visible text.
4930    #[inline]
4931    pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
4932        Self::create_menuitem_no_a11y().with_child(Self::create_text(text))
4933    }
4934
4935    /// Creates a menu item element with text and accessibility information.
4936    ///
4937    /// **Accessibility**: Represents a command in a menu. Use with appropriate role/aria
4938    /// attributes.
4939    ///
4940    /// Use [`Dom::create_menuitem_with_text_no_a11y`] only as a deliberate escape hatch.
4941    #[inline]
4942    pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
4943        Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
4944    }
4945
4946    /// Creates an output element without accessibility information.
4947    ///
4948    /// Prefer [`Dom::create_output`] so that screen readers can announce the
4949    /// computed value's purpose.
4950    #[inline(always)]
4951    pub const fn create_output_no_a11y() -> Self {
4952        Self {
4953            root: NodeData::create_node(NodeType::Output),
4954            children: DomVec::from_const_slice(&[]),
4955            css: azul_css::css::CssVec::from_const_slice(&[]),
4956            estimated_total_children: 0,
4957        }
4958    }
4959
4960    /// Creates an output element with accessibility information.
4961    ///
4962    /// **Accessibility**: Represents the result of a calculation or user action.
4963    /// Use `for` attribute to associate with input elements. Screen readers announce updates.
4964    ///
4965    /// Use [`Dom::create_output_no_a11y`] only as a deliberate escape hatch.
4966    #[inline]
4967    pub fn create_output(aria: SmallAriaInfo) -> Self {
4968        Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
4969    }
4970
4971    /// Creates a progress indicator element without accessibility information.
4972    ///
4973    /// Prefer [`Dom::create_progress`] so that the task being measured is announced.
4974    ///
4975    /// **Parameters:**
4976    /// - `value`: Current progress value
4977    /// - `max`: Maximum value
4978    #[inline]
4979    pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
4980        Self::create_node(NodeType::Progress)
4981            .with_attribute(AttributeType::Custom(AttributeNameValue {
4982                attr_name: "value".into(),
4983                value: value.to_string().into(),
4984            }))
4985            .with_attribute(AttributeType::Custom(AttributeNameValue {
4986                attr_name: "max".into(),
4987                value: max.to_string().into(),
4988            }))
4989    }
4990
4991    /// Creates a progress indicator element with accessibility information.
4992    ///
4993    /// **Accessibility**: Represents task progress. Screen readers announce progress
4994    /// percentage. The `aria` value carries the label, current value, max, and an
4995    /// indeterminate flag for spinners with no known endpoint.
4996    ///
4997    /// Use [`Dom::create_progress_no_a11y`] only as a deliberate escape hatch.
4998    #[inline]
4999    pub fn create_progress(aria: ProgressAriaInfo) -> Self {
5000        let mut node = Self::create_node(NodeType::Progress);
5001        if !aria.indeterminate {
5002            if let azul_css::OptionF32::Some(v) = aria.current_value {
5003                node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5004                    attr_name: "value".into(),
5005                    value: v.to_string().into(),
5006                }));
5007            }
5008        }
5009        if let azul_css::OptionF32::Some(m) = aria.max {
5010            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5011                attr_name: "max".into(),
5012                value: m.to_string().into(),
5013            }));
5014        }
5015        node.with_accessibility_info(aria.to_full_info())
5016    }
5017
5018    /// Creates a meter gauge element without accessibility information.
5019    ///
5020    /// Prefer [`Dom::create_meter`] so that the measurement's purpose is announced.
5021    ///
5022    /// **Parameters:**
5023    /// - `value`: Current meter value
5024    /// - `min`: Minimum value
5025    /// - `max`: Maximum value
5026    #[inline]
5027    pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
5028        Self::create_node(NodeType::Meter)
5029            .with_attribute(AttributeType::Custom(AttributeNameValue {
5030                attr_name: "value".into(),
5031                value: value.to_string().into(),
5032            }))
5033            .with_attribute(AttributeType::Custom(AttributeNameValue {
5034                attr_name: "min".into(),
5035                value: min.to_string().into(),
5036            }))
5037            .with_attribute(AttributeType::Custom(AttributeNameValue {
5038                attr_name: "max".into(),
5039                value: max.to_string().into(),
5040            }))
5041    }
5042
5043    /// Creates a meter gauge element with accessibility information.
5044    ///
5045    /// **Accessibility**: Represents a scalar measurement within a known range.
5046    /// Screen readers announce the measurement. The `aria` value carries the
5047    /// label plus value/min/max/low/high/optimum metadata.
5048    ///
5049    /// Use [`Dom::create_meter_no_a11y`] only as a deliberate escape hatch.
5050    #[inline]
5051    pub fn create_meter(aria: MeterAriaInfo) -> Self {
5052        let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
5053        if let azul_css::OptionF32::Some(v) = aria.low {
5054            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5055                attr_name: "low".into(),
5056                value: v.to_string().into(),
5057            }));
5058        }
5059        if let azul_css::OptionF32::Some(v) = aria.high {
5060            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5061                attr_name: "high".into(),
5062                value: v.to_string().into(),
5063            }));
5064        }
5065        if let azul_css::OptionF32::Some(v) = aria.optimum {
5066            node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5067                attr_name: "optimum".into(),
5068                value: v.to_string().into(),
5069            }));
5070        }
5071        node.with_accessibility_info(aria.to_full_info())
5072    }
5073
5074    /// Creates a datalist element without accessibility information.
5075    ///
5076    /// Prefer [`Dom::create_datalist`] so that the suggestion list's purpose is announced.
5077    #[inline(always)]
5078    pub const fn create_datalist_no_a11y() -> Self {
5079        Self {
5080            root: NodeData::create_node(NodeType::DataList),
5081            children: DomVec::from_const_slice(&[]),
5082            css: azul_css::css::CssVec::from_const_slice(&[]),
5083            estimated_total_children: 0,
5084        }
5085    }
5086
5087    /// Creates a datalist element with accessibility information.
5088    ///
5089    /// **Accessibility**: Provides autocomplete options for inputs.
5090    /// Associate with input using `list` attribute. Screen readers announce available options.
5091    ///
5092    /// Use [`Dom::create_datalist_no_a11y`] only as a deliberate escape hatch.
5093    #[inline]
5094    pub fn create_datalist(aria: SmallAriaInfo) -> Self {
5095        Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
5096    }
5097
5098    // Embedded Content Elements
5099
5100    /// Creates a canvas element for graphics without accessibility information.
5101    ///
5102    /// Prefer [`Dom::create_canvas`] so that the canvas's purpose is announced; canvas
5103    /// content is otherwise opaque to assistive technologies.
5104    #[inline(always)]
5105    pub const fn create_canvas_no_a11y() -> Self {
5106        Self {
5107            root: NodeData::create_node(NodeType::Canvas),
5108            children: DomVec::from_const_slice(&[]),
5109            css: azul_css::css::CssVec::from_const_slice(&[]),
5110            estimated_total_children: 0,
5111        }
5112    }
5113
5114    /// Creates a canvas element for graphics with accessibility information.
5115    ///
5116    /// **Accessibility**: Canvas content is not accessible by default.
5117    /// Always provide fallback content as children and/or detailed aria-label.
5118    /// Consider using SVG for accessible graphics when possible.
5119    ///
5120    /// Use [`Dom::create_canvas_no_a11y`] only as a deliberate escape hatch.
5121    #[inline]
5122    pub fn create_canvas(aria: SmallAriaInfo) -> Self {
5123        Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
5124    }
5125
5126    /// Creates an object element for embedded content.
5127    ///
5128    /// **Accessibility**: Provide fallback content as children. Use aria-label to describe content.
5129    #[inline(always)]
5130    pub const fn create_object() -> Self {
5131        Self {
5132            root: NodeData::create_node(NodeType::Object),
5133            children: DomVec::from_const_slice(&[]),
5134            css: azul_css::css::CssVec::from_const_slice(&[]),
5135            estimated_total_children: 0,
5136        }
5137    }
5138
5139    /// Creates a param element for object parameters.
5140    ///
5141    /// **Parameters:**
5142    /// - `name`: Parameter name
5143    /// - `value`: Parameter value
5144    #[inline]
5145    pub fn create_param(name: AzString, value: AzString) -> Self {
5146        Self::create_node(NodeType::Param)
5147            .with_attribute(AttributeType::Name(name))
5148            .with_attribute(AttributeType::Value(value))
5149    }
5150
5151    /// Creates an embed element.
5152    ///
5153    /// **Accessibility**: Provide alternative content or link. Use aria-label to describe embedded
5154    /// content.
5155    #[inline(always)]
5156    pub const fn create_embed() -> Self {
5157        Self {
5158            root: NodeData::create_node(NodeType::Embed),
5159            children: DomVec::from_const_slice(&[]),
5160            css: azul_css::css::CssVec::from_const_slice(&[]),
5161            estimated_total_children: 0,
5162        }
5163    }
5164
5165    /// Creates an audio element without accessibility information.
5166    ///
5167    /// Prefer [`Dom::create_audio`] so that screen readers announce the audio's purpose.
5168    #[inline(always)]
5169    pub const fn create_audio_no_a11y() -> Self {
5170        Self {
5171            root: NodeData::create_node(NodeType::Audio),
5172            children: DomVec::from_const_slice(&[]),
5173            css: azul_css::css::CssVec::from_const_slice(&[]),
5174            estimated_total_children: 0,
5175        }
5176    }
5177
5178    /// Creates an audio element with accessibility information.
5179    ///
5180    /// **Accessibility**: Always provide controls. Use `<track>` for captions/subtitles.
5181    /// Provide fallback text for unsupported browsers.
5182    ///
5183    /// Use [`Dom::create_audio_no_a11y`] only as a deliberate escape hatch.
5184    #[inline]
5185    pub fn create_audio(aria: SmallAriaInfo) -> Self {
5186        Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
5187    }
5188
5189    /// Creates a video element without accessibility information.
5190    ///
5191    /// Prefer [`Dom::create_video`] so that screen readers announce the video's purpose.
5192    #[inline(always)]
5193    pub const fn create_video_no_a11y() -> Self {
5194        Self {
5195            root: NodeData::create_node(NodeType::Video),
5196            children: DomVec::from_const_slice(&[]),
5197            css: azul_css::css::CssVec::from_const_slice(&[]),
5198            estimated_total_children: 0,
5199        }
5200    }
5201
5202    /// Creates a video element with accessibility information.
5203    ///
5204    /// **Accessibility**: Always provide controls. Use `<track>` for
5205    /// captions/subtitles/descriptions. Provide fallback text. Consider providing transcript.
5206    ///
5207    /// Use [`Dom::create_video_no_a11y`] only as a deliberate escape hatch.
5208    #[inline]
5209    pub fn create_video(aria: SmallAriaInfo) -> Self {
5210        Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
5211    }
5212
5213    /// Creates a source element for media.
5214    ///
5215    /// **Parameters:**
5216    /// - `src`: Media source URL
5217    /// - `media_type`: MIME type (e.g., "video/mp4", "audio/ogg")
5218    #[inline]
5219    pub fn create_source(src: AzString, media_type: AzString) -> Self {
5220        Self::create_node(NodeType::Source)
5221            .with_attribute(AttributeType::Src(src))
5222            .with_attribute(AttributeType::Custom(AttributeNameValue {
5223                attr_name: "type".into(),
5224                value: media_type,
5225            }))
5226    }
5227
5228    /// Creates a track element for media captions/subtitles.
5229    ///
5230    /// **Accessibility**: Essential for deaf/hard-of-hearing users and non-native speakers.
5231    /// Use `kind` (subtitles/captions/descriptions), `srclang`, and `label` attributes.
5232    ///
5233    /// **Parameters:**
5234    /// - `src`: Track file URL (WebVTT format)
5235    /// - `kind`: Track kind ("subtitles", "captions", "descriptions", "chapters", "metadata")
5236    #[inline]
5237    pub fn create_track(src: AzString, kind: AzString) -> Self {
5238        Self::create_node(NodeType::Track)
5239            .with_attribute(AttributeType::Src(src))
5240            .with_attribute(AttributeType::Custom(AttributeNameValue {
5241                attr_name: "kind".into(),
5242                value: kind,
5243            }))
5244    }
5245
5246    /// Creates a map element for image maps.
5247    ///
5248    /// **Accessibility**: Provide text alternatives. Ensure all areas have alt text.
5249    #[inline(always)]
5250    pub const fn create_map() -> Self {
5251        Self {
5252            root: NodeData::create_node(NodeType::Map),
5253            children: DomVec::from_const_slice(&[]),
5254            css: azul_css::css::CssVec::from_const_slice(&[]),
5255            estimated_total_children: 0,
5256        }
5257    }
5258
5259    /// Creates an area element for image map regions without accessibility information.
5260    ///
5261    /// Prefer [`Dom::create_area`] so that screen readers can announce the region's purpose.
5262    #[inline(always)]
5263    pub const fn create_area_no_a11y() -> Self {
5264        Self {
5265            root: NodeData::create_node(NodeType::Area),
5266            children: DomVec::from_const_slice(&[]),
5267            css: azul_css::css::CssVec::from_const_slice(&[]),
5268            estimated_total_children: 0,
5269        }
5270    }
5271
5272    /// Creates an area element for image map regions with accessibility information.
5273    ///
5274    /// **Accessibility**: Always provide `alt` text describing the region/link purpose.
5275    /// Keyboard users should be able to navigate areas.
5276    ///
5277    /// Use [`Dom::create_area_no_a11y`] only as a deliberate escape hatch.
5278    #[inline]
5279    pub fn create_area(aria: SmallAriaInfo) -> Self {
5280        Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
5281    }
5282
5283    // Metadata Elements
5284
5285    /// Creates an empty title element for document title.
5286    ///
5287    /// **Accessibility**: Required for all pages. Screen readers announce this first.
5288    #[inline(always)]
5289    pub fn create_title() -> Self {
5290        Self::create_node(NodeType::Title)
5291    }
5292
5293    /// Creates a title element for document title with text.
5294    ///
5295    /// **Accessibility**: Required for all pages. Screen readers announce this first.
5296    /// Should be unique and descriptive. Keep under 60 characters.
5297    #[inline]
5298    pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
5299        Self::create_title().with_child(Self::create_text(text))
5300    }
5301
5302    /// Creates a meta element.
5303    ///
5304    /// **Accessibility**: Use for charset, viewport, description. Crucial for proper text display.
5305    #[inline(always)]
5306    pub const fn create_meta() -> Self {
5307        Self {
5308            root: NodeData::create_node(NodeType::Meta),
5309            children: DomVec::from_const_slice(&[]),
5310            css: azul_css::css::CssVec::from_const_slice(&[]),
5311            estimated_total_children: 0,
5312        }
5313    }
5314
5315    /// Creates a link element for external resources.
5316    ///
5317    /// **Accessibility**: Use for stylesheets, icons, alternate versions.
5318    /// Provide meaningful `title` attribute for alternate stylesheets.
5319    #[inline(always)]
5320    pub const fn create_link() -> Self {
5321        Self {
5322            root: NodeData::create_node(NodeType::Link),
5323            children: DomVec::from_const_slice(&[]),
5324            css: azul_css::css::CssVec::from_const_slice(&[]),
5325            estimated_total_children: 0,
5326        }
5327    }
5328
5329    /// Creates a script element.
5330    ///
5331    /// **Accessibility**: Ensure scripted content is accessible.
5332    /// Provide noscript fallbacks for critical functionality.
5333    #[inline(always)]
5334    pub const fn create_script() -> Self {
5335        Self {
5336            root: NodeData::create_node(NodeType::Script),
5337            children: DomVec::from_const_slice(&[]),
5338            css: azul_css::css::CssVec::from_const_slice(&[]),
5339            estimated_total_children: 0,
5340        }
5341    }
5342
5343    /// Creates an empty style element for embedded CSS.
5344    ///
5345    /// **Note**: In Azul, use `.with_component_css()` instead for styling.
5346    /// This creates a `<style>` HTML element for embedded stylesheets.
5347    #[inline(always)]
5348    pub const fn create_style() -> Self {
5349        Self {
5350            root: NodeData::create_node(NodeType::Style),
5351            children: DomVec::from_const_slice(&[]),
5352            css: azul_css::css::CssVec::from_const_slice(&[]),
5353            estimated_total_children: 0,
5354        }
5355    }
5356
5357    /// Creates a style element for embedded CSS with the given stylesheet text.
5358    ///
5359    /// **Note**: In Azul, use `.with_component_css()` instead for styling.
5360    /// This creates a `<style>` HTML element for embedded stylesheets.
5361    #[inline]
5362    pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
5363        Self::create_style().with_child(Self::create_text(text))
5364    }
5365
5366    /// Creates a base element for document base URL.
5367    ///
5368    /// **Parameters:**
5369    /// - `href`: Base URL for relative URLs in the document
5370    #[inline]
5371    pub fn create_base(href: AzString) -> Self {
5372        Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
5373    }
5374
5375    // Advanced Constructors with Parameters
5376
5377    /// Creates a table header cell with scope.
5378    ///
5379    /// **Parameters:**
5380    /// - `scope`: "col", "row", "colgroup", or "rowgroup"
5381    /// - `text`: Header text
5382    ///
5383    /// **Accessibility**: The scope attribute is crucial for associating headers with data cells.
5384    #[inline]
5385    pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
5386        Self::create_node(NodeType::Th)
5387            .with_attribute(AttributeType::Scope(scope))
5388            .with_child(Self::create_text(text))
5389    }
5390
5391    /// Creates a table data cell with text.
5392    ///
5393    /// **Parameters:**
5394    /// - `text`: Cell content
5395    #[inline]
5396    pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
5397        Self::create_td().with_child(Self::create_text(text))
5398    }
5399
5400    /// Creates a table header cell with text.
5401    ///
5402    /// **Parameters:**
5403    /// - `text`: Header text
5404    #[inline]
5405    pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
5406        Self::create_th().with_child(Self::create_text(text))
5407    }
5408
5409    /// Creates a list item with text.
5410    ///
5411    /// **Parameters:**
5412    /// - `text`: List item content
5413    #[inline]
5414    pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
5415        Self::create_li().with_child(Self::create_text(text))
5416    }
5417
5418    /// Creates a paragraph with text.
5419    ///
5420    /// **Parameters:**
5421    /// - `text`: Paragraph content
5422    #[inline]
5423    pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
5424        Self::create_p().with_child(Self::create_text(text))
5425    }
5426
5427    // Accessibility-Aware Constructors
5428    // These constructors require explicit accessibility information.
5429    // Use the `*_no_a11y` variants only as a deliberate escape hatch.
5430
5431    /// Creates a button with text content and accessibility information.
5432    ///
5433    /// Use [`Dom::create_button_no_a11y`] to skip the accessibility information.
5434    ///
5435    /// **Parameters:**
5436    /// - `text`: The visible button text
5437    /// - `aria`: Accessibility information (role, description, etc.)
5438    #[inline]
5439    pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5440        let mut btn = Self::create_button_no_a11y(text.into());
5441        btn.root.set_accessibility_info(aria.to_full_info());
5442        btn
5443    }
5444
5445    /// Creates a link (anchor) with href, text, and accessibility information.
5446    ///
5447    /// Use [`Dom::create_a_no_a11y`] to skip the accessibility information (e.g. for
5448    /// image-only links whose accessible name comes from an `<img alt>`).
5449    ///
5450    /// **Parameters:**
5451    /// - `href`: The link destination
5452    /// - `text`: The visible link text
5453    /// - `aria`: Accessibility information (expanded description, etc.)
5454    #[inline]
5455    pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
5456        href: S1,
5457        text: S2,
5458        aria: SmallAriaInfo,
5459    ) -> Self {
5460        let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
5461        link.root.set_accessibility_info(aria.to_full_info());
5462        link
5463    }
5464
5465    /// Creates an input element with type, name, and accessibility information.
5466    ///
5467    /// Use [`Dom::create_input_no_a11y`] to skip the accessibility information.
5468    ///
5469    /// **Parameters:**
5470    /// - `input_type`: The input type (text, password, email, etc.)
5471    /// - `name`: The form field name
5472    /// - `label`: Base accessibility label
5473    /// - `aria`: Additional accessibility information (description, etc.)
5474    #[inline]
5475    pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
5476        input_type: S1,
5477        name: S2,
5478        label: S3,
5479        aria: SmallAriaInfo,
5480    ) -> Self {
5481        let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
5482        input.root.set_accessibility_info(aria.to_full_info());
5483        input
5484    }
5485
5486    /// Creates a textarea with name and accessibility information.
5487    ///
5488    /// Use [`Dom::create_textarea_no_a11y`] to skip the accessibility information.
5489    ///
5490    /// **Parameters:**
5491    /// - `name`: The form field name
5492    /// - `label`: Base accessibility label
5493    /// - `aria`: Additional accessibility information (description, etc.)
5494    #[inline]
5495    pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
5496        name: S1,
5497        label: S2,
5498        aria: SmallAriaInfo,
5499    ) -> Self {
5500        let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
5501        textarea.root.set_accessibility_info(aria.to_full_info());
5502        textarea
5503    }
5504
5505    /// Creates a select dropdown with name and accessibility information.
5506    ///
5507    /// Use [`Dom::create_select_no_a11y`] to skip the accessibility information.
5508    ///
5509    /// **Parameters:**
5510    /// - `name`: The form field name
5511    /// - `label`: Base accessibility label
5512    /// - `aria`: Additional accessibility information (description, etc.)
5513    #[inline]
5514    pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
5515        name: S1,
5516        label: S2,
5517        aria: SmallAriaInfo,
5518    ) -> Self {
5519        let mut select = Self::create_select_no_a11y(name.into(), label.into());
5520        select.root.set_accessibility_info(aria.to_full_info());
5521        select
5522    }
5523
5524    /// Creates a table with caption and accessibility information.
5525    ///
5526    /// Use [`Dom::create_table_no_a11y`] to skip the caption and accessibility
5527    /// information.
5528    ///
5529    /// **Parameters:**
5530    /// - `caption`: Table caption (visible title)
5531    /// - `aria`: Accessibility information describing table purpose
5532    #[inline]
5533    pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
5534        let mut table = Self::create_table_no_a11y()
5535            .with_child(Self::create_caption().with_child(Self::create_text(caption)));
5536        table.root.set_accessibility_info(aria.to_full_info());
5537        table
5538    }
5539
5540    /// Creates a label for a form control with additional accessibility information.
5541    ///
5542    /// Use [`Dom::create_label_no_a11y`] to skip the accessibility information.
5543    ///
5544    /// **Parameters:**
5545    /// - `for_id`: The ID of the associated form control
5546    /// - `text`: The visible label text
5547    /// - `aria`: Additional accessibility information (description, etc.)
5548    #[inline]
5549    pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
5550        for_id: S1,
5551        text: S2,
5552        aria: SmallAriaInfo,
5553    ) -> Self {
5554        let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
5555        label.root.set_accessibility_info(aria.to_full_info());
5556        label
5557    }
5558
5559    /// Parse XML/XHTML string into a DOM
5560    ///
5561    /// This is a simple wrapper that parses XML and converts it to a DOM.
5562    /// For now, it just creates a text node with the content since full XML parsing
5563    /// requires the xml feature and more complex parsing logic.
5564    #[cfg(feature = "xml")]
5565    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5566        // TODO: Implement full XML parsing
5567        // For now, just create a text node showing that XML was loaded
5568        Self::create_text(format!(
5569            "XML content loaded ({} bytes)",
5570            xml_str.as_ref().len()
5571        ))
5572    }
5573
5574    /// Parse XML/XHTML string into a DOM (fallback without xml feature)
5575    #[cfg(not(feature = "xml"))]
5576    pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5577        Self::create_text(format!(
5578            "XML parsing requires 'xml' feature ({} bytes)",
5579            xml_str.as_ref().len()
5580        ))
5581    }
5582
5583    // Swaps `self` with a default DOM, necessary for builder methods
5584    #[inline(always)]
5585    pub fn swap_with_default(&mut self) -> Self {
5586        let mut s = Self {
5587            root: NodeData::create_div(),
5588            children: DomVec::from_const_slice(&[]),
5589            css: azul_css::css::CssVec::from_const_slice(&[]),
5590            estimated_total_children: 0,
5591        };
5592        mem::swap(&mut s, self);
5593        s
5594    }
5595
5596    #[inline]
5597    pub fn add_child(&mut self, child: Dom) {
5598        let estimated = child.estimated_total_children;
5599        let mut v: DomVec = Vec::new().into();
5600        mem::swap(&mut v, &mut self.children);
5601        let mut v = v.into_library_owned_vec();
5602        v.push(child);
5603        self.children = v.into();
5604        self.estimated_total_children += estimated + 1;
5605    }
5606
5607    #[inline(always)]
5608    pub fn set_children(&mut self, children: DomVec) {
5609        let children_estimated = children
5610            .iter()
5611            .map(|s| s.estimated_total_children + 1)
5612            .sum();
5613        self.children = children;
5614        self.estimated_total_children = children_estimated;
5615    }
5616
5617    pub fn copy_except_for_root(&mut self) -> Self {
5618        Self {
5619            root: self.root.copy_special(),
5620            children: self.children.clone(),
5621            css: self.css.clone(),
5622            estimated_total_children: self.estimated_total_children,
5623        }
5624    }
5625    pub fn node_count(&self) -> usize {
5626        self.estimated_total_children + 1
5627    }
5628
5629    /// Push a component-level stylesheet onto this Dom subtree's
5630    /// stylesheet list. The cascade pass in `regenerate_layout()` merges
5631    /// every component-level `Css` together with the inline rules.
5632    /// Later `with_component_css(...)` calls have higher cascade priority
5633    /// (override earlier ones at equal specificity).
5634    pub fn with_component_css(mut self, css: azul_css::css::Css) -> Self {
5635        self.add_component_css(css);
5636        self
5637    }
5638
5639    /// Mutating form of `with_component_css`. Pushes a stylesheet onto
5640    /// the subtree's component-level CSS list in place.
5641    pub fn add_component_css(&mut self, css: azul_css::css::Css) {
5642        let mut v = Vec::new().into();
5643        core::mem::swap(&mut v, &mut self.css);
5644        let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
5645        v.push(css);
5646        self.css = v.into();
5647    }
5648
5649    /// Replace the subtree's entire component-level CSS list with the
5650    /// provided one. Use `add_component_css` / `with_component_css` for
5651    /// stacking; this is the wholesale-replace form.
5652    pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
5653        self.css = css;
5654    }
5655    #[inline(always)]
5656    pub fn with_children(mut self, children: DomVec) -> Self {
5657        self.set_children(children);
5658        self
5659    }
5660    #[inline(always)]
5661    pub fn with_child(mut self, child: Self) -> Self {
5662        self.add_child(child);
5663        self
5664    }
5665    #[inline(always)]
5666    pub fn with_node_type(mut self, node_type: NodeType) -> Self {
5667        self.root.set_node_type(node_type);
5668        self
5669    }
5670    #[inline(always)]
5671    pub fn with_id(mut self, id: AzString) -> Self {
5672        self.root.add_id(id);
5673        self
5674    }
5675    #[inline(always)]
5676    pub fn with_class(mut self, class: AzString) -> Self {
5677        self.root.add_class(class);
5678        self
5679    }
5680    #[inline(always)]
5681    pub fn with_callback<C: Into<CoreCallback>>(
5682        mut self,
5683        event: EventFilter,
5684        data: RefAny,
5685        callback: C,
5686    ) -> Self {
5687        self.root.add_callback(event, data, callback);
5688        self
5689    }
5690    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
5691    #[inline(always)]
5692    pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
5693        self.root.add_css_property(prop);
5694        self
5695    }
5696    /// Add a CSS property with optional conditions (hover, focus, active, etc.)
5697    #[inline(always)]
5698    pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
5699        self.root.add_css_property(prop);
5700    }
5701    #[inline(always)]
5702    pub fn add_class(&mut self, class: AzString) {
5703        self.root.add_class(class);
5704    }
5705    #[inline(always)]
5706    pub fn add_callback<C: Into<CoreCallback>>(
5707        &mut self,
5708        event: EventFilter,
5709        data: RefAny,
5710        callback: C,
5711    ) {
5712        self.root.add_callback(event, data, callback);
5713    }
5714    #[inline(always)]
5715    pub fn set_tab_index(&mut self, tab_index: TabIndex) {
5716        self.root.set_tab_index(tab_index);
5717    }
5718    #[inline(always)]
5719    pub fn set_contenteditable(&mut self, contenteditable: bool) {
5720        self.root.set_contenteditable(contenteditable);
5721    }
5722    #[inline(always)]
5723    pub fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
5724        self.root.set_tab_index(tab_index);
5725        self
5726    }
5727    #[inline(always)]
5728    pub fn with_contenteditable(mut self, contenteditable: bool) -> Self {
5729        self.root.set_contenteditable(contenteditable);
5730        self
5731    }
5732    #[inline]
5733    pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
5734        self.root.set_dataset(data);
5735        self
5736    }
5737    #[inline(always)]
5738    pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
5739        self.root.set_ids_and_classes(ids_and_classes);
5740        self
5741    }
5742
5743    /// Adds an attribute to this DOM element.
5744    #[inline(always)]
5745    pub fn with_attribute(mut self, attr: AttributeType) -> Self {
5746        let mut attrs = self.root.attributes().clone();
5747        let mut v = attrs.into_library_owned_vec();
5748        v.push(attr);
5749        self.root.set_attributes(v.into());
5750        self
5751    }
5752
5753    /// Adds multiple attributes to this DOM element.
5754    #[inline(always)]
5755    pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
5756        self.root.set_attributes(attributes);
5757        self
5758    }
5759
5760    #[inline(always)]
5761    pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
5762        self.root.callbacks = callbacks;
5763        self
5764    }
5765    /// Legacy: builder-form for the flat property+conditions list. Each entry
5766    /// becomes a single-declaration rule at `rule_priority::INLINE`.
5767    #[inline(always)]
5768    pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
5769        self.root.style = css_props.into();
5770        self
5771    }
5772    /// Builder-form for setting the inline `Css` directly.
5773    #[inline(always)]
5774    pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
5775        self.root.style = style;
5776        self
5777    }
5778
5779    /// Assigns a stable key to the root node of this DOM for reconciliation.
5780    ///
5781    /// This is crucial for performance and correct state preservation when
5782    /// lists of items change order or items are inserted/removed.
5783    ///
5784    /// # Example
5785    /// ```rust
5786    /// # use azul_core::dom::Dom;
5787    /// Dom::create_div()
5788    ///     .with_key("user-avatar-123");
5789    /// ```
5790    #[inline]
5791    pub fn with_key<K: core::hash::Hash>(mut self, key: K) -> Self {
5792        self.root.set_key(key);
5793        self
5794    }
5795
5796    /// Registers a callback to merge dataset state from the previous frame.
5797    ///
5798    /// This is used for components that maintain heavy internal state (video players,
5799    /// WebGL contexts, network connections) that should not be destroyed and recreated
5800    /// on every render frame.
5801    ///
5802    /// The callback receives both datasets as `RefAny` (cheap shallow clones) and
5803    /// returns the `RefAny` that should be used for the new node.
5804    #[inline]
5805    pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
5806        self.root.set_merge_callback(callback);
5807        self
5808    }
5809
5810    /// Parse and set CSS styles with full selector support.
5811    ///
5812    /// This is the unified API for setting inline CSS on a DOM node. It supports:
5813    /// - Simple properties: `color: red; font-size: 14px;`
5814    /// - Pseudo-selectors: `:hover { background: blue; }`
5815    /// - @-rules: `@os linux { font-size: 14px; }`
5816    /// - Nesting: `@os linux { font-size: 14px; :hover { color: red; }}`
5817    ///
5818    /// # Examples
5819    /// ```rust
5820    /// # use azul_core::dom::Dom;
5821    /// // Simple inline styles
5822    /// Dom::create_div().with_css("color: red; font-size: 14px;");
5823    ///
5824    /// // With hover and active states
5825    /// Dom::create_div().with_css("
5826    ///     color: blue;
5827    ///     :hover { color: red; }
5828    ///     :active { color: green; }
5829    /// ");
5830    ///
5831    /// // OS-specific with nested hover
5832    /// Dom::create_div().with_css("
5833    ///     font-size: 12px;
5834    ///     @os linux { font-size: 14px; :hover { color: red; }}
5835    ///     @os windows { font-size: 13px; }
5836    /// ");
5837    /// ```
5838    pub fn set_css(&mut self, style: &str) {
5839        let parsed = azul_css::css::Css::parse_inline(style);
5840        let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
5841        mem::swap(&mut current, &mut self.root.style.rules);
5842        let mut v = current.into_library_owned_vec();
5843        v.extend(parsed.rules.into_library_owned_vec());
5844        self.root.style.rules = v.into();
5845    }
5846
5847    /// Builder method for `set_css`
5848    pub fn with_css(mut self, style: &str) -> Self {
5849        self.set_css(style);
5850        self
5851    }
5852
5853    /// Sets the context menu for the root node
5854    #[inline]
5855    pub fn set_context_menu(&mut self, context_menu: Menu) {
5856        self.root.set_context_menu(context_menu);
5857    }
5858
5859    #[inline]
5860    pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
5861        self.set_context_menu(context_menu);
5862        self
5863    }
5864
5865    /// Sets the menu bar for the root node
5866    #[inline]
5867    pub fn set_menu_bar(&mut self, menu_bar: Menu) {
5868        self.root.set_menu_bar(menu_bar);
5869    }
5870
5871    #[inline]
5872    pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
5873        self.set_menu_bar(menu_bar);
5874        self
5875    }
5876
5877    #[inline]
5878    pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
5879        self.root.set_clip_mask(clip_mask);
5880        self
5881    }
5882
5883    #[inline]
5884    pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
5885        self.root.set_svg_data(SvgNodeData::Path(clip));
5886        self
5887    }
5888
5889    #[inline]
5890    pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
5891        self.root.set_svg_data(data);
5892        self
5893    }
5894
5895    #[inline]
5896    pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
5897        self.root.set_accessibility_info(accessibility_info);
5898        self
5899    }
5900
5901    pub fn fixup_children_estimated(&mut self) -> usize {
5902        if self.children.is_empty() {
5903            self.estimated_total_children = 0;
5904        } else {
5905            self.estimated_total_children = self
5906                .children
5907                .iter_mut()
5908                .map(|s| s.fixup_children_estimated() + 1)
5909                .sum();
5910        }
5911        return self.estimated_total_children;
5912    }
5913}
5914
5915impl core::iter::FromIterator<Dom> for Dom {
5916    fn from_iter<I: IntoIterator<Item = Dom>>(iter: I) -> Self {
5917        let mut estimated_total_children = 0;
5918        let children = iter
5919            .into_iter()
5920            .map(|c| {
5921                estimated_total_children += c.estimated_total_children + 1;
5922                c
5923            })
5924            .collect::<Vec<Dom>>();
5925
5926        Dom {
5927            root: NodeData::create_div(),
5928            children: children.into(),
5929            css: azul_css::css::CssVec::from_const_slice(&[]),
5930            estimated_total_children,
5931        }
5932    }
5933}
5934
5935impl fmt::Debug for Dom {
5936    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5937        fn print_dom(d: &Dom, f: &mut fmt::Formatter) -> fmt::Result {
5938            write!(f, "Dom {{\r\n")?;
5939            write!(f, "\troot: {:#?}\r\n", d.root)?;
5940            write!(
5941                f,
5942                "\testimated_total_children: {:#?}\r\n",
5943                d.estimated_total_children
5944            )?;
5945            write!(f, "\tchildren: [\r\n")?;
5946            for c in d.children.iter() {
5947                print_dom(c, f)?;
5948            }
5949            write!(f, "\t]\r\n")?;
5950            write!(f, "}}\r\n")?;
5951            Ok(())
5952        }
5953
5954        print_dom(self, f)
5955    }
5956}