(** Generic site building blocks. This module is the only place in the view layer that names HTML elements. It knows nothing about the application domain — no Git, repositories, or ogit routes — so the same vocabulary would serve any static-first web application: every function takes plain strings and already-built nodes. {2 Conventions} - {b Semantic first.} Each block picks the most meaningful element available ([nav], [header], [time], [dl], [details]) rather than a [div] with a class. Callers choose blocks by meaning, not by appearance. - {b Classes are a contract.} Blocks emit a fixed vocabulary of class names — [tree-dir], [tree-toggle], [line-anchor], [pagination-btn] and so on — which a stylesheet is expected to implement. Those names are structural, never domain-specific: a "tree" here is any hierarchical list, not a file tree in particular. Optional [?class_] arguments add a modifier {i alongside} the base class rather than replacing it. - {b No scripting.} Interactive blocks ({!val-disclosure}, {!val-css_toggle}) rely on native HTML and CSS, so pages stay usable with JavaScript disabled. - {b Accessibility is not optional.} Where a block can only be used correctly with an accessible name, that name is a required argument rather than an optional one — see {!val-navigation} and {!val-dismissible}. Nothing here performs I/O. The attribute plumbing that assembles these blocks is deliberately not exported: callers compose blocks, they do not assemble attributes. *) type node = Dream_html.node (** A rendered fragment. Exposed so callers can annotate lists of children without opening [Dream_html] themselves. *) val classes : string list -> string (** Join class names, dropping empty ones. Lets callers pass a modifier without having to manage separators or risk a stray leading space. *) (** {1 Text and grouping} *) val nothing : node (** Renders no markup. Use for absent optional content. *) val text : string -> node (** Escaped text. *) val group : node list -> node (** Several nodes where one is expected, without introducing a wrapper element. *) (** {1 Inline} *) val inline : ?class_:string -> ?decorative:bool -> node list -> node (** An inline run of text or nodes. @param decorative hides the span from assistive technology, for glyphs that repeat information already available as text. *) val inline_text : ?class_:string -> ?decorative:bool -> string -> node (** {!val-inline} around a single string. *) (** {1 Links} *) val link : ?id:string -> ?class_:string -> ?label:string -> href:string -> node list -> node (** A hyperlink. @param label an accessible name, for links whose visible text is not descriptive on its own. *) val text_link : ?id:string -> ?class_:string -> ?label:string -> href:string -> string -> node (** {!link} around a single string. *) (** {1 Images} *) val image : ?class_:string -> ?alt:string -> src:string -> unit -> node (** @param alt omit for decorative images; the block then marks itself presentational so screen readers skip it. *) (** {1 Blocks} *) val block : ?id:string -> ?class_:string -> node list -> node (** A generic grouping box. Reach for {!region} or one of the page landmarks first; this is for layout wrappers that carry no meaning of their own. *) val region : ?id:string -> ?class_:string -> node list -> node (** A self-contained part of a page. *) val paragraph : ?class_:string -> node list -> node val paragraph_text : ?class_:string -> string -> node val heading : ?id:string -> ?level:int -> ?class_:string -> node list -> node (** A heading. [level] follows the document outline: 1 for the page's subject, 2 and 3 for nested sections. Skipping levels breaks screen-reader navigation, so pass the level that matches the structure rather than the one that looks right. Levels outside 1–6 clamp to 6. [id] provides a local fragment target. *) val code_block : ?class_:string -> node list -> node (** A preformatted code block without line numbers. The children can be escaped text or server-rendered syntax-highlight spans. *) (** {1 Lists} *) val items : ?id:string -> ?class_:string -> node list -> node (** An unordered list wrapping already-built {!item} nodes. *) val ordered_items : ?id:string -> ?class_:string -> node list -> node (** An ordered list wrapping already-built {!item} nodes. *) val item : ?class_:string -> ?current:bool -> node list -> node (** A list entry. @param current marks the entry as the one matching the current page, for navigation lists. *) val items_of : ?id:string -> ?class_:string -> ('a -> node) -> 'a list -> node (** A list built from values, saving callers a [List.map]. *) val code_inline : ?class_:string -> string -> node (** An inline code fragment. *) (** {1 Badges} *) val badge : ?base_class:string -> ?variant:string -> ?href:string -> string -> node (** A small rounded label. [variant] is appended to the base class as [ -] so a stylesheet can colour each kind. [href] turns the label's text into a link while leaving the badge itself inert. *) (** {1 Time} *) val timestamp : machine:string -> string -> node (** A machine-readable timestamp: [machine] fills the [datetime] attribute, the positional argument is the visible text. *) (** {1 Definition lists} *) val definitions : ?class_:string -> (string * node list) list -> node (** Term/description pairs, for metadata panels. *) (** {1 Disclosure} *) val chevron : ?class_:string -> unit -> node (** Decorative open/close indicator, rotated by CSS from the enclosing [details]. It carries no textual meaning, so it is hidden from assistive technology. *) val disclosure : ?id:string -> ?class_:string -> ?expanded:bool -> ?summary_class:string -> summary:node list -> node list -> node (** A native disclosure widget: [details] wrapping a clickable [summary] and its panel. No JavaScript involved. @param expanded renders the panel open on load. @param summary the always-visible header contents. *) val css_toggle : id:string -> toggle_class:string -> control_class:string -> label:string -> glyph:string -> unit -> node (** A CSS-only toggle: a visually hidden checkbox paired with a [label] acting as its control. Lets stylesheets reveal and collapse content without scripting. @param label the accessible name of the control, whose [glyph] has none. *) (** {1 Table of contents} *) type toc_entry (** One entry in a table of contents. Build with {!val-toc_entry}. Entries may contain nested children to represent subheading hierarchy. *) val toc_entry : ?children:toc_entry list -> href:string -> string -> toc_entry (** A TOC entry linking to a fragment. @param children nested sub-entries displayed as an indented list beneath this entry. *) val toc : ?class_:string -> title:string -> toc_entry list -> node (** A collapsible table of contents with support for nested sub-entries. Renders as a disclosure widget with classes [toc], [toc-summary], and [toc-list]. Nested children produce nested [toc-list] elements for semantic indentation. Returns {!nothing} when the list has fewer than two entries. @param class_ an additional modifier alongside the base [toc] class. *) (** {1 Trees} A "tree" is any hierarchical list: rows that either stand alone or expand to reveal nested rows. Compose these into {!items}. *) val tree_leaf : ?modifier:string -> href:string -> string -> node (** A leaf row. @param modifier a class added alongside the base [tree-file] class. *) val tree_branch : ?modifier:string -> ?expanded:bool -> href:string -> string -> node list -> node (** A branch row. Renders a disclosure inside the list item: the summary holds a chevron and a link, the panel holds the nested list. Clicking the summary padding or the chevron toggles; clicking the link navigates. Every nested collection on a site therefore gets the same keyboard and pointer behaviour. @param modifier a class added alongside the base [tree-dir] class. @param expanded renders the nested list open on load. *) val tree_overflow : ?class_:string -> href:string -> string -> node (** A row standing in for entries omitted from a truncated list. *) (** {1 Breadcrumbs} *) type crumb (** One step in a trail. Build with {!val-crumb}. *) val crumb : ?href:string -> string -> crumb (** A trail step. Without an href it renders as plain text, which is how the current location should be shown. *) val breadcrumb : ?id:string -> ?class_:string -> ?link_class:string -> ?separator_class:string -> ?separator_decorative:bool -> separator:string -> crumb list -> node (** A trail of links joined by a separator. @param separator_decorative hides the separators from assistive technology. Appropriate when the trail already reads as a list of links; leave it off when the separator carries meaning, such as a path delimiter worth reading aloud. *) (** {1 Navigation} *) type nav_link (** A destination in a navigation list. Build with {!val-nav_link}. *) val nav_link : ?current:bool -> href:string -> string -> nav_link val navigation : ?id:string -> ?class_:string -> label:string -> node list -> node (** A navigation landmark. [label] is required because it names the landmark for assistive technology, which matters as soon as a page has more than one. *) val nav_links : ?id:string -> ?class_:string -> ?item_class:string -> nav_link list -> node (** A list of navigation links; the current page's item carries [aria-current="page"]. *) (** {1 Toolbars} *) val toolbar : ?id:string -> ?class_:string -> ?label:string -> node list -> node (** A bar of controls acting on the current page. An empty toolbar renders nothing, so layout offsets that depend on its presence stay consistent. *) val button_link : ?class_:string -> ?label:string -> href:string -> string -> node (** A link styled as a toolbar button. Still a link, not a [button], because it navigates rather than acting on the current page — which keeps middle-click and "open in new tab" working. *) val dismissible : ?class_:string -> ?dismiss_class:string -> value_class:string -> dismiss_href:string -> dismiss_label:string -> string -> node (** An active filter together with a control that removes it. @param value_class styles the displayed value. @param dismiss_label accessible name of the remove control, required because its glyph conveys nothing on its own. *) (** {1 Pagination} *) val pagination : ?label:string -> ?previous_text:string -> ?next_text:string -> ?previous_label:string -> ?next_label:string -> ?previous_href:string -> ?next_href:string -> int -> node (** Previous/next controls around a page number. A missing neighbour — an absent [previous_href] or [next_href] — renders as an inert, aria-hidden placeholder rather than disappearing, so the controls keep their position between pages. The glyphs are decorative; [previous_label] and [next_label] carry the accessible names. *) (** {1 Code} *) val code_listing : ?id:string -> ?class_:string -> ?anchor_prefix:string -> string -> node (** A line-numbered listing of source text. Every line gets a stable anchor so single lines can be linked and highlighted. [anchor_prefix] namespaces those anchors, which is required when one page shows more than one listing. Content is emitted verbatim as plain text with no syntax colouring. For highlighted output, use {!highlighted_code_listing} instead. *) val highlighted_code_listing : ?id:string -> ?class_:string -> ?anchor_prefix:string -> node list list -> node (** A line-numbered listing of pre-highlighted source code. Like {!code_listing} but accepts lines already tokenized into styled spans (e.g. from {!Highlight.highlight}). Each inner list represents one line's worth of nodes; the function adds line numbers and anchors in the same grid layout as [code_listing]. *) (** {1 Diffs} *) (** A viewer for line-oriented change sets. The data types are deliberately plain so any producer of diffs can feed them. *) module Diff : sig type change = Unchanged | Added | Removed type line = { before : string; (** line number in the old revision, or [""] *) after : string; (** line number in the new revision, or [""] *) change : change; content : string; } type section = { section_heading : string; lines : line list } type file = { path : string; detail : string; (** provenance line, e.g. revision identifiers *) sections : section list; note : string option; (** shown instead of sections, e.g. binary files *) } val view : empty_message:string -> file list -> node list (** Render a change set, or a single paragraph carrying [empty_message] when there is nothing to show. *) end (** {1 Document scaffolding} Two families, kept apart by prefix. The [document_] functions build the envelope a browser reads — [html], [head], [body] — and the [page_] functions build the landmarks a reader sees inside it. [document_head] and [page_banner] are the pair most easily confused: the first is metadata, the second is the visible masthead. *) val meta_viewport : node (** Opts the page into responsive layout. Without it mobile browsers assume a desktop-width viewport and scale the page down. *) val stylesheet : string -> node val icon : ?media_type:string -> string -> node val deferred_script : string -> node (** An external script that does not block rendering. *) val inline_script : string -> node (** Inline behaviour. Reserved for progressive enhancement: pages must stay usable when it does not run. *) val skip_link : href:string -> string -> node (** A link that jumps past repeated navigation, revealed on focus. Expected on every page for keyboard users. *) (** {2 The document envelope} *) val document_head : title:string -> node list -> node (** The [head] element: title, metadata and asset links. [title] comes first so it cannot be forgotten. *) val document_body : ?class_:string -> node list -> node (** The [body] element. *) val document : ?lang:string -> head:node -> body:node -> unit -> node (** A complete document. [lang] defaults to ["en"]; set it so screen readers pick the right pronunciation. *) (** {2 Landmarks within the page} *) val page_banner : ?id:string -> ?class_:string -> node list -> node (** The [header] element introducing the page — its masthead. Named for the ARIA landmark it maps to, and to keep it distinct from {!val-document_head}. *) val page_content : ?id:string -> ?class_:string -> node list -> node (** The [main] element: the content unique to this page, which the skip link targets. At most one per document. *) val page_footer : ?class_:string -> node list -> node (** The [footer] element closing the page. *) (** {1 Responses} *) val respond : ?status:[< Dream.status ] -> node -> Dream.response Dream.promise (** Send a rendered document as an HTTP response. *)