docs
Loading...
Searching...
No Matches
Schema.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5/// @file Schema.h
6///
7/// Factories and tags for defining a type's data-binding schema.
8///
9/// `#include <Ultralight/dom/data/Schema.h>`
10///
11/// @note This API is a preview and may still change after 2.0.
12///
13/// A schema declares which members of a native type the page can access. Passing entries to
14/// Schema() defines how HTML and CSS display your values and route user input back to native code.
15///
16/// This struct declares its schema as a `schema` member:
17///
18/// ```
19/// struct Buff {
20/// int64_t id = 0;
21/// std::string icon;
22/// };
23///
24/// struct Hud {
25/// int health = 100;
26/// double volume = 0.8;
27/// std::vector<Buff> buffs;
28///
29/// static constexpr auto schema = dd::Schema(
30/// dd::Field("health", &Hud::health), // {{hud.health}}
31/// dd::Field("volume", &Hud::volume, dd::Editable), // ul-value="hud.volume"
32/// dd::List("buffs", &Hud::buffs, &Buff::id), // ul-for="hud.buffs"
33/// dd::Action("respawn"), // ul-on:click="hud.respawn"
34/// dd::Var("--hp", &Hud::health)); // var(--hp) in CSS
35/// };
36/// ```
37///
38/// ## Choosing Schema Entries
39///
40/// Each task pairs with an entry or tag:
41///
42/// | What the Page Needs | Entry or Tag |
43/// |----------------------------------|--------------|
44/// | Show a value | Field() |
45/// | Repeat rows | List() |
46/// | Let the page request something | Action() |
47/// | Set a CSS variable | Var() |
48/// | Expose all members of a struct | Reflect() |
49/// | Accept edits from a form control | Editable |
50/// | Hide an entry from page markup | Internal |
51/// | Deliver only the newest action | OnlyLatest |
52/// | Check or clamp edits | Validate() |
53///
54/// ## Rules for Every Schema
55///
56/// - **Apply tags directly to an entry or supply them as standalone declarations.** Add tags like
57/// Editable or Internal to Field() or List(), or target an already declared member by name using
58/// `dd::Editable("volume")` for properties exposed by Reflect().
59/// - **Mistakes fail at compile time.** Duplicate entries and malformed declarations produce
60/// compilation errors that identify the violated rule (see Schema()).
61/// - **Store the schema in the type itself or in a traits specialization.** Define a
62/// `static constexpr auto schema` member inside types you control, or specialize
63/// dom::data::TypeTraits for existing classes and lambda accessors (see dom::data::TypeTraits).
64/// - **Include this header on its own in a type's header.** A header defining your types needs only
65/// `<Ultralight/dom/data/Schema.h>`, while `<Ultralight/dom/data/Context.h>` includes it
66/// automatically for code that binds instances.
67///
68/// @see dom::data::Schema(), dom::data::TypeTraits, dom::data::Reflect()
69///
70#pragma once
71#include <Ultralight/detail/FixedString.h>
73#include <Ultralight/dom/data/detail/Accessors.h>
74#include <Ultralight/dom/detail/Support.h>
75
76#include <cstddef>
77#include <cstdint>
78#include <string_view>
79#include <tuple>
80#include <type_traits>
81#include <utility>
82
83namespace ultralight {
84
85class String;
86
87namespace dom {
88namespace data {
89
90///
91/// Flags on a schema entry.
92///
93/// You set Editable on fields, Internal on fields and lists, and OnlyLatest on actions. The
94/// library sets Keyed, Var, and Nullable from the entry's declaration. Tools read the flags from
95/// the schema JSON (see Context::schema()).
96///
97/// @note These values stay the same across releases. New flags are added at the end.
98///
99enum EntryFlags : uint8_t {
100 kEntryFlags_Editable = 1 << 0, ///< The page may request changes to this field.
101 kEntryFlags_Internal = 1 << 1, ///< Pages can't reach this entry (see dd::Internal).
102 kEntryFlags_OnlyLatest = 1 << 2, ///< Only the newest pending action is delivered.
103
104 kEntryFlags_Keyed = 1 << 3, ///< A list whose rows have a key member.
105 kEntryFlags_Var = 1 << 4, ///< A CSS custom property (see Var()).
106 kEntryFlags_Nullable = 1 << 5, ///< A nested object that may be absent.
107};
108
109/// \cond INTERNAL
110namespace detail {
111
112inline constexpr size_t kNpos = static_cast<size_t>(-1);
113
114// Consteval-validation failure markers. Reaching one of these while Schema(...) is
115// constant-evaluated IS the diagnostic: the compiler reports a non-constant expression and
116// names the function, which names the violated rule. Never defined, never reached at
117// runtime (the factories are consteval).
118void SchemaErrorDuplicateEntryName(const char* name);
119void SchemaErrorEntryNameNotAnIdentifier(const char* name);
120void SchemaErrorVarNameMustStartWithDashDash(const char* name);
121void SchemaErrorVarNameMalformed(const char* name);
122void SchemaErrorAnnotationTargetNotFound(const char* name);
123void SchemaErrorAnnotationTargetNotAField(const char* name);
124void SchemaErrorAnnotationDuplicated(const char* name);
125void SchemaErrorValidatorDuplicated(const char* name);
126void SchemaErrorValidatorSignatureMismatch(const char* name);
127void SchemaErrorValidatorWithoutEditable(const char* name);
128void SchemaErrorEditableOnNonLeaf(const char* name);
129void SchemaErrorEditableOnStyleValue(const char* name);
130
131template <typename T>
132inline constexpr bool kAlwaysFalse = false;
133
134// Stops at the first differing character: the schema checks compare every name pair, and a
135// string_view compare would measure both names first.
136constexpr bool NamesEqual(const char* a, const char* b) {
137 while (*a && *a == *b) {
138 a++;
139 b++;
140 }
141 return *a == *b;
142}
143
144// The one name grammar of the data-binding system, shared by the consteval schema checks
145// and the engine's runtime checks (Bind, DefineFormat, the C type builder). Field, list,
146// action, binding, and format names are markup path segments or pipe names:
147// [A-Za-z_][A-Za-z0-9_]*. A leading '$' stays free for names the library defines.
148constexpr bool IsIdentifierName(std::string_view name) {
149 if (name.empty())
150 return false;
151 for (size_t i = 0; i < name.size(); i++) {
152 const char c = name[i];
153 const bool alpha = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
154 if (!alpha && (i == 0 || c < '0' || c > '9'))
155 return false;
156 }
157 return true;
158}
159
160// Var names are CSS custom properties in the lowercase form `ul-var-*` markup produces:
161// "--" followed by [a-z0-9-]+.
162constexpr bool IsVarName(std::string_view name) {
163 if (name.size() < 3 || name[0] != '-' || name[1] != '-')
164 return false;
165 for (char c : name.substr(2)) {
166 if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-'))
167 return false;
168 }
169 return true;
170}
171
172consteval void CheckTokenName(const char* name, bool is_var) {
173 const std::string_view n(name);
174 if (!is_var) {
175 if (!IsIdentifierName(n))
176 SchemaErrorEntryNameNotAnIdentifier(name);
177 } else if (n.size() < 3 || n[0] != '-' || n[1] != '-') {
178 SchemaErrorVarNameMustStartWithDashDash(name);
179 } else if (!IsVarName(n)) {
180 SchemaErrorVarNameMalformed(name);
181 }
182}
183
184template <typename C>
185using ListRow = std::remove_cvref_t<decltype(*std::begin(std::declval<const C&>()))>;
186
187template <typename C>
188concept IsIterable = requires(const C& c) {
189 std::begin(c);
190 std::end(c);
191};
192
193} // namespace detail
194/// \endcond
195
196/// \cond INTERNAL
197
198///
199/// Absence marker for a field token's validator slot (the default when no Validate tag is
200/// attached).
201///
202struct NoValidate {};
203
204///
205/// A Validate annotation attached directly to a Field token, holding the user's callable.
206///
207/// Produced by Validate(fn); consumed when the owning schema entry is lowered.
208///
209template <typename Fn>
210struct ValidateWith {
211 Fn fn; ///< The validation callable (captureless; see Validate()).
212};
213
214///
215/// A name-addressed Editable annotation entry, pairing onto an already-declared field.
216///
217/// Produced by `Editable("name")`; consumed (and removed) by Schema() construction.
218///
219struct EditableEntry {
220 const char* name; ///< Name of the field the annotation applies to.
221};
222
223///
224/// A name-addressed Internal annotation entry, pairing onto an already-declared field.
225///
226/// Produced by `Internal("name")`; consumed (and removed) by Schema() construction.
227///
228struct InternalEntry {
229 const char* name; ///< Name of the field the annotation applies to.
230};
231
232///
233/// A name-addressed Validate annotation entry, pairing onto an already-declared field.
234///
235/// Produced by `Validate("name", fn)`. Unlike Editable/Internal entries it is retained by
236/// the constructed schema (the callable's type cannot merge into the field token), and is
237/// resolved against its field when the schema is lowered.
238///
239template <typename Fn>
240struct ValidateEntry {
241 const char* name; ///< Name of the field the validator applies to.
242 Fn fn; ///< The validation callable (captureless; see Validate()).
243};
244
245/// \endcond
246
247///
248/// Marks a field the page may request changes to.
249///
250/// The page requests a change through a form control bound with `ul-value`. The library never
251/// writes the change into your object. It delivers the request to the field's
252/// Binding::OnChange() handler inside Context::Sync(), and your handler applies it (with no
253/// handler, the change is dropped).
254///
255/// Use it on the field itself, or as a Schema() entry for a field that's already declared (eg,
256/// by Reflect()):
257///
258/// ```
259/// dd::Field("volume", &Settings::volume, dd::Editable)
260/// dd::Editable("volume")
261/// ```
262///
263/// @note Page edits come back only for an Editable bool, number, string, enum, or color field of
264/// the bound type itself. On a field of a row or a nested object, `ul-value` only shows the
265/// value. `ul-value` on a field that isn't Editable doesn't bind the control.
266///
268 ///
269 /// Mark an already-declared field as Editable (a Schema() entry).
270 ///
271 /// @param name The field's name.
272 ///
273 /// @return Returns the entry to pass to Schema().
274 ///
275 consteval EditableEntry operator()(const char* name) const { return { name }; }
276};
277inline constexpr EditableTag Editable {};
278
279///
280/// Marks a field or list that pages can't reach.
281///
282/// Markup can't bind to an Internal entry, but your Sync function can still read it with
283/// Snapshot::Get(). Use it for values that only steer your sync logic (eg, a revision counter
284/// that decides whether to skip work). The schema %JSON (Context::schema()) lists
285/// Internal entries with an `internal` flag, and the schema tools leave them out of generated
286/// typings and mocks.
287///
288/// Use it on the entry itself, or as a Schema() entry for one that's already declared:
289///
290/// ```
291/// dd::Field("revision", &Inventory::revision, dd::Internal)
292/// dd::Internal("revision")
293/// ```
294///
296 ///
297 /// Mark an already-declared field or list as Internal (a Schema() entry).
298 ///
299 /// @param name The entry's name.
300 ///
301 /// @return Returns the entry to pass to Schema().
302 ///
303 consteval InternalEntry operator()(const char* name) const { return { name }; }
304};
305inline constexpr InternalTag Internal {};
306
307///
308/// Marks an action whose pending emissions collapse into the newest one.
309///
310/// Use it for continuous input like scroll positions or pointer previews:
311///
312/// ```
313/// dd::Action<double>("scroll", dd::OnlyLatest)
314/// ```
315///
316/// Each Context::Sync() then delivers the action at most once with the newest payload. The
317/// action keeps the queue position of its first pending emission. Without this tag, every
318/// emission is delivered in order.
319///
321inline constexpr OnlyLatestTag OnlyLatest {};
322
323///
324/// Attach a validator to a field (a Field() tag).
325///
326/// A validator checks or corrects a value the page requests before the request is queued for
327/// your handler. It takes the requested value and returns the value to accept:
328///
329/// ```
330/// dd::Field("volume", &Settings::volume, dd::Editable,
331/// dd::Validate([](float v) { return std::clamp(v, 0.0f, 1.0f); }))
332/// ```
333///
334/// Return a `std::optional` to reject a request (an empty optional). The control then shows the
335/// last accepted value again.
336///
337/// To check the value against other fields, take a second parameter. It gets a Snapshot of the
338/// values the page shows. Declare it `auto` when you write the validator inside the
339/// schema, since the type isn't complete there:
340///
341/// ```
342/// dd::Field("bid", &Auction::bid, dd::Editable,
343/// dd::Validate([](int64_t bid, auto s) -> std::optional<int64_t> {
344/// if (bid > s.template Get<"gold">())
345/// return std::nullopt;
346/// return bid;
347/// }))
348/// ```
349///
350/// The value parameter can use the field's own type or the library's form of it (`int64_t` or
351/// `double` for numbers, `const String&` for strings). An enum field gets the enum value. A
352/// request for a name that isn't one of its enumerators is rejected before your validator runs.
353///
354/// @param fn The validator (a lambda or function object with no captures). A function pointer
355/// doesn't compile.
356///
357/// @return Returns the tag to pass to Field().
358///
359/// @note Validators run on the Renderer's thread, so they must not read or write your objects
360/// (use the Snapshot parameter). They run only for page edits of an Editable bool,
361/// number, string, enum, or color field of the bound type itself. Values you stage from C
362/// (ulDOMDataContextStageChangeBool() and its siblings) skip them.
363///
364/// @warning A validator must not throw. Reject a value by returning an empty optional.
365///
366template <typename Fn>
367 requires std::is_class_v<Fn>
368consteval ValidateWith<Fn> Validate(Fn fn) {
369 return { fn };
370}
371
372///
373/// Attach a validator to an already-declared field (a Schema() entry).
374///
375/// Use this form for a field that Reflect() declares. The validator follows the rules of the
376/// Field() tag form.
377///
378/// @param name The field's name.
379///
380/// @param fn The validator.
381///
382/// @return Returns the entry to pass to Schema().
383///
384template <typename Fn>
385 requires std::is_class_v<Fn>
386consteval ValidateEntry<Fn> Validate(const char* name, Fn fn) {
387 return { name, fn };
388}
389
390///
391/// A field entry (the value Field() returns). Pass it to Schema().
392///
393template <typename Owner, typename Value, typename Accessor, typename Validator = NoValidate>
395 using owner_type = Owner; ///< The type the accessor reads from (`void` without one).
396 using value_type = Value; ///< The field's value type.
397 using accessor_type = Accessor; ///< The accessor's type (`std::nullptr_t` without one).
398 using validator_type = Validator; ///< The type of the validator from Validate(), if any.
399
400 const char* name; ///< The field's name.
401 Accessor accessor; ///< Reads the value from a const instance.
402 ULTRALIGHT_DOM_NO_UNIQUE_ADDRESS Validator validator; ///< The validator, if any.
403 uint8_t flags = 0; ///< The EntryFlags set on this field.
404
405 ///
406 /// Whether or not the field has an accessor.
407 ///
408 static constexpr bool kHasAccessor = !std::is_same_v<Accessor, std::nullptr_t>;
409
410 ///
411 /// Whether or not a validator is attached with the Field() tag form of Validate().
412 ///
413 static constexpr bool kHasValidator = !std::is_same_v<Validator, NoValidate>;
414};
415
416///
417/// A list entry (the value List() returns). Pass it to Schema().
418///
419template <typename Owner, typename Container, typename Accessor, typename KeyPtr>
420struct ListToken {
421 using owner_type = Owner; ///< The type the accessor reads from.
422 using container_type = Container; ///< The container type.
423 using row_type = detail::ListRow<Container>; ///< The row type.
424 using key_pointer_type = KeyPtr; ///< The key member pointer's type (else `std::nullptr_t`).
425
426 const char* name; ///< The list's name.
427 Accessor accessor; ///< Reads the container from a const instance.
428 KeyPtr key; ///< The row member that identifies each row (null without a key).
429 uint8_t flags = 0; ///< The EntryFlags set on this list.
430
431 ///
432 /// Whether or not rows have a key (rows are matched by position otherwise).
433 ///
434 static constexpr bool kKeyed = !std::is_same_v<KeyPtr, std::nullptr_t>;
435};
436
437///
438/// An action entry (the value Action() returns). Pass it to Schema().
439///
440template <typename Payload>
442 using payload_type = Payload; ///< The payload type (`void` for none).
443
444 const char* name; ///< The action's name.
445 uint8_t flags = 0; ///< The EntryFlags set on this action.
446};
447
448///
449/// A CSS custom property entry (the value Var() returns). Pass it to Schema().
450///
451template <typename Owner, typename Value, typename Accessor>
452struct VarToken {
453 using owner_type = Owner; ///< The type the accessor reads from.
454 using value_type = Value; ///< The accessor's value type.
455 using accessor_type = Accessor; ///< The accessor's type.
456
457 const char* name; ///< The property's name, including the leading two hyphens.
458 Accessor accessor; ///< Reads the value from a const instance.
459};
460
461#if ULTRALIGHT_REFLECTION
462
463///
464/// A Reflect() entry (the value Reflect() returns). Pass it to Schema().
465///
466template <typename T>
467struct ReflectEntry {};
468
469///
470/// Declare a field for each member of a plain struct (a Schema() entry).
471///
472/// Each field gets the member's name. Use it when you want every member plus a few
473/// annotations:
474///
475/// ```
476/// template <> struct ultralight::dom::data::TypeTraits<ToolbarState> {
477/// static constexpr auto schema = dd::Schema(
478/// dd::Reflect<ToolbarState>(), dd::Editable("url"),
479/// dd::Action("back"), dd::Action("reload"));
480/// };
481/// ```
482///
483/// A plain struct with no schema gets the same fields automatically (see TypeTraits).
484///
485/// @return Returns the entry to pass to Schema().
486///
487/// \parblock
488/// @note `T` must be a plain aggregate (no constructors, virtual functions, or C array
489/// members) with at most 24 members. It must have external linkage (not declared in an
490/// anonymous namespace or inside a function).
491/// \endparblock
492///
493/// \parblock
494/// @note Reflect() needs a complete type, so use it in a TypeTraits specialization (as above)
495/// rather than in the type's own `schema` member.
496/// \endparblock
497///
498template <typename T>
499consteval ReflectEntry<T> Reflect() {
500 static_assert(std::is_aggregate_v<T> && std::is_class_v<T> && !std::is_polymorphic_v<T>,
501 "Reflect<T>() requires a plain aggregate class type");
502 return {};
503}
504
505#endif // ULTRALIGHT_REFLECTION
506
507} // namespace data
508} // namespace dom
509} // namespace ultralight
510
511// Schema construction machinery (entry classification, Reflect expansion, the consteval
512// validation passes, the constructed Schema type). Definitions must follow the token
513// declarations above and precede the factories below.
514#include <Ultralight/dom/data/detail/SchemaImpl.h>
515
516namespace ultralight {
517namespace dom {
518namespace data {
519
520///
521/// Declare a field.
522///
523/// The accessor reads the value from a const instance. It can be a data member pointer, a const
524/// member function pointer, or a lambda with no captures that takes a const reference to the
525/// instance. The field's type decides what the entry is:
526///
527/// - **A value type** (bool, a number, a string, an enum, a StyleValue, a Color, or any type
528/// with a ValueTraits specialization) is a value pages can show.
529/// - **A described type** (a type with a schema) is a nested object. Pages reach its fields
530/// with a longer path (eg, `hud.player.name`).
531/// - **A pointer, `std::optional`, `std::unique_ptr`, or `std::shared_ptr` to a described
532/// type** is a nested object that may be absent (null).
533/// - **A container of described rows** (eg, `std::vector<Row>`) is a list whose rows are
534/// matched by position. Use List() to give rows a key.
535///
536/// Any other type fails to compile (eg, `std::vector<float>` or `std::optional<int>`).
537///
538/// @param name The field's name (pages use it in paths like `hud.health`).
539///
540/// @param accessor How to read the value from a const instance.
541///
542/// @param tags Optional tags (dd::Editable, dd::Internal, and one Validate()).
543///
544/// @return Returns the entry to pass to Schema().
545///
546/// @note A lambda accessor reads the type's members, so it needs the complete type. Declare a
547/// schema that uses one in a TypeTraits specialization (inside the type's own `schema`
548/// member, the type isn't complete yet).
549///
550template <detail::IsAccessor A, typename... Tags>
551consteval auto Field(const char* name, A accessor, Tags... tags) {
552 detail::CheckTokenName(name, false);
553 static_assert((int(detail::IsValidateWith<Tags>::value) + ... + 0) <= 1,
554 "only one Validate(fn) per Field");
555 auto validator = detail::PickValidator(tags...);
556 return FieldToken<detail::AccessorOwner<A>, detail::AccessorValue<A>, A,
557 decltype(validator)> { name, accessor, validator,
558 detail::FieldFlags(tags...) };
559}
560
561///
562/// Declare a field without an accessor.
563///
564/// Give the value type as the template argument, since there's no accessor to take it from
565/// (eg, `dd::Field<int>("score")`). The type's Sync function writes the value with Model::Set().
566///
567/// @param name The field's name.
568///
569/// @param tags Optional tags, as in the accessor form.
570///
571/// @return Returns the entry to pass to Schema().
572///
573/// @note The library's default sync skips a field without an accessor, so declare a Sync
574/// function for the type (see TypeTraits).
575///
576template <typename Value, typename... Tags>
577 requires (!detail::IsAccessor<Value>)
578consteval auto Field(const char* name, Tags... tags) {
579 detail::CheckTokenName(name, false);
580 static_assert((int(detail::IsValidateWith<Tags>::value) + ... + 0) <= 1,
581 "only one Validate(fn) per Field");
582 auto validator = detail::PickValidator(tags...);
583 return FieldToken<void, Value, std::nullptr_t, decltype(validator)> {
584 name, nullptr, validator, detail::FieldFlags(tags...)
585 };
586}
587
588///
589/// Declare a keyed list.
590///
591/// The accessor returns an iterable container of rows (by reference or by value), and each row
592/// is a described type. The key is the row member that identifies a row. When the list
593/// reorders, each row keeps its elements in the page:
594///
595/// ```
596/// dd::List("roster", &Match::roster, &PlayerRow::id)
597/// ```
598///
599/// @param name The list's name.
600///
601/// @param accessor How to read the container from a const instance.
602///
603/// @param key A pointer to the row member that identifies each row. The member must be
604/// an integer, an enum, or a string.
605///
606/// @param tags Optional tags (dd::Internal).
607///
608/// @return Returns the entry to pass to Schema().
609///
610/// @note Keys must be unique among the rows. With duplicates, the library logs a warning and
611/// the last row wins.
612///
613template <detail::IsAccessor A, typename KeyPtr, typename... Tags>
614 requires std::is_member_object_pointer_v<KeyPtr>
615consteval auto List(const char* name, A accessor, KeyPtr key, Tags... tags) {
616 detail::CheckTokenName(name, false);
617 using Container = detail::AccessorValue<A>;
618 static_assert(detail::IsIterable<Container>,
619 "List accessor must return an iterable container of rows");
620 static_assert(std::is_base_of_v<detail::AccessorOwner<KeyPtr>, detail::ListRow<Container>>,
621 "List key must be a member pointer of the row type");
623 name, accessor, key, detail::ListFlags(tags...)
624 };
625}
626
627///
628/// Declare a list whose rows are matched by position.
629///
630/// This suits a list that only grows at the end. When rows reorder, each position keeps its
631/// elements and shows the new row's values. Use the keyed form when rows move.
632///
633/// @param name The list's name.
634///
635/// @param accessor How to read the container from a const instance.
636///
637/// @param tags Optional tags (dd::Internal).
638///
639/// @return Returns the entry to pass to Schema().
640///
641template <detail::IsAccessor A, typename... Tags>
642 requires (!std::is_member_object_pointer_v<Tags> && ...)
643consteval auto List(const char* name, A accessor, Tags... tags) {
644 detail::CheckTokenName(name, false);
645 using Container = detail::AccessorValue<A>;
646 static_assert(detail::IsIterable<Container>,
647 "List accessor must return an iterable container of rows");
648 return ListToken<detail::AccessorOwner<A>, Container, A, std::nullptr_t> {
649 name, accessor, nullptr, detail::ListFlags(tags...)
650 };
651}
652
653///
654/// Declare an action (a request the page sends to your code).
655///
656/// Pages fire an action from markup (eg, `ul-on:click="hud.respawn"`), and your code can send
657/// one with Binding::PostAction(). Handlers registered with Binding::OnAction() receive it inside
658/// Context::Sync().
659///
660/// To send a payload with each action, give its type as the template argument:
661///
662/// ```
663/// dd::Action("respawn") // no payload
664/// dd::Action<MoveItem>("moveItem") // a described struct of value fields
665/// dd::Action<double>("seek") // one bool, number, string, or enum
666/// ```
667///
668/// In the schema JSON, a single-value payload is a struct with one field, `value`. Handlers
669/// receive the value itself.
670///
671/// @param name The action's name.
672///
673/// @param tags Optional tags (dd::OnlyLatest).
674///
675/// @return Returns the entry to pass to Schema().
676///
677/// \parblock
678/// @note Actions fired from markup carry no payload, so a handler that takes a payload never
679/// receives them.
680/// \endparblock
681///
682/// \parblock
683/// @note The action queue holds 1024 actions by default, and a full queue drops new ones
684/// (see Context::set_action_queue_capacity()).
685/// \endparblock
686///
687template <typename Payload = void, typename... Tags>
688consteval ActionToken<Payload> Action(const char* name, Tags... tags) {
689 detail::CheckTokenName(name, false);
690 return ActionToken<Payload> { name, detail::ActionFlags(tags...) };
691}
692
693///
694/// Declare a CSS custom property that follows your data.
695///
696/// The page needs no markup for it. On the bound type itself, the property is set on the page's
697/// root element, so the whole document can use it. On a row type, it's set on each row's root
698/// element:
699///
700/// ```
701/// dd::Var("--hp", &HUD::health) // .bar { width: calc(var(--hp) * 1%); }
702/// ```
703///
704/// The accessor returns a number (a value without a unit), a StyleValue (a value with a unit),
705/// or a Color (written as hex, eg `#ff8000`).
706///
707/// @param name The property's name, starting with two hyphens.
708///
709/// @param accessor How to read the value from a const instance.
710///
711/// @return Returns the entry to pass to Schema().
712///
713/// \parblock
714/// @note A Var on a type that's only used as a nested object is never set.
715/// \endparblock
716///
717/// \parblock
718/// @note A change to a Var on the bound type makes every style in the page that uses it
719/// recompute. For a value that changes every frame, prefer a `ul-var-*` attribute on the
720/// element that uses it. If two bindings set the same property on the root element, the
721/// last one written wins and the library logs a warning.
722/// \endparblock
723///
724template <detail::IsAccessor A>
725consteval auto Var(const char* name, A accessor) {
726 detail::CheckTokenName(name, true);
727 return VarToken<detail::AccessorOwner<A>, detail::AccessorValue<A>, A> { name, accessor };
728}
729
730///
731/// Declare a type's schema.
732///
733/// A schema lists what pages can show and do with a type. Store it in the type as
734/// `static constexpr auto schema`, or in a TypeTraits specialization for a type you can't edit:
735///
736/// ```
737/// struct HUD {
738/// int health = 100;
739/// std::vector<Buff> buffs;
740///
741/// static constexpr auto schema = dd::Schema(
742/// dd::Field("health", &HUD::health),
743/// dd::List("buffs", &HUD::buffs, &Buff::id),
744/// dd::Action("respawn"),
745/// dd::Var("--hp", &HUD::health));
746/// };
747/// ```
748///
749/// Entries keep the order you declare them in (Reflect() adds its fields in member order).
750///
751/// ## Compile-Time Checks
752///
753/// Schema() runs at compile time, and these mistakes fail to compile:
754///
755/// - **Two entries with the same name.**
756/// - **A malformed name.** A name starts with a letter or an underscore, followed by letters,
757/// digits, and underscores (eg, `maxHealth` or `slot_2`). A Var() name is two hyphens
758/// followed by lowercase letters, digits, and hyphens (eg, `--max-hp`).
759/// - **An annotation for a missing entry**, or for an entry it doesn't apply to (eg,
760/// dd::Editable on a list).
761/// - **A second Editable, Internal, or validator on the same field.**
762/// - **A validator that doesn't fit its field's type**, or one on a field that isn't
763/// dd::Editable (validators only check page edits).
764/// - **dd::Editable on a field that isn't a bool, number, string, enum, or color** (eg, a
765/// nested object or a StyleValue). This check runs where the type is bound.
766///
767/// Look for a function like `SchemaErrorDuplicateEntryName` in the compiler's error. It states
768/// the rule the schema breaks.
769///
770/// @param entries The entries in order (Field(), List(), Action(), Var(), and Reflect()), plus
771/// any annotations for declared entries (`dd::Editable("name")`,
772/// `dd::Internal("name")`, and `dd::Validate("name", fn)`).
773///
774/// @return Returns the schema.
775///
776template <typename... Entries>
777consteval auto Schema(Entries... entries) {
778 return detail::BuildSchema(entries...);
779}
780
781} // namespace data
782} // namespace dom
783} // namespace ultralight
A layout node that organizes child nodes into a row or column.
Definition Container.h:101
Unicode string container with conversions for UTF-8, UTF-16, and UTF-32.
Definition String.h:31
A generic value passed to data-binding formatters and handlers.
Definition Value.h:95
Data-binding API that connects native C++ data to HTML and CSS markup.
Definition ActionInfo.h:13
consteval ValidateWith< Fn > Validate(Fn fn)
Attach a validator to a field (a Field() tag).
Definition Schema.h:368
consteval auto Field(const char *name, A accessor, Tags... tags)
Declare a field.
Definition Schema.h:551
constexpr InternalTag Internal
Definition Schema.h:305
consteval ActionToken< Payload > Action(const char *name, Tags... tags)
Declare an action (a request the page sends to your code).
Definition Schema.h:688
constexpr EditableTag Editable
Definition Schema.h:277
consteval auto Schema(Entries... entries)
Declare a type's schema.
Definition Schema.h:777
constexpr OnlyLatestTag OnlyLatest
Definition Schema.h:321
consteval auto Var(const char *name, A accessor)
Declare a CSS custom property that follows your data.
Definition Schema.h:725
EntryFlags
Flags on a schema entry.
Definition Schema.h:99
@ kEntryFlags_Nullable
A nested object that may be absent.
Definition Schema.h:106
@ kEntryFlags_OnlyLatest
Only the newest pending action is delivered.
Definition Schema.h:102
@ kEntryFlags_Keyed
A list whose rows have a key member.
Definition Schema.h:104
@ kEntryFlags_Internal
Pages can't reach this entry (see dd::Internal).
Definition Schema.h:101
@ kEntryFlags_Var
A CSS custom property (see Var()).
Definition Schema.h:105
@ kEntryFlags_Editable
The page may request changes to this field.
Definition Schema.h:100
consteval auto List(const char *name, A accessor, KeyPtr key, Tags... tags)
Declare a keyed list.
Definition Schema.h:615
Direct C++ access to modify page elements and handle events.
Root namespace for every public Ultralight type, function, and enumeration.
An action entry (the value Action() returns).
Definition Schema.h:441
Payload payload_type
The payload type (void for none).
Definition Schema.h:442
const char * name
The action's name.
Definition Schema.h:444
uint8_t flags
The EntryFlags set on this action.
Definition Schema.h:445
Marks a field the page may request changes to.
Definition Schema.h:267
consteval EditableEntry operator()(const char *name) const
Mark an already-declared field as Editable (a Schema() entry).
Definition Schema.h:275
A field entry (the value Field() returns).
Definition Schema.h:394
Value value_type
The field's value type.
Definition Schema.h:396
static constexpr bool kHasValidator
Whether or not a validator is attached with the Field() tag form of Validate().
Definition Schema.h:413
Accessor accessor_type
The accessor's type (std::nullptr_t without one).
Definition Schema.h:397
Owner owner_type
The type the accessor reads from (void without one).
Definition Schema.h:395
const char * name
The field's name.
Definition Schema.h:400
ULTRALIGHT_DOM_NO_UNIQUE_ADDRESS Validator validator
The validator, if any.
Definition Schema.h:402
uint8_t flags
The EntryFlags set on this field.
Definition Schema.h:403
Accessor accessor
Reads the value from a const instance.
Definition Schema.h:401
static constexpr bool kHasAccessor
Whether or not the field has an accessor.
Definition Schema.h:408
Validator validator_type
The type of the validator from Validate(), if any.
Definition Schema.h:398
Marks a field or list that pages can't reach.
Definition Schema.h:295
consteval InternalEntry operator()(const char *name) const
Mark an already-declared field or list as Internal (a Schema() entry).
Definition Schema.h:303
A list entry (the value List() returns).
Definition Schema.h:420
static constexpr bool kKeyed
Whether or not rows have a key (rows are matched by position otherwise).
Definition Schema.h:434
Owner owner_type
The type the accessor reads from.
Definition Schema.h:421
const char * name
The list's name.
Definition Schema.h:426
Container container_type
The container type.
Definition Schema.h:422
KeyPtr key_pointer_type
The key member pointer's type (else std::nullptr_t).
Definition Schema.h:424
uint8_t flags
The EntryFlags set on this list.
Definition Schema.h:429
KeyPtr key
The row member that identifies each row (null without a key).
Definition Schema.h:428
Accessor accessor
Reads the container from a const instance.
Definition Schema.h:427
detail::ListRow< Container > row_type
The row type.
Definition Schema.h:423
Marks an action whose pending emissions collapse into the newest one.
Definition Schema.h:320
A CSS custom property entry (the value Var() returns).
Definition Schema.h:452
Value value_type
The accessor's value type.
Definition Schema.h:454
Accessor accessor_type
The accessor's type.
Definition Schema.h:455
Owner owner_type
The type the accessor reads from.
Definition Schema.h:453
const char * name
The property's name, including the leading two hyphens.
Definition Schema.h:457
Accessor accessor
Reads the value from a const instance.
Definition Schema.h:458