···47 | Ok (reader, ic, system_id) ->
48 (* Run validation *)
49 let result = Htmlrw_check.check ~system_id reader in
50-51 (* Close input if it's not stdin *)
52 if file <> "-" then close_in ic;
53
···47 | Ok (reader, ic, system_id) ->
48 (* Run validation *)
49 let result = Htmlrw_check.check ~system_id reader in
050 (* Close input if it's not stdin *)
51 if file <> "-" then close_in ic;
52
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** JavaScript API for HTML5 validation in the browser.
7+8+ This module provides the main entry points for validating HTML in a
9+ browser environment. It wraps the core {!Htmlrw_check} validator and
10+ adds browser-specific functionality for element mapping and annotation.
11+12+ {2 JavaScript Usage}
13+14+ After loading the compiled JavaScript, the API is available on [window]:
15+16+ {v
17+ // Validate an element (recommended)
18+ const result = html5rw.validateElement(document.body);
19+ console.log(result.errorCount, "errors found");
20+21+ // Validate with annotation
22+ html5rw.validateAndAnnotate(document.body, {
23+ showTooltips: true,
24+ showPanel: true
25+ });
26+27+ // Validate a raw HTML string
28+ const result = html5rw.validateString("<div><p>Hello</div>");
29+ result.warnings.forEach(w => console.log(w.message));
30+ v}
31+32+ {2 OCaml Usage}
33+34+ {[
35+ let result = Htmlrw_js.validate_element (Brr.Document.body G.document) in
36+ List.iter (fun bm ->
37+ Brr.Console.log [Jstr.v bm.Htmlrw_js_types.message.text]
38+ ) result.messages
39+ ]} *)
40+41+42+open Htmlrw_js_types
43+44+45+(** {1 Validation} *)
46+47+(** Validate an HTML string.
48+49+ This is the simplest form of validation. Since there's no source element,
50+ the returned {!browser_message}s will not have element references.
51+52+ {[
53+ let result = validate_string "<html><body><img></body></html>" in
54+ if Htmlrw_check.has_errors result.core_result then
55+ (* handle errors *)
56+ ]} *)
57+val validate_string : string -> result
58+59+(** Validate a DOM element's HTML.
60+61+ Serializes the element to HTML, validates it, and maps the results
62+ back to the live DOM elements.
63+64+ {[
65+ let result = validate_element (Document.body G.document) in
66+ List.iter (fun bm ->
67+ match bm.element_ref with
68+ | Some { element = Some el; _ } ->
69+ El.set_class (Jstr.v "has-error") true el
70+ | _ -> ()
71+ ) result.messages
72+ ]} *)
73+val validate_element : Brr.El.t -> result
74+75+76+(** {1 Validation with Annotation}
77+78+ These functions validate and immediately annotate the DOM with results. *)
79+80+(** Validate and annotate an element.
81+82+ This combines validation with DOM annotation. The element and its
83+ descendants are annotated with data attributes, classes, and optionally
84+ tooltips based on the validation results.
85+86+ @param config Annotation configuration. Defaults to {!default_annotation_config}. *)
87+val validate_and_annotate :
88+ ?config:annotation_config -> Brr.El.t -> result
89+90+(** Validate, annotate, and show the warning panel.
91+92+ The all-in-one function for browser validation with full UI.
93+94+ @param annotation_config How to annotate elements.
95+ @param panel_config How to display the warning panel. *)
96+val validate_and_show_panel :
97+ ?annotation_config:annotation_config ->
98+ ?panel_config:panel_config ->
99+ Brr.El.t ->
100+ result
101+102+103+(** {1 Result Inspection} *)
104+105+(** Get messages filtered by severity. *)
106+val errors : result -> browser_message list
107+val warnings_only : result -> browser_message list
108+val infos : result -> browser_message list
109+110+(** Check if there are any errors. *)
111+val has_errors : result -> bool
112+113+(** Check if there are any warnings or errors. *)
114+val has_issues : result -> bool
115+116+(** Get total count of all messages. *)
117+val message_count : result -> int
118+119+120+(** {1 JavaScript Export}
121+122+ These functions register the API on the JavaScript global object. *)
123+124+(** Register the validation API on [window.html5rw].
125+126+ Call this from your main entry point to expose the JavaScript API:
127+128+ {[
129+ let () = Htmlrw_js.register_global_api ()
130+ ]}
131+132+ This exposes:
133+ - [html5rw.validateString(html)] -> result object
134+ - [html5rw.validateElement(el)] -> result object
135+ - [html5rw.validateAndAnnotate(el, config?)] -> result object
136+ - [html5rw.validateAndShowPanel(el, config?)] -> result object
137+ - [html5rw.clearAnnotations(el)] -> void
138+ - [html5rw.hidePanel()] -> void *)
139+val register_global_api : unit -> unit
140+141+(** Register the API on a custom object instead of [window.html5rw].
142+143+ Useful for module bundlers or when you want to control the namespace. *)
144+val register_api_on : Jv.t -> unit
145+146+147+(** {1 Low-level Access} *)
148+149+(** Access the element map from a validation result.
150+151+ Useful for custom element lookup logic. Returns [None] if the result
152+ was from {!validate_string} (no source element). *)
153+val element_map : result -> Htmlrw_js_dom.t option
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** DOM annotation for validation warnings.
7+8+ This module applies validation results to the live DOM by adding
9+ data attributes, CSS classes, and tooltip overlays to elements
10+ that have warnings. *)
11+12+open Htmlrw_js_types
13+14+15+(** {1 Annotation} *)
16+17+(** Annotate elements in a subtree based on validation results.
18+19+ For each message with an element reference, this function:
20+ 1. Adds data attributes ([data-html5rw-severity], etc.) if configured
21+ 2. Adds CSS classes ([html5rw-error], etc.) if configured
22+ 3. Creates tooltip elements if configured
23+24+ @param config Annotation configuration.
25+ @param root The root element to annotate within.
26+ @param messages The validation messages with element references. *)
27+val annotate :
28+ config:annotation_config ->
29+ root:Brr.El.t ->
30+ browser_message list ->
31+ unit
32+33+(** Annotate a single element with a message.
34+35+ Lower-level function for custom annotation logic. *)
36+val annotate_element :
37+ config:annotation_config ->
38+ Brr.El.t ->
39+ Htmlrw_check.message ->
40+ unit
41+42+43+(** {1 Clearing Annotations} *)
44+45+(** Remove all annotations from a subtree.
46+47+ This removes:
48+ - All [data-html5rw-*] attributes
49+ - All [html5rw-*] CSS classes
50+ - All tooltip elements created by this module *)
51+val clear : Brr.El.t -> unit
52+53+(** Remove annotations from a single element (not descendants). *)
54+val clear_element : Brr.El.t -> unit
55+56+57+(** {1 Tooltips} *)
58+59+(** Tooltip state for an element. *)
60+type tooltip
61+62+(** Create a tooltip for an element.
63+64+ The tooltip is not immediately visible; it appears on hover
65+ if CSS is set up correctly, or can be shown programmatically.
66+67+ @param position Where to position the tooltip.
68+ @param el The element to attach the tooltip to.
69+ @param messages All messages for this element (may be multiple). *)
70+val create_tooltip :
71+ position:[ `Above | `Below | `Auto ] ->
72+ Brr.El.t ->
73+ Htmlrw_check.message list ->
74+ tooltip
75+76+(** Show a tooltip immediately. *)
77+val show_tooltip : tooltip -> unit
78+79+(** Hide a tooltip. *)
80+val hide_tooltip : tooltip -> unit
81+82+(** Remove a tooltip from the DOM. *)
83+val remove_tooltip : tooltip -> unit
84+85+(** Get all tooltips created in a subtree. *)
86+val tooltips_in : Brr.El.t -> tooltip list
87+88+89+(** {1 Highlighting} *)
90+91+(** Highlight an element (for click-to-navigate in the panel).
92+93+ Adds a temporary visual highlight and scrolls the element into view. *)
94+val highlight_element : Brr.El.t -> unit
95+96+(** Remove highlight from an element. *)
97+val unhighlight_element : Brr.El.t -> unit
98+99+(** Remove all highlights. *)
100+val clear_highlights : unit -> unit
101+102+103+(** {1 Data Attributes}
104+105+ Constants for the data attributes used by annotation. *)
106+107+module Data_attr : sig
108+ (** [data-html5rw-severity] - "error", "warning", or "info" *)
109+ val severity : Jstr.t
110+111+ (** [data-html5rw-message] - The warning message text *)
112+ val message : Jstr.t
113+114+ (** [data-html5rw-code] - The error code *)
115+ val code : Jstr.t
116+117+ (** [data-html5rw-count] - Number of warnings on this element *)
118+ val count : Jstr.t
119+end
120+121+122+(** {1 CSS Classes}
123+124+ Constants for the CSS classes used by annotation. *)
125+126+module Css_class : sig
127+ (** [html5rw-error] - Element has at least one error *)
128+ val error : Jstr.t
129+130+ (** [html5rw-warning] - Element has warnings but no errors *)
131+ val warning : Jstr.t
132+133+ (** [html5rw-info] - Element has only info messages *)
134+ val info : Jstr.t
135+136+ (** [html5rw-has-issues] - Element has any validation messages *)
137+ val has_issues : Jstr.t
138+139+ (** [html5rw-highlighted] - Element is currently highlighted *)
140+ val highlighted : Jstr.t
141+142+ (** [html5rw-tooltip] - The tooltip container element *)
143+ val tooltip : Jstr.t
144+145+ (** [html5rw-tooltip-visible] - Tooltip is currently visible *)
146+ val tooltip_visible : Jstr.t
147+end
148+149+150+(** {1 CSS Injection}
151+152+ Optionally inject default styles for annotations. *)
153+154+(** Inject default CSS styles for annotations and tooltips.
155+156+ Adds a [<style>] element to the document head with styles for:
157+ - Annotation classes (outlines, backgrounds)
158+ - Tooltip positioning and appearance
159+ - Highlight animation
160+161+ @param theme Light or dark theme. [`Auto] uses [prefers-color-scheme].
162+ @return The injected style element (can be removed later). *)
163+val inject_default_styles : theme:[ `Light | `Dark | `Auto ] -> Brr.El.t
164+165+(** Remove the injected style element. *)
166+val remove_injected_styles : Brr.El.t -> unit
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+open Brr
7+8+(* Helper to compare elements using JavaScript strict equality *)
9+let el_equal a b =
10+ Jv.strict_equal (El.to_jv a) (El.to_jv b)
11+12+(* A location-keyed map for finding elements by line/column *)
13+module LocMap = Map.Make(struct
14+ type t = int * int
15+ let compare = compare
16+end)
17+18+type t = {
19+ root : El.t;
20+ html_source : string;
21+ loc_to_el : El.t LocMap.t;
22+ (* Mapping from (line, column) to browser elements *)
23+}
24+25+let outer_html el =
26+ Jstr.to_string (Jv.get (El.to_jv el) "outerHTML" |> Jv.to_jstr)
27+28+let inner_html el =
29+ Jstr.to_string (Jv.get (El.to_jv el) "innerHTML" |> Jv.to_jstr)
30+31+let iter_elements f root =
32+ let rec walk el =
33+ f el;
34+ List.iter walk (El.children ~only_els:true el)
35+ in
36+ walk root
37+38+let fold_elements f acc root =
39+ let rec walk acc el =
40+ let acc = f acc el in
41+ List.fold_left walk acc (El.children ~only_els:true el)
42+ in
43+ walk acc root
44+45+let filter_elements pred root =
46+ fold_elements (fun acc el ->
47+ if pred el then el :: acc else acc
48+ ) [] root |> List.rev
49+50+(* Build element map by walking browser DOM and parsed DOM in parallel *)
51+let create root =
52+ let raw_html = outer_html root in
53+ (* Prepend DOCTYPE if not present - outerHTML doesn't include it *)
54+ let html =
55+ let lower = String.lowercase_ascii raw_html in
56+ if String.length lower >= 9 && String.sub lower 0 9 = "<!doctype" then
57+ raw_html
58+ else
59+ "<!DOCTYPE html>" ^ raw_html
60+ in
61+62+ (* Parse the HTML to get a tree with locations *)
63+ let reader = Bytesrw.Bytes.Reader.of_string html in
64+ let parsed = Html5rw.parse ~collect_errors:false reader in
65+66+ (* Walk both trees in parallel to build the mapping.
67+ Browser elements are in document order, and so are Html5rw nodes. *)
68+ let browser_elements = fold_elements (fun acc el -> el :: acc) [] root |> List.rev in
69+70+ (* Extract elements from Html5rw DOM in document order *)
71+ let rec extract_html5rw_elements acc node =
72+ if Html5rw.is_element node then
73+ let children = node.Html5rw.Dom.children in
74+ let acc = node :: acc in
75+ List.fold_left extract_html5rw_elements acc children
76+ else
77+ let children = node.Html5rw.Dom.children in
78+ List.fold_left extract_html5rw_elements acc children
79+ in
80+ let html5rw_elements = extract_html5rw_elements [] (Html5rw.root parsed) |> List.rev in
81+82+ (* Build the location map by matching elements *)
83+ let loc_to_el =
84+ let rec match_elements loc_map browser_els html5rw_els =
85+ match browser_els, html5rw_els with
86+ | [], _ | _, [] -> loc_map
87+ | b_el :: b_rest, h_el :: h_rest ->
88+ let b_tag = String.lowercase_ascii (Jstr.to_string (El.tag_name b_el)) in
89+ let h_tag = String.lowercase_ascii h_el.Html5rw.Dom.name in
90+ if b_tag = h_tag then
91+ (* Tags match - record the mapping if we have a location *)
92+ let loc_map =
93+ match h_el.Html5rw.Dom.location with
94+ | Some loc -> LocMap.add (loc.line, loc.column) b_el loc_map
95+ | None -> loc_map
96+ in
97+ match_elements loc_map b_rest h_rest
98+ else
99+ (* Tags don't match - try to resync by skipping one side *)
100+ (* This handles cases where browser might have implicit elements *)
101+ match_elements loc_map b_rest html5rw_els
102+ in
103+ match_elements LocMap.empty browser_elements html5rw_elements
104+ in
105+106+ { root; html_source = html; loc_to_el }, html
107+108+let find_by_location t ~line ~column =
109+ LocMap.find_opt (line, column) t.loc_to_el
110+111+let find_by_location_and_tag t ~line ~column ~tag =
112+ match LocMap.find_opt (line, column) t.loc_to_el with
113+ | Some el when String.lowercase_ascii (Jstr.to_string (El.tag_name el)) =
114+ String.lowercase_ascii tag ->
115+ Some el
116+ | _ -> None
117+118+let find_for_message t msg =
119+ (* Try to find element by location first *)
120+ match msg.Htmlrw_check.location with
121+ | Some loc ->
122+ (match msg.Htmlrw_check.element with
123+ | Some tag -> find_by_location_and_tag t ~line:loc.line ~column:loc.column ~tag
124+ | None -> find_by_location t ~line:loc.line ~column:loc.column)
125+ | None ->
126+ (* No location - try to find by element name if we have one *)
127+ match msg.Htmlrw_check.element with
128+ | Some tag ->
129+ (* Find first element with this tag *)
130+ let matches = filter_elements (fun el ->
131+ String.lowercase_ascii (Jstr.to_string (El.tag_name el)) =
132+ String.lowercase_ascii tag
133+ ) t.root in
134+ (match matches with
135+ | el :: _ -> Some el
136+ | [] -> None)
137+ | None -> None
138+139+let html_source t = t.html_source
140+141+let root_element t = t.root
142+143+let selector_path ?root el =
144+ let stop_at = match root with
145+ | Some r -> Some r
146+ | None -> None
147+ in
148+ let rec build_path el acc =
149+ (* Stop if we've reached the root *)
150+ let should_stop = match stop_at with
151+ | Some r -> el_equal el r
152+ | None -> String.lowercase_ascii (Jstr.to_string (El.tag_name el)) = "body"
153+ in
154+ if should_stop then
155+ acc
156+ else
157+ let tag = String.lowercase_ascii (Jstr.to_string (El.tag_name el)) in
158+ let segment =
159+ match El.parent el with
160+ | None -> tag
161+ | Some parent ->
162+ let siblings = El.children ~only_els:true parent in
163+ let same_tag = List.filter (fun sib ->
164+ String.lowercase_ascii (Jstr.to_string (El.tag_name sib)) = tag
165+ ) siblings in
166+ if List.length same_tag <= 1 then
167+ tag
168+ else
169+ let idx =
170+ let rec find_idx i = function
171+ | [] -> 1
172+ | sib :: rest ->
173+ if el_equal sib el then i
174+ else find_idx (i + 1) rest
175+ in
176+ find_idx 1 same_tag
177+ in
178+ Printf.sprintf "%s:nth-of-type(%d)" tag idx
179+ in
180+ let new_acc = segment :: acc in
181+ match El.parent el with
182+ | None -> new_acc
183+ | Some parent -> build_path parent new_acc
184+ in
185+ String.concat " > " (build_path el [])
186+187+let short_selector ?root el =
188+ (* Try ID first *)
189+ match El.at (Jstr.v "id") el with
190+ | Some id when not (Jstr.is_empty id) ->
191+ "#" ^ Jstr.to_string id
192+ | _ ->
193+ (* Try parent ID + short path *)
194+ let rec find_id_ancestor el depth =
195+ if depth > 3 then None
196+ else match El.parent el with
197+ | None -> None
198+ | Some parent ->
199+ match El.at (Jstr.v "id") parent with
200+ | Some id when not (Jstr.is_empty id) -> Some (parent, id)
201+ | _ -> find_id_ancestor parent (depth + 1)
202+ in
203+ match find_id_ancestor el 0 with
204+ | Some (ancestor, id) ->
205+ let path = selector_path ~root:ancestor el in
206+ "#" ^ Jstr.to_string id ^ " > " ^ path
207+ | None ->
208+ selector_path ?root el
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** Browser DOM utilities for mapping validation results to live elements.
7+8+ This module bridges the gap between HTML string validation (which produces
9+ line/column locations) and live DOM manipulation (which needs element
10+ references). It builds mappings between source positions and DOM elements
11+ by walking both the serialized HTML and the DOM tree in parallel. *)
12+13+14+(** {1 Element Mapping}
15+16+ When we validate [element.outerHTML], we get messages with line/column
17+ positions. To annotate the original DOM, we need to map those positions
18+ back to the live elements. *)
19+20+(** An element map associates source locations with DOM elements. *)
21+type t
22+23+(** Build an element map by walking a DOM element and its serialization.
24+25+ This function:
26+ 1. Serializes the element to HTML via [outerHTML]
27+ 2. Parses that HTML with Html5rw to get the parse tree with locations
28+ 3. Walks both trees in parallel to build a bidirectional mapping
29+30+ @param root The DOM element to map.
31+ @return The element map and the HTML source string. *)
32+val create : Brr.El.t -> t * string
33+34+(** Find the DOM element corresponding to a source location.
35+36+ @param line 1-indexed line number
37+ @param column 1-indexed column number
38+ @return The element at or containing that position, or [None]. *)
39+val find_by_location : t -> line:int -> column:int -> Brr.El.t option
40+41+(** Find the DOM element corresponding to an element name at a location.
42+43+ More precise than {!find_by_location} when the validator provides
44+ the element name along with the location.
45+46+ @param line 1-indexed line number
47+ @param column 1-indexed column number
48+ @param tag Element tag name (lowercase)
49+ @return The matching element, or [None]. *)
50+val find_by_location_and_tag :
51+ t -> line:int -> column:int -> tag:string -> Brr.El.t option
52+53+(** Find the DOM element for a validation message.
54+55+ Uses the message's location and element fields to find the best match.
56+ This is the primary function used by the annotation system. *)
57+val find_for_message : t -> Htmlrw_check.message -> Brr.El.t option
58+59+(** The HTML source string that was used to build this map. *)
60+val html_source : t -> string
61+62+(** The root element this map was built from. *)
63+val root_element : t -> Brr.El.t
64+65+66+(** {1 CSS Selector Generation} *)
67+68+(** Build a CSS selector path that uniquely identifies an element.
69+70+ The selector uses child combinators and [:nth-child] to be specific:
71+ ["body > div.main:nth-child(2) > p > img:nth-child(1)"]
72+73+ @param root Optional root element; selector will be relative to this.
74+ Defaults to [document.body].
75+ @param el The element to build a selector for.
76+ @return A CSS selector string. *)
77+val selector_path : ?root:Brr.El.t -> Brr.El.t -> string
78+79+(** Build a shorter selector using IDs and classes when available.
80+81+ Tries to find the shortest unique selector:
82+ 1. If element has an ID: ["#myId"]
83+ 2. If parent has ID: ["#parentId > .myClass"]
84+ 3. Falls back to full path from {!selector_path}
85+86+ @param root Optional root element.
87+ @param el The element to build a selector for. *)
88+val short_selector : ?root:Brr.El.t -> Brr.El.t -> string
89+90+91+(** {1 DOM Iteration} *)
92+93+(** Iterate over all elements in document order (depth-first pre-order). *)
94+val iter_elements : (Brr.El.t -> unit) -> Brr.El.t -> unit
95+96+(** Fold over all elements in document order. *)
97+val fold_elements : ('a -> Brr.El.t -> 'a) -> 'a -> Brr.El.t -> 'a
98+99+(** Find all elements matching a predicate. *)
100+val filter_elements : (Brr.El.t -> bool) -> Brr.El.t -> Brr.El.t list
101+102+103+(** {1 Serialization} *)
104+105+(** Get the outer HTML of an element.
106+107+ This is a wrapper around the browser's [outerHTML] property. *)
108+val outer_html : Brr.El.t -> string
109+110+(** Get the inner HTML of an element. *)
111+val inner_html : Brr.El.t -> string
+9
lib/js/htmlrw_js_main.ml
···000000000
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(* Entry point for the standalone JavaScript build.
7+ This registers the API on window.html5rw when the script loads. *)
8+9+let () = Htmlrw_js.register_global_api ()
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** Entry point for the standalone JavaScript build.
7+8+ This module is compiled to [htmlrw.js] and automatically registers
9+ the validation API on [window.html5rw] when loaded.
10+11+ {2 Browser Usage}
12+13+ {v
14+ <script src="htmlrw.js"></script>
15+ <script>
16+ // API is available immediately after loading
17+ const result = html5rw.validateElement(document.body);
18+19+ if (result.errorCount > 0) {
20+ console.log("Found", result.errorCount, "errors");
21+22+ // Show the warning panel
23+ html5rw.showPanel(result);
24+ }
25+ </script>
26+ v}
27+28+ {2 Module Bundler Usage}
29+30+ If using a bundler that supports CommonJS or ES modules, you can
31+ import the module instead:
32+33+ {v
34+ import { validateElement, showPanel } from './htmlrw.js';
35+36+ const result = validateElement(document.body);
37+ if (result.hasErrors) {
38+ showPanel(result);
39+ }
40+ v}
41+42+ The module exports are set up to work with both import styles.
43+44+ {2 API Reference}
45+46+ See {!Htmlrw_js} for the full API documentation. The JavaScript API
47+ mirrors the OCaml API with camelCase naming:
48+49+ - [html5rw.validateString(html)] - Validate an HTML string
50+ - [html5rw.validateElement(el)] - Validate a DOM element
51+ - [html5rw.validateAndAnnotate(el, config?)] - Validate and annotate
52+ - [html5rw.showPanel(result, config?)] - Show the warning panel
53+ - [html5rw.hidePanel()] - Hide the warning panel
54+ - [html5rw.clearAnnotations(el)] - Clear annotations from an element *)
55+56+(* This module has no values; its side effect is registering the API *)
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** Browser-specific types for HTML5rw JavaScript validation.
7+8+ Core validation types ({!Htmlrw_check.severity}, {!Htmlrw_check.message}, etc.)
9+ are reused from the main library. This module adds only the browser-specific
10+ types needed for DOM element references, annotation, and UI. *)
11+12+13+(** {1 Element References}
14+15+ Since we validate HTML strings but want to annotate live DOM elements,
16+ we need to map validation messages back to browser elements. *)
17+18+(** A reference to a DOM element, providing both programmatic access
19+ and a serializable CSS selector. *)
20+type element_ref = {
21+ element : Brr.El.t option;
22+ (** The live DOM element, if still attached to the document.
23+ May be [None] if validation was performed on a raw HTML string
24+ without a source element. *)
25+26+ selector : string;
27+ (** A CSS selector path that uniquely identifies this element.
28+ Format: ["body > div.container > p:nth-child(3) > img"]
29+ Useful for logging and re-finding elements. *)
30+}
31+32+(** A validation message paired with its DOM element reference. *)
33+type browser_message = {
34+ message : Htmlrw_check.message;
35+ (** The core validation message with severity, text, error code, etc. *)
36+37+ element_ref : element_ref option;
38+ (** Reference to the problematic DOM element, if identifiable.
39+ [None] for document-level issues like missing DOCTYPE. *)
40+}
41+42+(** Browser validation result. *)
43+type result = {
44+ messages : browser_message list;
45+ (** All validation messages with element references. *)
46+47+ core_result : Htmlrw_check.t;
48+ (** The underlying validation result from the core library.
49+ Use for access to {!Htmlrw_check.errors}, {!Htmlrw_check.has_errors}, etc. *)
50+51+ source_element : Brr.El.t option;
52+ (** The root element that was validated, if validation started from an element. *)
53+}
54+55+56+(** {1 Annotation Configuration} *)
57+58+(** Configuration for how warnings are displayed on annotated elements. *)
59+type annotation_config = {
60+ add_data_attrs : bool;
61+ (** Add [data-html5rw-*] attributes to elements:
62+ - [data-html5rw-severity]: ["error"], ["warning"], or ["info"]
63+ - [data-html5rw-message]: The warning message text
64+ - [data-html5rw-code]: The error code *)
65+66+ add_classes : bool;
67+ (** Add CSS classes: [html5rw-error], [html5rw-warning], [html5rw-info],
68+ and [html5rw-has-issues] on any element with warnings. *)
69+70+ show_tooltips : bool;
71+ (** Create tooltip overlays that appear on hover. *)
72+73+ tooltip_position : [ `Above | `Below | `Auto ];
74+ (** Tooltip position. [`Auto] chooses based on viewport. *)
75+76+ highlight_on_hover : bool;
77+ (** Highlight elements when hovering over warnings in the panel. *)
78+}
79+80+(** Default: all annotation features enabled, tooltips auto-positioned. *)
81+val default_annotation_config : annotation_config
82+83+84+(** {1 Panel Configuration} *)
85+86+(** Configuration for the floating warning panel. *)
87+type panel_config = {
88+ initial_position : [ `TopRight | `TopLeft | `BottomRight | `BottomLeft | `Custom of int * int ];
89+ (** Where the panel appears initially. *)
90+91+ draggable : bool;
92+ resizable : bool;
93+ collapsible : bool;
94+ start_collapsed : bool;
95+96+ max_height : int option;
97+ (** Maximum height in pixels before scrolling. *)
98+99+ group_by_severity : bool;
100+ (** Group warnings: errors first, then warnings, then info. *)
101+102+ click_to_highlight : bool;
103+ (** Clicking a warning scrolls to and highlights the element. *)
104+105+ show_selector_path : bool;
106+ (** Show the CSS selector path in each warning row. *)
107+108+ theme : [ `Light | `Dark | `Auto ];
109+ (** Color scheme. [`Auto] follows [prefers-color-scheme]. *)
110+}
111+112+(** Default panel configuration. *)
113+val default_panel_config : panel_config
114+115+116+(** {1 Conversions} *)
117+118+(** Build a CSS selector path for an element. *)
119+val selector_of_element : Brr.El.t -> string
120+121+(** Convert a browser message to a JavaScript object. *)
122+val browser_message_to_jv : browser_message -> Jv.t
123+124+(** Convert a result to a JavaScript object. *)
125+val result_to_jv : result -> Jv.t
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3+ SPDX-License-Identifier: MIT
4+ ---------------------------------------------------------------------------*)
5+6+(** Floating warning panel UI.
7+8+ This module creates and manages a draggable, floating panel that displays
9+ validation warnings. The panel supports:
10+ - Grouping by severity (errors first)
11+ - Click-to-navigate to problematic elements
12+ - Collapse/expand functionality
13+ - Light/dark themes *)
14+15+open Htmlrw_js_types
16+17+18+(** {1 Panel Management} *)
19+20+(** The warning panel. *)
21+type t
22+23+(** Create and display a warning panel.
24+25+ The panel is appended to [document.body] and positioned according
26+ to the configuration.
27+28+ @param config Panel configuration.
29+ @param result Validation result to display. *)
30+val create : config:panel_config -> result -> t
31+32+(** Update the panel with new validation results.
33+34+ Use this to re-validate and refresh the panel without destroying it. *)
35+val update : t -> result -> unit
36+37+(** Show the panel if hidden. *)
38+val show : t -> unit
39+40+(** Hide the panel (but keep it in the DOM). *)
41+val hide : t -> unit
42+43+(** Remove the panel from the DOM entirely. *)
44+val destroy : t -> unit
45+46+(** Check if the panel is currently visible. *)
47+val is_visible : t -> bool
48+49+(** Check if the panel is currently collapsed. *)
50+val is_collapsed : t -> bool
51+52+53+(** {1 Panel State} *)
54+55+(** Collapse the panel to just show the summary badge. *)
56+val collapse : t -> unit
57+58+(** Expand the panel to show the full warning list. *)
59+val expand : t -> unit
60+61+(** Toggle collapsed state. *)
62+val toggle_collapsed : t -> unit
63+64+(** Get the current position of the panel. *)
65+val position : t -> int * int
66+67+(** Move the panel to a new position. *)
68+val set_position : t -> int -> int -> unit
69+70+71+(** {1 Interaction} *)
72+73+(** Scroll to and highlight an element from a warning row.
74+75+ This is called internally when clicking a warning, but can be
76+ invoked programmatically. *)
77+val navigate_to_element : t -> browser_message -> unit
78+79+(** Get the currently highlighted element, if any. *)
80+val highlighted_element : t -> Brr.El.t option
81+82+(** Clear the current highlight. *)
83+val clear_highlight : t -> unit
84+85+86+(** {1 Event Callbacks}
87+88+ Register callbacks for panel events. *)
89+90+(** Called when a warning row is clicked. *)
91+val on_warning_click : t -> (browser_message -> unit) -> unit
92+93+(** Called when the panel is collapsed or expanded. *)
94+val on_collapse_toggle : t -> (bool -> unit) -> unit
95+96+(** Called when the panel is closed. *)
97+val on_close : t -> (unit -> unit) -> unit
98+99+(** Called when the panel is dragged to a new position. *)
100+val on_move : t -> (int * int -> unit) -> unit
101+102+103+(** {1 Global Panel State}
104+105+ For convenience, there's a single "current" panel that the
106+ JavaScript API manages. *)
107+108+(** Get the current panel, if one exists. *)
109+val current : unit -> t option
110+111+(** Hide and destroy the current panel. *)
112+val hide_current : unit -> unit
113+114+115+(** {1 Panel Elements}
116+117+ Access to the panel's DOM structure for custom styling. *)
118+119+(** The root panel element. *)
120+val root_element : t -> Brr.El.t
121+122+(** The header element (contains title and controls). *)
123+val header_element : t -> Brr.El.t
124+125+(** The content element (contains warning list). *)
126+val content_element : t -> Brr.El.t
127+128+(** The summary badge element (shown when collapsed). *)
129+val badge_element : t -> Brr.El.t
130+131+132+(** {1 CSS Classes}
133+134+ Classes used by the panel for custom styling. *)
135+136+module Css_class : sig
137+ val panel : Jstr.t
138+ val panel_header : Jstr.t
139+ val panel_content : Jstr.t
140+ val panel_collapsed : Jstr.t
141+ val panel_dragging : Jstr.t
142+ val warning_list : Jstr.t
143+ val warning_row : Jstr.t
144+ val warning_row_error : Jstr.t
145+ val warning_row_warning : Jstr.t
146+ val warning_row_info : Jstr.t
147+ val severity_badge : Jstr.t
148+ val message_text : Jstr.t
149+ val selector_path : Jstr.t
150+ val collapse_btn : Jstr.t
151+ val close_btn : Jstr.t
152+ val summary_badge : Jstr.t
153+ val error_count : Jstr.t
154+ val warning_count : Jstr.t
155+ val theme_light : Jstr.t
156+ val theme_dark : Jstr.t
157+end
158+159+160+(** {1 CSS Injection} *)
161+162+(** Inject default CSS styles for the panel.
163+164+ Styles include layout, colors, shadows, and animations.
165+ The styles are scoped to the panel's CSS classes.
166+167+ @param theme Color scheme to use.
168+ @return The injected style element. *)
169+val inject_default_styles : theme:[ `Light | `Dark | `Auto ] -> Brr.El.t