1
+
Added:
(* -*- mode: tuareg; -*- *)
2
+
Added:
3
+
Added:
(** Generic site building blocks.
4
+
Added:
5
+
Added:
This module is the only place in the view layer that names HTML elements. It
6
+
Added:
knows nothing about the application domain — no Git, repositories, or ogit
7
+
Added:
routes — so the same vocabulary would serve any static-first web
8
+
Added:
application: every function takes plain strings and already-built nodes.
9
+
Added:
10
+
Added:
{2 Conventions}
11
+
Added:
12
+
Added:
- {b Semantic first.} Each block picks the most meaningful element available
13
+
Added:
([nav], [header], [time], [dl], [details]) rather than a [div] with a
14
+
Added:
class. Callers choose blocks by meaning, not by appearance.
15
+
Added:
- {b Classes are a contract.} Blocks emit a fixed vocabulary of class names
16
+
Added:
— [tree-dir], [tree-toggle], [line-anchor], [pagination-btn] and so on —
17
+
Added:
which a stylesheet is expected to implement. Those names are structural,
18
+
Added:
never domain-specific: a "tree" here is any hierarchical list, not a file
19
+
Added:
tree in particular. Optional [?class_] arguments add a modifier
20
+
Added:
{i alongside} the base class rather than replacing it.
21
+
Added:
- {b No scripting.} Interactive blocks ({!disclosure}, {!css_toggle}) rely
22
+
Added:
on native HTML and CSS, so pages stay usable with JavaScript disabled.
23
+
Added:
- {b Accessibility is not optional.} Where a block can only be used
24
+
Added:
correctly with an accessible name, that name is a required argument rather
25
+
Added:
than an optional one — see {!navigation} and {!dismissible}.
26
+
Added:
27
+
Added:
Nothing here performs I/O. The attribute plumbing that assembles these
28
+
Added:
blocks is deliberately not exported: callers compose blocks, they do not
29
+
Added:
assemble attributes. *)
30
+
Added:
31
+
Added:
type node = Dream_html.node
32
+
Added:
(** A rendered fragment. Exposed so callers can annotate lists of children
33
+
Added:
without opening [Dream_html] themselves. *)
34
+
Added:
35
+
Added:
val classes : string list -> string
36
+
Added:
(** Join class names, dropping empty ones. Lets callers pass a modifier without
37
+
Added:
having to manage separators or risk a stray leading space. *)
38
+
Added:
39
+
Added:
(** {1 Text and grouping} *)
40
+
Added:
41
+
Added:
val nothing : node
42
+
Added:
(** Renders no markup. Use for absent optional content. *)
43
+
Added:
44
+
Added:
val text : string -> node
45
+
Added:
(** Escaped text. *)
46
+
Added:
47
+
Added:
val group : node list -> node
48
+
Added:
(** Several nodes where one is expected, without introducing a wrapper element.
49
+
Added:
*)
50
+
Added:
51
+
Added:
(** {1 Inline} *)
52
+
Added:
53
+
Added:
val inline : ?class_:string -> ?decorative:bool -> node list -> node
54
+
Added:
(** An inline run of text or nodes.
55
+
Added:
56
+
Added:
@param decorative
57
+
Added:
hides the span from assistive technology, for glyphs that repeat
58
+
Added:
information already available as text. *)
59
+
Added:
60
+
Added:
val inline_text : ?class_:string -> ?decorative:bool -> string -> node
61
+
Added:
(** {!inline} around a single string. *)
62
+
Added:
63
+
Added:
(** {1 Links} *)
64
+
Added:
65
+
Added:
val link :
66
+
Added:
?id:string ->
67
+
Added:
?class_:string ->
68
+
Added:
?label:string ->
69
+
Added:
href:string ->
70
+
Added:
node list ->
71
+
Added:
node
72
+
Added:
(** A hyperlink.
73
+
Added:
74
+
Added:
@param label
75
+
Added:
an accessible name, for links whose visible text is not descriptive on its
76
+
Added:
own. *)
77
+
Added:
78
+
Added:
val text_link :
79
+
Added:
?id:string -> ?class_:string -> ?label:string -> href:string -> string -> node
80
+
Added:
(** {!link} around a single string. *)
81
+
Added:
82
+
Added:
(** {1 Images} *)
83
+
Added:
84
+
Added:
val image : ?class_:string -> ?alt:string -> src:string -> unit -> node
85
+
Added:
(** @param alt
86
+
Added:
omit for decorative images; the block then marks itself presentational so
87
+
Added:
screen readers skip it. *)
88
+
Added:
89
+
Added:
(** {1 Blocks} *)
90
+
Added:
91
+
Added:
val block : ?id:string -> ?class_:string -> node list -> node
92
+
Added:
(** A generic grouping box. Reach for {!region} or one of the page landmarks
93
+
Added:
first; this is for layout wrappers that carry no meaning of their own. *)
94
+
Added:
95
+
Added:
val region : ?id:string -> ?class_:string -> node list -> node
96
+
Added:
(** A self-contained part of a page. *)
97
+
Added:
98
+
Added:
val paragraph : ?class_:string -> node list -> node
99
+
Added:
val paragraph_text : ?class_:string -> string -> node
100
+
Added:
101
+
Added:
val heading : ?level:int -> ?class_:string -> node list -> node
102
+
Added:
(** A heading. [level] follows the document outline: 1 for the page's subject, 2
103
+
Added:
and 3 for nested sections. Skipping levels breaks screen-reader navigation,
104
+
Added:
so pass the level that matches the structure rather than the one that looks
105
+
Added:
right. Levels outside 1–6 clamp to 6. *)
106
+
Added:
107
+
Added:
(** {1 Lists} *)
108
+
Added:
109
+
Added:
val items : ?id:string -> ?class_:string -> node list -> node
110
+
Added:
(** An unordered list wrapping already-built {!item} nodes. *)
111
+
Added:
112
+
Added:
val item : ?class_:string -> ?current:bool -> node list -> node
113
+
Added:
(** A list entry.
114
+
Added:
115
+
Added:
@param current
116
+
Added:
marks the entry as the one matching the current page, for navigation
117
+
Added:
lists. *)
118
+
Added:
119
+
Added:
val items_of : ?id:string -> ?class_:string -> ('a -> node) -> 'a list -> node
120
+
Added:
(** A list built from values, saving callers a [List.map]. *)
121
+
Added:
122
+
Added:
(** {1 Badges} *)
123
+
Added:
124
+
Added:
val badge :
125
+
Added:
?base_class:string -> ?variant:string -> ?href:string -> string -> node
126
+
Added:
(** A small rounded label. [variant] is appended to the base class as
127
+
Added:
[<base> <base>-<variant>] so a stylesheet can colour each kind. [href] turns
128
+
Added:
the label's text into a link while leaving the badge itself inert. *)
129
+
Added:
130
+
Added:
(** {1 Time} *)
131
+
Added:
132
+
Added:
val timestamp : machine:string -> string -> node
133
+
Added:
(** A machine-readable timestamp: [machine] fills the [datetime] attribute, the
134
+
Added:
positional argument is the visible text. *)
135
+
Added:
136
+
Added:
(** {1 Definition lists} *)
137
+
Added:
138
+
Added:
val definitions : ?class_:string -> (string * node list) list -> node
139
+
Added:
(** Term/description pairs, for metadata panels. *)
140
+
Added:
141
+
Added:
(** {1 Disclosure} *)
142
+
Added:
143
+
Added:
val chevron : ?class_:string -> unit -> node
144
+
Added:
(** Decorative open/close indicator, rotated by CSS from the enclosing
145
+
Added:
[details]. It carries no textual meaning, so it is hidden from assistive
146
+
Added:
technology. *)
147
+
Added:
148
+
Added:
val disclosure :
149
+
Added:
?class_:string ->
150
+
Added:
?expanded:bool ->
151
+
Added:
?summary_class:string ->
152
+
Added:
summary:node list ->
153
+
Added:
node list ->
154
+
Added:
node
155
+
Added:
(** A native disclosure widget: [details] wrapping a clickable [summary] and its
156
+
Added:
panel. No JavaScript involved.
157
+
Added:
158
+
Added:
@param expanded renders the panel open on load.
159
+
Added:
@param summary the always-visible header contents. *)
160
+
Added:
161
+
Added:
val css_toggle :
162
+
Added:
id:string ->
163
+
Added:
toggle_class:string ->
164
+
Added:
control_class:string ->
165
+
Added:
label:string ->
166
+
Added:
glyph:string ->
167
+
Added:
unit ->
168
+
Added:
node
169
+
Added:
(** A CSS-only toggle: a visually hidden checkbox paired with a [label] acting
170
+
Added:
as its control. Lets stylesheets reveal and collapse content without
171
+
Added:
scripting.
172
+
Added:
173
+
Added:
@param label the accessible name of the control, whose [glyph] has none. *)
174
+
Added:
175
+
Added:
(** {1 Trees}
176
+
Added:
177
+
Added:
A "tree" is any hierarchical list: rows that either stand alone or expand to
178
+
Added:
reveal nested rows. Compose these into {!items}. *)
179
+
Added:
180
+
Added:
val tree_leaf : ?modifier:string -> href:string -> string -> node
181
+
Added:
(** A leaf row.
182
+
Added:
183
+
Added:
@param modifier a class added alongside the base [tree-file] class. *)
184
+
Added:
185
+
Added:
val tree_branch :
186
+
Added:
?modifier:string ->
187
+
Added:
?expanded:bool ->
188
+
Added:
href:string ->
189
+
Added:
string ->
190
+
Added:
node list ->
191
+
Added:
node
192
+
Added:
(** A branch row.
193
+
Added:
194
+
Added:
Renders a disclosure inside the list item: the summary holds a chevron and a
195
+
Added:
link, the panel holds the nested list. Clicking the summary padding or the
196
+
Added:
chevron toggles; clicking the link navigates. Every nested collection on a
197
+
Added:
site therefore gets the same keyboard and pointer behaviour.
198
+
Added:
199
+
Added:
@param modifier a class added alongside the base [tree-dir] class.
200
+
Added:
@param expanded renders the nested list open on load. *)
201
+
Added:
202
+
Added:
val tree_more : ?class_:string -> href:string -> string -> node
203
+
Added:
(** A row standing in for entries omitted from a truncated list. *)
204
+
Added:
205
+
Added:
(** {1 Breadcrumbs} *)
206
+
Added:
207
+
Added:
type crumb
208
+
Added:
(** One step in a trail. Build with {!crumb}. *)
209
+
Added:
210
+
Added:
val crumb : ?href:string -> string -> crumb
211
+
Added:
(** A trail step. Without an href it renders as plain text, which is how the
212
+
Added:
current location should be shown. *)
213
+
Added:
214
+
Added:
val breadcrumb :
215
+
Added:
?id:string ->
216
+
Added:
?class_:string ->
217
+
Added:
?link_class:string ->
218
+
Added:
?separator_class:string ->
219
+
Added:
?separator_decorative:bool ->
220
+
Added:
separator:string ->
221
+
Added:
crumb list ->
222
+
Added:
node
223
+
Added:
(** A trail of links joined by a separator.
224
+
Added:
225
+
Added:
@param separator_decorative
226
+
Added:
hides the separators from assistive technology. Appropriate when the trail
227
+
Added:
already reads as a list of links; leave it off when the separator carries
228
+
Added:
meaning, such as a path delimiter worth reading aloud. *)
229
+
Added:
230
+
Added:
(** {1 Navigation} *)
231
+
Added:
232
+
Added:
type nav_link
233
+
Added:
(** A destination in a navigation list. Build with {!nav_link}. *)
234
+
Added:
235
+
Added:
val nav_link : ?current:bool -> href:string -> string -> nav_link
236
+
Added:
237
+
Added:
val navigation :
238
+
Added:
?id:string -> ?class_:string -> label:string -> node list -> node
239
+
Added:
(** A navigation landmark. [label] is required because it names the landmark for
240
+
Added:
assistive technology, which matters as soon as a page has more than one. *)
241
+
Added:
242
+
Added:
val nav_links :
243
+
Added:
?id:string -> ?class_:string -> ?item_class:string -> nav_link list -> node
244
+
Added:
(** A list of navigation links; the current page's item carries
245
+
Added:
[aria-current="page"]. *)
246
+
Added:
247
+
Added:
(** {1 Toolbars} *)
248
+
Added:
249
+
Added:
val toolbar : ?id:string -> ?class_:string -> ?label:string -> node list -> node
250
+
Added:
(** A bar of controls acting on the current page. An empty toolbar renders
251
+
Added:
nothing, so layout offsets that depend on its presence stay consistent. *)
252
+
Added:
253
+
Added:
val button_link :
254
+
Added:
?class_:string -> ?label:string -> href:string -> string -> node
255
+
Added:
(** A link styled as a toolbar button. Still a link, not a [button], because it
256
+
Added:
navigates rather than acting on the current page — which keeps middle-click
257
+
Added:
and "open in new tab" working. *)
258
+
Added:
259
+
Added:
val dismissible :
260
+
Added:
?class_:string ->
261
+
Added:
?dismiss_class:string ->
262
+
Added:
value_class:string ->
263
+
Added:
dismiss_href:string ->
264
+
Added:
dismiss_label:string ->
265
+
Added:
string ->
266
+
Added:
node
267
+
Added:
(** An active filter together with a control that removes it.
268
+
Added:
269
+
Added:
@param value_class styles the displayed value.
270
+
Added:
@param dismiss_label
271
+
Added:
accessible name of the remove control, required because its glyph conveys
272
+
Added:
nothing on its own. *)
273
+
Added:
274
+
Added:
(** {1 Pagination} *)
275
+
Added:
276
+
Added:
val pagination :
277
+
Added:
?label:string ->
278
+
Added:
?previous_text:string ->
279
+
Added:
?next_text:string ->
280
+
Added:
?previous_label:string ->
281
+
Added:
?next_label:string ->
282
+
Added:
?previous_href:string ->
283
+
Added:
?next_href:string ->
284
+
Added:
int ->
285
+
Added:
node
286
+
Added:
(** Previous/next controls around a page number.
287
+
Added:
288
+
Added:
A missing neighbour — an absent [previous_href] or [next_href] — renders as
289
+
Added:
an inert, aria-hidden placeholder rather than disappearing, so the controls
290
+
Added:
keep their position between pages.
291
+
Added:
292
+
Added:
The glyphs are decorative; [previous_label] and [next_label] carry the
293
+
Added:
accessible names. *)
294
+
Added:
295
+
Added:
(** {1 Code} *)
296
+
Added:
297
+
Added:
val code_listing :
298
+
Added:
?id:string -> ?class_:string -> ?anchor_prefix:string -> string -> node
299
+
Added:
(** A line-numbered listing of source text.
300
+
Added:
301
+
Added:
Every line gets a stable anchor so single lines can be linked and
302
+
Added:
highlighted. [anchor_prefix] namespaces those anchors, which is required
303
+
Added:
when one page shows more than one listing.
304
+
Added:
305
+
Added:
Content is emitted verbatim as text; syntax colouring, if any, is a
306
+
Added:
progressive enhancement layered on top. *)
307
+
Added:
308
+
Added:
(** {1 Diffs} *)
309
+
Added:
310
+
Added:
(** A viewer for line-oriented change sets. The data types are deliberately
311
+
Added:
plain so any producer of diffs can feed them. *)
312
+
Added:
module Diff : sig
313
+
Added:
type change = Unchanged | Added | Removed
314
+
Added:
315
+
Added:
type line = {
316
+
Added:
before : string; (** line number in the old revision, or [""] *)
317
+
Added:
after : string; (** line number in the new revision, or [""] *)
318
+
Added:
change : change;
319
+
Added:
content : string;
320
+
Added:
}
321
+
Added:
322
+
Added:
type section = { section_heading : string; lines : line list }
323
+
Added:
324
+
Added:
type file = {
325
+
Added:
path : string;
326
+
Added:
detail : string; (** provenance line, e.g. revision identifiers *)
327
+
Added:
sections : section list;
328
+
Added:
note : string option; (** shown instead of sections, e.g. binary files *)
329
+
Added:
}
330
+
Added:
331
+
Added:
val view : empty_message:string -> file list -> node list
332
+
Added:
(** Render a change set, or a single paragraph carrying [empty_message] when
333
+
Added:
there is nothing to show. *)
334
+
Added:
end
335
+
Added:
336
+
Added:
(** {1 Document scaffolding} *)
337
+
Added:
338
+
Added:
val meta_viewport : node
339
+
Added:
(** Opts the page into responsive layout. Without it mobile browsers assume a
340
+
Added:
desktop-width viewport and scale the page down. *)
341
+
Added:
342
+
Added:
val stylesheet : string -> node
343
+
Added:
val icon : ?media_type:string -> string -> node
344
+
Added:
345
+
Added:
val deferred_script : string -> node
346
+
Added:
(** An external script that does not block rendering. *)
347
+
Added:
348
+
Added:
val inline_script : string -> node
349
+
Added:
(** Inline behaviour. Reserved for progressive enhancement: pages must stay
350
+
Added:
usable when it does not run. *)
351
+
Added:
352
+
Added:
val document_head : title:string -> node list -> node
353
+
Added:
(** The document head. [title] comes first so it cannot be forgotten. *)
354
+
Added:
355
+
Added:
val skip_link : href:string -> string -> node
356
+
Added:
(** A link that jumps past repeated navigation, revealed on focus. Expected on
357
+
Added:
every page for keyboard users. *)
358
+
Added:
359
+
Added:
val page_header : ?id:string -> ?class_:string -> node list -> node
360
+
Added:
val page_main : ?id:string -> ?class_:string -> node list -> node
361
+
Added:
val page_footer : ?class_:string -> node list -> node
362
+
Added:
val document_body : ?class_:string -> node list -> node
363
+
Added:
364
+
Added:
val document : ?lang:string -> head:node -> body:node -> unit -> node
365
+
Added:
(** A complete document. [lang] defaults to ["en"]; set it so screen readers
366
+
Added:
pick the right pronunciation. *)
367
+
Added:
368
+
Added:
(** {1 Responses} *)
369
+
Added:
370
+
Added:
val respond : ?status:[< Dream.status ] -> node -> Dream.response Dream.promise
371
+
Added:
(** Send a rendered document as an HTTP response. *)