morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
author.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <algorithm>
5#include <chrono>
6#include <concepts>
7#include <cstddef>
8#include <cstdint>
9#include <exception>
10#include <expected>
11#include <functional>
12#include <glaze/glaze.hpp>
13#include <initializer_list>
14#include <iterator>
15#include <map>
16#include <memory>
17#include <optional>
18#include <stdexcept>
19#include <string>
20#include <string_view>
21#include <type_traits>
22#include <utility>
23#include <vector>
24
25#include "../core/changes.hpp"
26#include "../core/effects.hpp"
27#include "../core/logger.hpp"
28#include "../core/model_key.hpp"
29#include "../core/registry.hpp"
33#include "../ui/document.hpp"
34#include "../ui/value.hpp"
35#include "../util/datetime.hpp"
36#include "../util/rational.hpp"
37#include "alignment.hpp"
38#include "button_emphasis.hpp"
39#include "button_style.hpp"
40
41/// @file
42/// @brief Server-side authoring of UI documents (spec 5 §11): typed builders that write a screen's JSON, and the
43/// registration that validates it once — `loadDocument` with the builders' catalog facts, then the
44/// accessibility rules.
45///
46/// The builders live in `morph::ui::author`, beside the view-tree builders of `morph::ui` that share their names; a
47/// screen's source writes `namespace ui = morph::ui::author;`. Specified in `docs/spec/ui/author.md`.
48
49namespace morph::ui::author {
50
51// NOLINTBEGIN(bugprone-easily-swappable-parameters): a builder takes its operands in the order the document writes
52// them, and most of them are expressions, which every C++ value converts to; distinct parameter types would make the
53// builders unusable rather than safer.
54
55/// @brief The value type of a handle whose value the screen cannot know when it is written: a query result read by
56/// name, an element value, an event payload.
57struct Any {};
58
59/// @brief What a builder throws when it is given something a document cannot say, such as a field read from a
60/// value that has no name. Registration turns it into a `Refusal` at the document's root.
61class AuthoringError : public std::logic_error {
62public:
63 using std::logic_error::logic_error;
64};
65
66/// @brief Catalog facts of a model type that its C++ type does not show. Specialise it for a model whose shared
67/// singleton is equivalent to a private instance.
68/// @tparam Model The model type.
69template <typename Model>
70struct ModelFacts {
71 /// @brief Whether a shared singleton is equivalent to a private instance.
72 static constexpr bool stateless = false;
73};
74
75/// @brief Catalog facts of an action that its C++ type does not show. Specialise it for an action whose reply rows
76/// carry server-calculated fields, whose reply carries a secret, or which sends a credential.
77/// @tparam Action The action type.
78template <typename Action>
80 /// @brief Whether the action sends a credential.
81 static constexpr bool credential = false;
82
83 /// @brief The reply-row fields the server calculates; an `optimistic` write may not name them.
84 /// @return The field names.
85 [[nodiscard]] static std::vector<std::string> calculatedFields() { return {}; }
86
87 /// @brief The reply fields the server marks `secret`.
88 /// @return The field names.
89 [[nodiscard]] static std::vector<std::string> secretFields() { return {}; }
90};
91
92/// @brief Satisfied when `Model` runs `Action`: the model has an `execute` that takes it.
93template <typename Model, typename Action>
94concept Runs = requires(Model& model, Action& action) { model.execute(action); };
95
96namespace detail {
97
98/// @brief The members of an action result that hold a list, when the result is a record.
99/// @tparam Result The action's result type.
100/// @return The names glaze writes them under, or `std::nullopt` when glaze does not write @p Result as a record.
101template <typename Result>
102[[nodiscard]] std::optional<std::vector<std::string>> listFields() {
103 if constexpr (!std::is_class_v<Result> || (!glz::reflectable<Result> && !glz::glaze_object_t<Result>)) {
104 return std::nullopt;
105 } else {
106 std::vector<std::string> names;
107 [&names]<std::size_t... I>(std::index_sequence<I...>) {
108 // An action whose result has no members expands the fold below to nothing.
109 [[maybe_unused]] auto add = [&names]<std::size_t Index> {
110 using Member =
111 std::remove_cvref_t<decltype(::morph::detail::reflectedMember<Index>(std::declval<Result&>()))>;
112 if constexpr (glz::writable_array_t<Member>) {
113 names.emplace_back(glz::reflect<Result>::keys[Index]);
114 }
115 };
116 (add.template operator()<I>(), ...);
117 }(std::make_index_sequence<glz::reflect<Result>::size>{});
118 return names;
119 }
120}
121
122/// @brief The catalog's form of an action's declared effects.
123/// @param actionId The action's id.
124/// @return Its effects, or `std::nullopt` when it declared none: the action reads.
125/// @throws morph::model::EffectsRegistrationError when the registered records and effects are invalid.
126[[nodiscard]] inline std::optional<std::vector<ActionEffect>> effectsInfo(std::string_view actionId) {
127 auto const declared = ::morph::effects::effectsOf(actionId);
128 if (!declared) {
129 return std::nullopt;
130 }
131 std::vector<ActionEffect> out;
132 out.reserve(declared->size());
133 for (auto const& effect : *declared) {
134 std::string_view kind = "none";
135 if (effect.kind == ::morph::effects::Kind::Updates) {
136 kind = "updates";
137 } else if (effect.kind == ::morph::effects::Kind::Changes) {
138 kind = "changes";
139 } else if (effect.kind == ::morph::effects::Kind::ChangesInstance) {
140 kind = "changesInstance";
141 }
142 out.push_back(
143 ActionEffect{.kind = std::string{kind},
144 .record = effect.record.empty() ? std::nullopt : std::optional<std::string>{effect.record}});
145 }
146 // A record derived from a changed one changes as a whole too. The server expands derivations before an event
147 // leaves; stating them here as well lets the caller's own reply mark entries holding the derived record, which
148 // matters in process, where no event comes back.
150 for (auto const& effect : *declared) {
151 if (!effect.record.empty() &&
152 (effect.kind == ::morph::effects::Kind::Updates || effect.kind == ::morph::effects::Kind::Changes)) {
154 }
155 }
156 for (auto const& change : derived.records) {
157 ActionEffect added{.kind = "changes", .record = change.record};
158 if (std::ranges::find(out, added) == out.end()) {
159 out.push_back(std::move(added));
160 }
161 }
162 return out;
163}
164
165/// @brief The catalog's form of the records an action's result holds.
166/// @param actionId The action's id.
167/// @return One entry per record; empty when the result holds none.
168/// @throws morph::model::EffectsRegistrationError when the registered records and effects are invalid.
169[[nodiscard]] inline std::vector<ActionRecordInfo> recordsInfo(std::string_view actionId) {
170 std::vector<ActionRecordInfo> out;
171 for (auto const& held : ::morph::effects::recordsOf(actionId)) {
172 out.push_back(ActionRecordInfo{.record = held.record, .keyField = held.keyField, .paths = held.paths});
173 }
174 return out;
175}
176
177} // namespace detail
178
179/// @brief The catalog facts the builders gathered from the model and action types a screen names.
181 /// @brief Model facts by registered type id.
182 std::map<std::string, ModelInfo, std::less<>> models;
183 /// @brief Action facts by action id.
184 std::map<std::string, ActionInfo, std::less<>> actions;
185 /// @brief The `v-*` view documents `collection` nodes build from, by the action of their `v-query`.
186 std::map<std::string, std::string, std::less<>> views;
187 /// @brief The `w-*` wizard documents `wizard` nodes build from, by the action of their first step.
188 std::map<std::string, std::string, std::less<>> wizards;
189
190 /// @brief Adds another set of facts; a fact already present is kept.
191 /// @param other The facts to add.
192 void merge(CatalogFacts const& other) {
193 for (auto const& [type, info] : other.models) {
194 models.emplace(type, info);
195 }
196 for (auto const& [action, info] : other.actions) {
197 actions.emplace(action, info);
198 }
199 views.insert(other.views.begin(), other.views.end());
200 wizards.insert(other.wizards.begin(), other.wizards.end());
201 }
202
203 /// @brief Records a model type.
204 /// @tparam Model The model type; `::morph::model::ModelTraits<Model>` names its type id.
205 template <typename Model>
206 void model() {
207 models.emplace(
209 ModelInfo{.stateless = ModelFacts<Model>::stateless, .keyed = ::morph::model::KeyedModel<Model>});
210 }
211
212 /// @brief Records an action as one of a model type's.
213 /// @tparam Model The model type that runs it.
214 /// @tparam Action The action type; `::morph::model::ActionTraits<Action>` names its id.
215 /// @throws morph::model::EffectsRegistrationError when the registered records and effects are invalid: the
216 /// action's effects and records are read from the process registry here, so a misconfigured registry
217 /// fails at the start, not on a client's first watch.
218 template <typename Model, typename Action>
219 requires Runs<Model, Action>
220 void action() {
221 model<Model>();
222 auto const actionId = std::string{::morph::model::ActionTraits<Action>::typeId()};
223 actions.emplace(actionId, ActionInfo{
224 .modelType = std::string{::morph::model::ModelTraits<Model>::typeId()},
225 .calculatedFields = ActionFacts<Action>::calculatedFields(),
226 .secretFields = ActionFacts<Action>::secretFields(),
229 decltype(std::declval<Model&>().execute(std::declval<Action&>()))>>(),
230 .effects = detail::effectsInfo(actionId),
231 .records = detail::recordsInfo(actionId),
232 });
233 }
234
235 /// @brief The facts as a `ui::Catalog`.
236 /// @return Lookups over a copy of the facts, so the catalog outlives this object.
237 [[nodiscard]] Catalog catalog() const {
238 auto shared = std::make_shared<CatalogFacts const>(*this);
239 Catalog out;
240 out.model = [shared](std::string_view typeId) -> std::optional<ModelInfo> {
241 auto const found = shared->models.find(typeId);
242 if (found == shared->models.end()) {
243 return std::nullopt;
244 }
245 return found->second;
246 };
247 out.action = [shared](std::string_view actionId) -> std::optional<ActionInfo> {
248 auto const found = shared->actions.find(actionId);
249 if (found == shared->actions.end()) {
250 return std::nullopt;
251 }
252 return found->second;
253 };
254 // Left empty without documents, so a bundle that has none serves none.
255 if (!views.empty()) {
257 }
258 if (!wizards.empty()) {
260 }
261 return out;
262 }
263};
264
265template <typename T>
266class Handle;
267
268namespace detail {
269
270/// @brief Whether a type is a `std::optional`.
271/// @tparam T The type.
272template <typename T>
273struct IsOptional : std::false_type {};
274
275/// @brief A `std::optional` is one.
276/// @tparam T The engaged type.
277template <typename T>
278struct IsOptional<std::optional<T>> : std::true_type {};
279
280/// @brief A type with one `std::optional` removed.
281/// @tparam T The type.
282template <typename T>
283struct Unwrapped {
284 /// @brief The type itself.
285 using type = T;
286};
287
288/// @brief The engaged type of a `std::optional`.
289/// @tparam T The engaged type.
290template <typename T>
291struct Unwrapped<std::optional<T>> {
292 /// @brief The engaged type.
293 using type = T;
294};
295
296/// @brief The type with one `std::optional` removed.
297/// @tparam T The type.
298template <typename T>
300
301/// @brief Whether a type is a `std::vector`.
302/// @tparam T The type.
303template <typename T>
304struct IsVector : std::false_type {};
305
306/// @brief A `std::vector` is one.
307/// @tparam T The element type.
308/// @tparam Alloc The allocator.
309template <typename T, typename Alloc>
310struct IsVector<std::vector<T, Alloc>> : std::true_type {};
311
312/// @brief Whether a type is a `Handle`.
313/// @tparam T The type.
314template <typename T>
315struct IsHandle : std::false_type {};
316
317/// @brief A `Handle` is one.
318/// @tparam T The handle's value type.
319template <typename T>
320struct IsHandle<Handle<T>> : std::true_type {};
321
322/// @brief Whether a scalar value of one type can stand where a value of another is expected.
323/// @tparam From The value's type, without `std::optional`.
324/// @tparam To The expected type, without `std::optional`.
325/// @return True for `Any` on either side, the same type, or a conversion between them.
326template <typename From, typename To>
327[[nodiscard]] consteval bool scalarAssignable() {
328 if constexpr (std::same_as<From, Any> || std::same_as<To, Any>) {
329 return true;
330 } else {
331 return std::same_as<From, To> || std::is_convertible_v<From, To> || std::is_constructible_v<To, From>;
332 }
333}
334
335/// @brief Whether an expression of one value type can stand where a value of another is expected. `null`
336/// propagates through every expression, so an optional value may stand for a required one.
337/// @tparam From The expression's value type.
338/// @tparam To The expected value type.
339/// @return Whether it can.
340template <typename From, typename To>
341[[nodiscard]] consteval bool assignable() {
342 using Source = UnwrappedT<From>;
343 using Target = UnwrappedT<To>;
346 } else {
348 }
349}
350
351/// @brief Satisfied by a C++ scalar a builder writes as a document constant.
352template <typename L>
354 std::same_as<L, bool> || (std::integral<L> && !std::same_as<L, char>) ||
355 std::convertible_to<L const&, std::string_view> || std::same_as<L, math::Rational> || std::same_as<L, Decimal> ||
356 std::same_as<L, Quantity> || std::same_as<L, time::DateTime> || std::same_as<L, std::nullptr_t>;
357
358/// @brief Satisfied by a C++ value a builder writes as a document constant: a scalar, a list of scalars, or a
359/// `ui::Value`.
360template <typename L>
361concept Literal =
363
364/// @brief The value type a constant stands for.
365/// @tparam L The C++ type of the constant.
366template <typename L>
367struct LiteralOf {
368 /// @brief The constant's own type.
369 using type = L;
370};
371
372/// @brief Text is a `std::string`.
373/// @tparam N The literal's size.
374template <std::size_t N>
375// NOLINTNEXTLINE(modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): a string literal's own type.
376struct LiteralOf<char[N]> {
377 /// @brief `std::string`.
378 using type = std::string;
379};
380
381/// @brief A C string is a `std::string`.
382template <>
383struct LiteralOf<char const*> {
384 /// @brief `std::string`.
385 using type = std::string;
386};
387
388/// @brief A string view is a `std::string`.
389template <>
390struct LiteralOf<std::string_view> {
391 /// @brief `std::string`.
392 using type = std::string;
393};
394
395/// @brief The value type a constant stands for.
396/// @tparam L The C++ type of the constant.
397template <typename L>
399
400/// @brief The value type of an operand: a handle's value type, or the type its constant stands for.
401/// @tparam X The operand's C++ type.
402template <typename X>
403struct ValueOf {
404 /// @brief The constant's value type.
406};
407
408/// @brief A handle's value type.
409/// @tparam T The value type.
410template <typename T>
411struct ValueOf<Handle<T>> {
412 /// @brief The handle's value type.
413 using type = T;
414};
415
416/// @brief Satisfied by an operand of an expression operator: a handle or a constant.
417template <typename X>
419
420/// @brief Satisfied when two operands are operands and at least one is a handle, so the builders' operators never
421/// apply to two plain C++ values.
422template <typename L, typename R>
424
425/// @brief The value type of an arithmetic expression: the left handle's, else the right's.
426/// @tparam L The left operand's C++ type.
427/// @tparam R The right operand's C++ type.
428template <typename L, typename R>
429using ArithmeticT = std::conditional_t<IsHandle<L>::value, typename ValueOf<L>::type, typename ValueOf<R>::type>;
430
431/// @brief A C++ scalar as a document value.
432/// @tparam L The scalar's type.
433/// @param literal The scalar.
434/// @return The value.
435template <typename L>
436[[nodiscard]] Value scalarValue(L const& literal) {
437 if constexpr (std::same_as<L, bool> || std::same_as<L, Decimal> || std::same_as<L, Quantity> ||
438 std::same_as<L, time::DateTime> || std::same_as<L, std::nullptr_t>) {
439 return Value{literal};
440 } else if constexpr (std::same_as<L, math::Rational>) {
441 return Value{Decimal{literal}};
442 } else if constexpr (std::integral<L>) {
443 return Value{static_cast<std::int64_t>(literal)};
444 } else {
445 return Value{std::string{std::string_view{literal}}};
446 }
447}
448
449/// @brief A C++ constant as a document value.
450/// @tparam L The constant's type.
451/// @param literal The constant.
452/// @return The value.
453template <typename L>
454[[nodiscard]] Value literalValue(L const& literal) {
455 if constexpr (std::same_as<L, Value>) {
456 return literal;
457 } else if constexpr (IsVector<L>::value) {
458 ValueList items;
459 items.reserve(literal.size());
460 for (auto const& item : literal) {
461 items.push_back(scalarValue(item));
462 }
463 return Value{std::move(items)};
464 } else {
465 return scalarValue(literal);
466 }
467}
468
469/// @brief Sets a member of a JSON object, replacing one of the same name and keeping the order of the others.
470/// @param object The object.
471/// @param key The member's name.
472/// @param value The member's value.
473inline void put(Json& object, std::string_view key, Json value) {
474 std::get<Json::object_t>(object.data).insert_or_assign(std::string{key}, std::move(value));
475}
476
477/// @brief Sets a member of a JSON object to another object, unless that one is empty.
478/// @param object The object.
479/// @param key The member's name.
480/// @param section The value; an empty object is left out.
481inline void putSection(Json& object, std::string_view key, Json const& section) {
482 if (!std::get<Json::object_t>(section.data).empty()) {
483 put(object, key, section);
484 }
485}
486
487/// @brief How many members a JSON object has.
488/// @param object The object.
489/// @return The count.
490[[nodiscard]] inline std::size_t members(Json const& object) { return std::get<Json::object_t>(object.data).size(); }
491
492/// @brief A document value as a constant expression: a scalar as itself, a list, record, decimal or quantity
493/// wrapped in `{"lit": v}`.
494/// @param value The value.
495/// @return The expression's JSON.
496[[nodiscard]] inline Json constant(Value const& value) {
497 Json json = valueToJson(value);
498 if (std::holds_alternative<Json::object_t>(json.data) || std::holds_alternative<Json::array_t>(json.data)) {
499 Json wrapped = Json::object_t{};
500 detail::put(wrapped, "lit", std::move(json));
501 return wrapped;
502 }
503 return json;
504}
505
506/// @brief A one-key JSON object.
507/// @param key The key.
508/// @param value The value.
509/// @return `{key: value}`.
510[[nodiscard]] inline Json single(std::string_view key, Json value) {
511 Json json = Json::object_t{};
512 detail::put(json, key, std::move(value));
513 return json;
514}
515
516/// @brief An alignment's document name.
517/// @param align The alignment.
518/// @return `start`, `center`, `end` or `stretch`.
519[[nodiscard]] inline std::string alignName(Align align) {
520 static constexpr std::array<char const*, 4> kNames = {"stretch", "start", "center", "end"};
521 return kNames.at(static_cast<std::size_t>(align));
522}
523
524/// @brief A justification's document name.
525/// @param justify The justification.
526/// @return `start`, `center`, `end` or `between`.
527[[nodiscard]] inline std::string justifyName(Justify justify) {
528 static constexpr std::array<char const*, 4> kNames = {"start", "center", "end", "between"};
529 return kNames.at(static_cast<std::size_t>(justify));
530}
531
532/// @brief The value of a constant expression, as `constant` writes one.
533/// @param expression The expression's JSON.
534/// @return A scalar as itself, the inside of `{"lit": v}`; `nullopt` for an expression that is not a constant.
535[[nodiscard]] inline std::optional<Json> literalOf(Json const& expression) {
536 if (auto const* const object = std::get_if<Json::object_t>(&expression.data)) {
537 auto const* const found = object->find("lit");
538 if (object->size() == 1 && found != object->end()) {
539 return found->second;
540 }
541 return std::nullopt;
542 }
543 if (std::holds_alternative<Json::array_t>(expression.data)) {
544 return std::nullopt;
545 }
546 return expression;
547}
548
549/// @brief A call of an `expr/1` library function.
550/// @param function The function's name.
551/// @param args The argument expressions.
552/// @return `{"fn": function, "args": [...]}`.
553[[nodiscard]] inline Json call(std::string_view function, std::vector<Json> args) {
554 Json json = Json::object_t{};
555 detail::put(json, "fn", std::string{function});
556 detail::put(json, "args",
557 Json::array_t(std::make_move_iterator(args.begin()), std::make_move_iterator(args.end())));
558 return json;
559}
560
561/// @brief The dotted path a named read reads, when the expression is one.
562/// @param json The expression.
563/// @return The form (`ref` or `row`) and its path, or nothing for any other expression.
564[[nodiscard]] inline std::optional<std::pair<std::string, std::string>> namedRead(Json const& json) {
565 auto const* const object = std::get_if<Json::object_t>(&json.data);
566 if (object == nullptr || object->size() != 1) {
567 return std::nullopt;
568 }
569 auto const& [form, path] = *object->begin();
570 auto const* const text = std::get_if<std::string>(&path.data);
571 if (text == nullptr || (form != "ref" && form != "row")) {
572 return std::nullopt;
573 }
574 return std::pair{form, *text};
575}
576
577/// @brief A field read below a named read.
578/// @param json The named read.
579/// @param field The field.
580/// @return The read of `path.field`.
581/// @throws AuthoringError when @p json is not a named read: `expr/1` reads fields only below a name.
582[[nodiscard]] inline Json fieldOf(Json const& json, std::string_view field) {
583 auto read = namedRead(json);
584 if (!read) {
585 throw AuthoringError{
586 "a field is read below a name -- a param, state, let, query, row, element or bound "
587 "name -- and not below a computed value; field `" +
588 std::string{field} + "`"};
589 }
590 std::string path = read->second;
591 if (!path.empty()) {
592 path += '.';
593 }
594 path += field;
595 return single(read->first, std::move(path));
596}
597
598/// @brief The name a named read reads.
599/// @param json The expression.
600/// @param what What the name is for, for the error.
601/// @return The `ref` path.
602/// @throws AuthoringError when @p json is not a `ref`.
603[[nodiscard]] inline std::string refPath(Json const& json, std::string_view what) {
604 auto read = namedRead(json);
605 if (!read || read->first != "ref") {
606 throw AuthoringError{std::string{what} + " names a state, an element or a declared value"};
607 }
608 return read->second;
609}
610
611/// @brief The reflected name of a data member, found by its address in a value-initialised object of its class.
612/// @tparam Class The class; glaze reflects it.
613/// @tparam Member The member's type.
614/// @param member The member.
615/// @return The name the member has in the class's JSON.
616/// @throws AuthoringError when the member is not one glaze reflects.
617template <typename Class, typename Member>
618[[nodiscard]] std::string memberName(Member Class::* member) {
619 Class const probe{};
620 void const* const target = std::addressof(probe.*member);
621 std::string found;
622 [&]<std::size_t... Index>(std::index_sequence<Index...>) {
623 auto visit = [&]<std::size_t I>() {
624 decltype(auto) value = morph::detail::reflectedMember<I>(probe);
625 if constexpr (std::is_reference_v<decltype(value)>) {
626 if (static_cast<void const*>(std::addressof(value)) == target) {
627 found = std::string{glz::reflect<Class>::keys.at(I)};
628 }
629 }
630 };
631 (visit.template operator()<Index>(), ...);
632 }(std::make_index_sequence<glz::reflect<Class>::size>{});
633 if (found.empty()) {
634 throw AuthoringError{"a member pointer names a member its class's JSON does not have"};
635 }
636 return found;
637}
638
639} // namespace detail
640
641/// @brief An expression of a document, typed by the C++ value it evaluates to (spec 5 §11).
642///
643/// A handle is a JSON expression tree. Operators build calls of the `expr/1` library: `a + b` is `add`, `a < b`
644/// is `lt`, `a && b` is `and`, `!a` is `not`; `at` reads a field by member pointer, and `orElse` is `coalesce`. A
645/// default-constructed handle is absent: a builder leaves out the key it would fill.
646/// @tparam T The C++ type of the value, `Any` when the screen cannot know it.
647template <typename T = Any>
648class Handle {
649public:
650 /// @brief The C++ type of the value.
651 using ValueType = T;
652
653 /// @brief An absent handle.
654 Handle() = default;
655
656 /// @brief A constant.
657 /// @tparam L The constant's C++ type, which must be able to stand for `T`.
658 /// @param literal The constant.
659 template <typename L>
661 Handle(L const& literal) // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
662 : _json{std::make_shared<Json const>(detail::constant(detail::literalValue(normalise(literal))))} {}
663
664 /// @brief A handle of another value type that can stand for this one.
665 /// @tparam U The other value type.
666 /// @param other The handle.
667 template <typename U>
668 requires(!std::same_as<U, T>) && (detail::assignable<U, T>())
669 Handle(Handle<U> const& other) // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
670 : _json{other._json} {}
671
672 /// @brief A handle over an expression's JSON, for a form no builder spells.
673 /// @param json The expression.
674 /// @return The handle.
675 [[nodiscard]] static Handle raw(Json json) {
676 Handle out;
677 out._json = std::make_shared<Json const>(std::move(json));
678 return out;
679 }
680
681 /// @brief Whether the handle holds an expression.
682 /// @return False for a default-constructed handle.
683 [[nodiscard]] bool present() const noexcept { return _json != nullptr; }
684
685 /// @brief The expression's JSON.
686 /// @return The JSON.
687 /// @throws AuthoringError when the handle is absent.
688 [[nodiscard]] Json const& json() const {
689 if (_json == nullptr) {
690 throw AuthoringError{"an absent expression is used as an operand"};
691 }
692 return *_json;
693 }
694
695 /// @brief A field of the value, by member pointer: a renamed field is a compile error.
696 /// @tparam Class The class of the member, the value's type.
697 /// @tparam Member The member's type.
698 /// @param member The member.
699 /// @return The field read.
700 /// @throws AuthoringError when the handle is not a named read.
701 template <typename Class, typename Member>
702 requires std::same_as<T, Any> || std::same_as<detail::UnwrappedT<T>, Class>
703 [[nodiscard]] Handle<Member> at(Member Class::* member) const {
705 }
706
707 /// @brief A field of the value, by name.
708 /// @param field The field, or a dotted path.
709 /// @return The field read.
710 /// @throws AuthoringError when the handle is not a named read.
711 [[nodiscard]] Handle<Any> at(std::string_view field) const {
712 return Handle<Any>::raw(detail::fieldOf(json(), field));
713 }
714
715 /// @brief The value, or a fallback when it is `null`.
716 /// @param fallback The fallback.
717 /// @return `coalesce(this, fallback)`.
718 [[nodiscard]] Handle orElse(Handle const& fallback) const {
719 return raw(detail::call("coalesce", {json(), fallback.json()}));
720 }
721
722private:
723 template <typename U>
724 friend class Handle;
725
726 template <typename L>
727 [[nodiscard]] static auto normalise(L const& literal) {
728 if constexpr (std::convertible_to<L const&, std::string_view> && !std::same_as<L, std::nullptr_t>) {
729 return std::string{std::string_view{literal}};
730 } else {
731 return literal;
732 }
733 }
734
735 std::shared_ptr<Json const> _json;
736};
737
738/// @brief An expression whose value type is not tracked: the type of every node property.
740
741namespace detail {
742
743/// @brief An operand's JSON.
744/// @tparam X The operand's C++ type.
745/// @param operand The operand.
746/// @return The JSON.
747template <typename X>
748[[nodiscard]] Json operandJson(X const& operand) {
749 if constexpr (IsHandle<X>::value) {
750 return operand.json();
751 } else {
752 return Handle<LiteralT<X>>{operand}.json();
753 }
754}
755
756/// @brief A two-operand call.
757/// @tparam Result The result's value type.
758/// @tparam L The left operand's C++ type.
759/// @tparam R The right operand's C++ type.
760/// @param function The library function.
761/// @param lhs The left operand.
762/// @param rhs The right operand.
763/// @return The call.
764template <typename Result, typename L, typename R>
765[[nodiscard]] Handle<Result> binary(std::string_view function, L const& lhs, R const& rhs) {
766 return Handle<Result>::raw(call(function, {operandJson(lhs), operandJson(rhs)}));
767}
768
769} // namespace detail
770
771/// @brief `add`.
772/// @tparam L The left operand's C++ type.
773/// @tparam R The right operand's C++ type.
774/// @param lhs The left operand.
775/// @param rhs The right operand.
776/// @return The sum.
777template <typename L, typename R>
778 requires detail::HandleOperands<L, R>
779[[nodiscard]] Handle<detail::ArithmeticT<L, R>> operator+(L const& lhs, R const& rhs) {
780 return detail::binary<detail::ArithmeticT<L, R>>("add", lhs, rhs);
781}
782
783/// @brief `sub`.
784/// @tparam L The left operand's C++ type.
785/// @tparam R The right operand's C++ type.
786/// @param lhs The left operand.
787/// @param rhs The right operand.
788/// @return The difference.
789template <typename L, typename R>
790 requires detail::HandleOperands<L, R>
791[[nodiscard]] Handle<detail::ArithmeticT<L, R>> operator-(L const& lhs, R const& rhs) {
792 return detail::binary<detail::ArithmeticT<L, R>>("sub", lhs, rhs);
793}
794
795/// @brief `mul`.
796/// @tparam L The left operand's C++ type.
797/// @tparam R The right operand's C++ type.
798/// @param lhs The left operand.
799/// @param rhs The right operand.
800/// @return The product.
801template <typename L, typename R>
802 requires detail::HandleOperands<L, R>
803[[nodiscard]] Handle<detail::ArithmeticT<L, R>> operator*(L const& lhs, R const& rhs) {
804 return detail::binary<detail::ArithmeticT<L, R>>("mul", lhs, rhs);
805}
806
807/// @brief `div`.
808/// @tparam L The left operand's C++ type.
809/// @tparam R The right operand's C++ type.
810/// @param lhs The left operand.
811/// @param rhs The right operand.
812/// @return The exact quotient.
813template <typename L, typename R>
814 requires detail::HandleOperands<L, R>
815[[nodiscard]] Handle<detail::ArithmeticT<L, R>> operator/(L const& lhs, R const& rhs) {
816 return detail::binary<detail::ArithmeticT<L, R>>("div", lhs, rhs);
817}
818
819/// @brief `lt`.
820/// @tparam L The left operand's C++ type.
821/// @tparam R The right operand's C++ type.
822/// @param lhs The left operand.
823/// @param rhs The right operand.
824/// @return Whether the left is less.
825template <typename L, typename R>
826 requires detail::HandleOperands<L, R>
827[[nodiscard]] Handle<bool> operator<(L const& lhs, R const& rhs) {
828 return detail::binary<bool>("lt", lhs, rhs);
829}
830
831/// @brief `le`.
832/// @tparam L The left operand's C++ type.
833/// @tparam R The right operand's C++ type.
834/// @param lhs The left operand.
835/// @param rhs The right operand.
836/// @return Whether the left is less or equal.
837template <typename L, typename R>
838 requires detail::HandleOperands<L, R>
839[[nodiscard]] Handle<bool> operator<=(L const& lhs, R const& rhs) {
840 return detail::binary<bool>("le", lhs, rhs);
841}
842
843/// @brief `gt`.
844/// @tparam L The left operand's C++ type.
845/// @tparam R The right operand's C++ type.
846/// @param lhs The left operand.
847/// @param rhs The right operand.
848/// @return Whether the left is greater.
849template <typename L, typename R>
850 requires detail::HandleOperands<L, R>
851[[nodiscard]] Handle<bool> operator>(L const& lhs, R const& rhs) {
852 return detail::binary<bool>("gt", lhs, rhs);
853}
854
855/// @brief `ge`.
856/// @tparam L The left operand's C++ type.
857/// @tparam R The right operand's C++ type.
858/// @param lhs The left operand.
859/// @param rhs The right operand.
860/// @return Whether the left is greater or equal.
861template <typename L, typename R>
862 requires detail::HandleOperands<L, R>
863[[nodiscard]] Handle<bool> operator>=(L const& lhs, R const& rhs) {
864 return detail::binary<bool>("ge", lhs, rhs);
865}
866
867/// @brief `and`, with Kleene's three-valued logic. Both operands are written: a document has no short circuit.
868/// @tparam L The left operand's C++ type.
869/// @tparam R The right operand's C++ type.
870/// @param lhs The left operand.
871/// @param rhs The right operand.
872/// @return The conjunction.
873template <typename L, typename R>
874 requires detail::HandleOperands<L, R>
875[[nodiscard]] Handle<bool> operator&&(L const& lhs, R const& rhs) {
876 return detail::binary<bool>("and", lhs, rhs);
877}
878
879/// @brief `or`, with Kleene's three-valued logic.
880/// @tparam L The left operand's C++ type.
881/// @tparam R The right operand's C++ type.
882/// @param lhs The left operand.
883/// @param rhs The right operand.
884/// @return The disjunction.
885template <typename L, typename R>
886 requires detail::HandleOperands<L, R>
887[[nodiscard]] Handle<bool> operator||(L const& lhs, R const& rhs) {
888 return detail::binary<bool>("or", lhs, rhs);
889}
890
891/// @brief `not`.
892/// @tparam T The operand's value type.
893/// @param operand The operand.
894/// @return The negation.
895template <typename T>
896[[nodiscard]] Handle<bool> operator!(Handle<T> const& operand) {
897 return Handle<bool>::raw(detail::call("not", {operand.json()}));
898}
899
900/// @brief `neg`.
901/// @tparam T The operand's value type.
902/// @param operand The operand.
903/// @return The negated number.
904template <typename T>
905[[nodiscard]] Handle<T> operator-(Handle<T> const& operand) {
906 return Handle<T>::raw(detail::call("neg", {operand.json()}));
907}
908
909/// @brief `eq`: equality of magnitude. A function, not `==`: an `==` that returns an expression rather than a
910/// `bool` breaks the comparisons C++ rewrites from it.
911/// @tparam L The left operand's C++ type.
912/// @tparam R The right operand's C++ type.
913/// @param lhs The left operand.
914/// @param rhs The right operand.
915/// @return Whether they are equal.
916template <typename L, typename R>
917 requires detail::HandleOperands<L, R>
918[[nodiscard]] Handle<bool> eq(L const& lhs, R const& rhs) {
919 return detail::binary<bool>("eq", lhs, rhs);
920}
921
922/// @brief `ne`.
923/// @tparam L The left operand's C++ type.
924/// @tparam R The right operand's C++ type.
925/// @param lhs The left operand.
926/// @param rhs The right operand.
927/// @return Whether they differ.
928template <typename L, typename R>
929 requires detail::HandleOperands<L, R>
930[[nodiscard]] Handle<bool> ne(L const& lhs, R const& rhs) {
931 return detail::binary<bool>("ne", lhs, rhs);
932}
933
934/// @brief A call of any `expr/1` library function, for the functions without a builder of their own.
935/// @tparam R The result's value type.
936/// @param function The function's name.
937/// @param args The arguments.
938/// @return The call.
939template <typename R = Any>
940[[nodiscard]] Handle<R> call(std::string_view function, std::vector<AnyHandle> const& args) {
941 std::vector<Json> json;
942 json.reserve(args.size());
943 for (AnyHandle const& arg : args) {
944 json.push_back(arg.json());
945 }
946 return Handle<R>::raw(detail::call(function, std::move(json)));
947}
948
949/// @brief `engaged`: whether a value is not `null`.
950/// @param value The value.
951/// @return The test.
952[[nodiscard]] inline Handle<bool> engaged(AnyHandle const& value) { return call<bool>("engaged", {value}); }
953
954/// @brief `coalesce`: the first value that is not `null`.
955/// @param values The candidates, in order.
956/// @return The call.
957[[nodiscard]] inline AnyHandle coalesce(std::vector<AnyHandle> const& values) { return call("coalesce", values); }
958
959/// @brief `concat`.
960/// @param parts The strings.
961/// @return The joined string.
962[[nodiscard]] inline Handle<std::string> concat(std::vector<AnyHandle> const& parts) {
963 return call<std::string>("concat", parts);
964}
965
966/// @brief `format`: a template with `{0}`, `{1}`, … placeholders.
967/// @param pattern The template.
968/// @param args The values for the placeholders.
969/// @return The formatted string.
970[[nodiscard]] inline Handle<std::string> format(AnyHandle const& pattern, std::vector<AnyHandle> args) {
971 std::vector<AnyHandle> operands;
972 operands.reserve(args.size() + 1);
973 operands.push_back(pattern);
974 std::ranges::move(args, std::back_inserter(operands));
975 return call<std::string>("format", operands);
976}
977
978/// @brief `concatLists`: the lists, one after another; `null` while any of them is `null`.
979/// @param lists The lists, in order.
980/// @return The joined list.
981[[nodiscard]] inline AnyHandle concatLists(std::vector<AnyHandle> const& lists) { return call("concatLists", lists); }
982
983/// @brief `concatLists` of lists of one element type.
984/// @tparam T The element type.
985/// @param lists The lists, in order.
986/// @return The joined list.
987template <typename T>
988[[nodiscard]] Handle<std::vector<T>> concatLists(std::vector<Handle<std::vector<T>>> const& lists) {
989 std::vector<Json> json;
990 json.reserve(lists.size());
991 for (Handle<std::vector<T>> const& list : lists) {
992 json.push_back(list.json());
993 }
994 return Handle<std::vector<T>>::raw(detail::call("concatLists", std::move(json)));
995}
996
997/// @brief `count`: a list's length.
998/// @param list The list.
999/// @return The count.
1000[[nodiscard]] inline Handle<std::int64_t> count(AnyHandle const& list) { return call<std::int64_t>("count", {list}); }
1001
1002/// @brief `sum` of a list of numbers.
1003/// @param list The list.
1004/// @return The exact sum.
1005[[nodiscard]] inline AnyHandle sum(AnyHandle const& list) { return call("sum", {list}); }
1006
1007/// @brief `includes`: whether a list holds a value.
1008/// @param list The list.
1009/// @param value The value.
1010/// @return The test.
1011[[nodiscard]] inline Handle<bool> includes(AnyHandle const& list, AnyHandle const& value) {
1012 return call<bool>("includes", {list, value});
1013}
1014
1015/// @brief `isEmpty`: an empty string or list.
1016/// @param value The string or list.
1017/// @return The test.
1018[[nodiscard]] inline Handle<bool> isEmpty(AnyHandle const& value) { return call<bool>("isEmpty", {value}); }
1019
1020/// @brief `round(x, dp)`, half to even.
1021/// @param value The number.
1022/// @param places The places.
1023/// @return The rounded number.
1024[[nodiscard]] inline AnyHandle round(AnyHandle const& value, std::int64_t places) {
1025 return call("round", {value, places});
1026}
1027
1028/// @brief `parseDecimal(text)`: typed text, in the client's locale, as an exact decimal; `null` while it does not
1029/// spell one.
1030/// @param text The text, typically a text input's `event`.
1031/// @return The decimal, at the places typed.
1032[[nodiscard]] inline AnyHandle parseDecimal(AnyHandle const& text) { return call("parseDecimal", {text}); }
1033
1034/// @brief `parseDecimal(text, dp)`: typed text as an exact decimal at @p places; `null` while it does not spell one,
1035/// or when it has more fraction digits than @p places.
1036/// @param text The text, typically a text input's `event`.
1037/// @param places The places, 0 to 18.
1038/// @return The decimal.
1039[[nodiscard]] inline AnyHandle parseDecimal(AnyHandle const& text, std::int64_t places) {
1040 return call("parseDecimal", {text, places});
1041}
1042
1043/// @brief `quantity(x)`: a quantity for display in its own unit.
1044/// @param value The quantity.
1045/// @return The text.
1046[[nodiscard]] inline Handle<std::string> quantity(AnyHandle const& value) {
1047 return call<std::string>("quantity", {value});
1048}
1049
1050/// @brief `quantity(x, unit)`: a quantity for display in another unit.
1051/// @param value The quantity.
1052/// @param unit The display unit.
1053/// @return The text.
1054[[nodiscard]] inline Handle<std::string> quantity(AnyHandle const& value, AnyHandle const& unit) {
1055 return call<std::string>("quantity", {value, unit});
1056}
1057
1058/// @brief `t(key, fallback, args)`: a translated MessageFormat 2 message.
1059/// @param key The catalogue key.
1060/// @param fallback The pattern shown when the catalogue has none.
1061/// @param args The named arguments.
1062/// @return The `{"t": ...}` form.
1063[[nodiscard]] inline Handle<std::string> t(std::string const& key, std::string const& fallback,
1064 std::vector<std::pair<std::string, AnyHandle>> const& args = {}) {
1065 Json json = Json::object_t{};
1066 detail::put(json, "t", key);
1067 detail::put(json, "fallback", fallback);
1068 if (!args.empty()) {
1069 Json record = Json::object_t{};
1070 for (auto const& [name, value] : args) {
1071 detail::put(record, name, value.json());
1072 }
1073 detail::put(json, "args", std::move(record));
1074 }
1075 return Handle<std::string>::raw(std::move(json));
1076}
1077
1078/// @brief `{"if": [c, a, b]}`: a choice between two values.
1079/// @tparam T The value type of the branches.
1080/// @param condition The condition.
1081/// @param then The value when it is true.
1082/// @param otherwise The value otherwise.
1083/// @return The choice.
1084template <typename T>
1085[[nodiscard]] Handle<T> cond(Handle<bool> const& condition, Handle<T> const& then, Handle<T> const& otherwise) {
1086 Json json = Json::object_t{};
1087 detail::put(json, "if", Json::array_t{condition.json(), then.json(), otherwise.json()});
1088 return Handle<T>::raw(std::move(json));
1089}
1090
1091/// @brief `{"obj": {...}}`: a record built from expressions.
1092/// @param fields The fields, in order.
1093/// @return The record.
1094[[nodiscard]] inline AnyHandle obj(std::vector<std::pair<std::string, AnyHandle>> const& fields) {
1095 Json record = Json::object_t{};
1096 for (auto const& [name, value] : fields) {
1097 detail::put(record, name, value.json());
1098 }
1099 return AnyHandle::raw(detail::single("obj", std::move(record)));
1100}
1101
1102/// @brief The content sizing, `"content"`: as much as the content needs.
1103/// @return The sizing, a constant the loader checks.
1104[[nodiscard]] inline AnyHandle content() { return AnyHandle{std::string{"content"}}; }
1105
1106/// @brief A fixed sizing, `{"lit": {"fixed": units}}`.
1107/// @param units The size in backend units, 0 or more.
1108/// @return The sizing, a constant the loader checks.
1109[[nodiscard]] inline AnyHandle fixed(int units) {
1110 return AnyHandle::raw(detail::constant(Value{ValueRecord{{"fixed", Value{std::int64_t{units}}}}}));
1111}
1112
1113/// @brief A stretch sizing, `{"lit": {"stretch": weight}}`: a share of the leftover space.
1114/// @param weight The share's weight, 1 or more.
1115/// @return The sizing, a constant the loader checks.
1116[[nodiscard]] inline AnyHandle stretch(int weight = 1) {
1117 return AnyHandle::raw(detail::constant(Value{ValueRecord{{"stretch", Value{std::int64_t{weight}}}}}));
1118}
1119
1120/// @brief `{"list": [...]}`: a list built from expressions.
1121/// @param items The items.
1122/// @return The list.
1123[[nodiscard]] inline AnyHandle list(std::vector<AnyHandle> const& items) {
1124 Json::array_t array;
1125 array.reserve(items.size());
1126 for (AnyHandle const& item : items) {
1127 array.push_back(item.json());
1128 }
1129 return AnyHandle::raw(detail::single("list", std::move(array)));
1130}
1131
1132/// @brief A named read, `{"ref": "name.path"}`: a param, state, `let`, query value, element value or bound name.
1133/// @tparam T The value type.
1134/// @param path The name and path.
1135/// @return The read.
1136template <typename T = Any>
1137[[nodiscard]] Handle<T> ref(std::string const& path) {
1138 return Handle<T>::raw(detail::single("ref", path));
1139}
1140
1141/// @brief The innermost row, `{"row": ""}`.
1142/// @tparam T The row's type.
1143/// @return The read.
1144template <typename T = Any>
1145[[nodiscard]] Handle<T> row() {
1146 return Handle<T>::raw(detail::single("row", std::string{}));
1147}
1148
1149/// @brief A field of the innermost row, by member pointer.
1150/// @tparam Class The row type.
1151/// @tparam Member The member's type.
1152/// @param member The member.
1153/// @return The read.
1154template <typename Class, typename Member>
1155[[nodiscard]] Handle<Member> row(Member Class::* member) {
1157}
1158
1159/// @brief A field of the innermost row, by name.
1160/// @param path The field or dotted path.
1161/// @return The read.
1162[[nodiscard]] inline AnyHandle row(std::string const& path) { return AnyHandle::raw(detail::single("row", path)); }
1163
1164/// @brief A comprehension over a list, `{"map": e, "as": "x", "to": e}`.
1165/// @tparam E The element type.
1166/// @param source The list.
1167/// @param name The element's name inside @p body.
1168/// @param body Builds the mapped value from the element's handle.
1169/// @return The mapped list.
1170template <typename E = Any>
1171[[nodiscard]] AnyHandle map(AnyHandle const& source, std::string const& name,
1172 std::type_identity_t<std::function<AnyHandle(Handle<E> const&)>> const& body) {
1173 Json json = Json::object_t{};
1174 detail::put(json, "map", source.json());
1175 detail::put(json, "as", name);
1176 detail::put(json, "to", body(author::ref<E>(name)).json());
1177 return AnyHandle::raw(std::move(json));
1178}
1179
1180/// @brief A filter over a list, `{"filter": e, "as": "x", "where": e}`.
1181/// @tparam E The element type.
1182/// @param source The list.
1183/// @param name The element's name inside @p where.
1184/// @param where Builds the condition from the element's handle.
1185/// @return The filtered list.
1186template <typename E = Any>
1187[[nodiscard]] AnyHandle filter(AnyHandle const& source, std::string const& name,
1188 std::type_identity_t<std::function<Handle<bool>(Handle<E> const&)>> const& where) {
1189 Json json = Json::object_t{};
1190 detail::put(json, "filter", source.json());
1191 detail::put(json, "as", name);
1192 detail::put(json, "where", where(author::ref<E>(name)).json());
1193 return AnyHandle::raw(std::move(json));
1194}
1195
1196/// @brief The elements of a list whose boolean member is true.
1197/// @tparam Class The element type.
1198/// @param source The list.
1199/// @param member The member.
1200/// @return The filtered list.
1201template <typename Class>
1202[[nodiscard]] AnyHandle filter(AnyHandle const& source, bool Class::* member) {
1203 return filter<Class>(source, "item", [member](Handle<Class> const& item) { return item.at(member); });
1204}
1205
1206/// @brief A client environment value, `{"env": name}`: `widthClass`, `locale`, `platform`, `online`, `backend`.
1207/// @param name The name.
1208/// @return The read.
1209[[nodiscard]] inline AnyHandle env(std::string const& name) { return AnyHandle::raw(detail::single("env", name)); }
1210
1211/// @brief A session attribute, `{"session": name}`.
1212/// @param name The attribute.
1213/// @return The read.
1214[[nodiscard]] inline AnyHandle session(std::string const& name) {
1215 return AnyHandle::raw(detail::single("session", name));
1216}
1217
1218/// @brief A value the application registered with the client, `{"host": name}`.
1219/// @param name The value's name.
1220/// @return The read; `null` on a client without it.
1221[[nodiscard]] inline AnyHandle hostValue(std::string const& name) {
1222 return AnyHandle::raw(detail::single("host", name));
1223}
1224
1225/// @brief A prop of a component, inside the component's fallback.
1226/// @param name The prop.
1227/// @return `{"ref": "props.name"}`.
1228[[nodiscard]] inline AnyHandle prop(std::string const& name) { return author::ref("props." + name); }
1229
1230/// @brief The payload of the event whose command list runs.
1231/// @return `{"ref": "event"}`.
1232[[nodiscard]] inline AnyHandle event() { return author::ref("event"); }
1233
1234/// @brief An element of the view, named by its `id`, whose live values relations read (spec 5 §6).
1235class Element {
1236public:
1237 /// @brief An element.
1238 /// @param name The element's id.
1239 explicit Element(std::string name) : _name{std::move(name)} {}
1240
1241 /// @brief The element's id.
1242 /// @return The id.
1243 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1244
1245 /// @brief One of the element's values.
1246 /// @param value The value's name, or a dotted path such as a form field's `mass.value`.
1247 /// @return The read.
1248 [[nodiscard]] AnyHandle get(std::string const& value) const { return author::ref(_name + "." + value); }
1249
1250 /// @brief An input's committed value.
1251 /// @return The read.
1252 [[nodiscard]] AnyHandle value() const { return get("value"); }
1253
1254 /// @brief An input's text while it is edited.
1255 /// @return The read.
1256 [[nodiscard]] AnyHandle draft() const { return get("draft"); }
1257
1258 /// @brief A checkbox's state.
1259 /// @return The read.
1260 [[nodiscard]] Handle<bool> checked() const { return author::ref<bool>(_name + ".checked"); }
1261
1262 /// @brief A select's chosen key.
1263 /// @return The read.
1264 [[nodiscard]] AnyHandle selected() const { return get("selected"); }
1265
1266 /// @brief A table's selection.
1267 /// @return The read.
1268 [[nodiscard]] AnyHandle selection() const { return get("selection"); }
1269
1270 /// @brief Whether a dialog or drawer is open.
1271 /// @return The read.
1272 [[nodiscard]] Handle<bool> open() const { return author::ref<bool>(_name + ".open"); }
1273
1274 /// @brief Whether a form, dialog or element is valid.
1275 /// @return The read.
1276 [[nodiscard]] Handle<bool> valid() const { return author::ref<bool>(_name + ".valid"); }
1277
1278 /// @brief Whether a form or dialog is ready to submit.
1279 /// @return The read.
1280 [[nodiscard]] Handle<bool> ready() const { return author::ref<bool>(_name + ".ready"); }
1281
1282private:
1283 std::string _name;
1284};
1285
1286/// @brief An element of the view.
1287/// @param name The element's id.
1288/// @return The element.
1289[[nodiscard]] inline Element element(std::string name) { return Element{std::move(name)}; }
1290
1291/// @brief The dialog or drawer a command list runs in, for `close`.
1292struct Self {};
1293
1294/// @brief The dialog or drawer a command list runs in.
1295inline constexpr Self self{};
1296
1297/// @brief A query of a scope, without its action type: what `refetch`, `prefetch` and `optimistic` name.
1299public:
1300 /// @brief A query by name.
1301 /// @param name The query's name.
1302 explicit QueryRef(std::string name) : _name{std::move(name)} {}
1303
1304 /// @brief The query's name.
1305 /// @return The name.
1306 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1307
1308private:
1309 std::string _name;
1310};
1311
1312/// @brief A mutation of a scope, without its action type: what `signIn`, `upload` and `can` name.
1314public:
1315 /// @brief A mutation by name.
1316 /// @param name The mutation's name.
1317 explicit MutationRef(std::string name) : _name{std::move(name)} {}
1318
1319 /// @brief The mutation's name.
1320 /// @return The name.
1321 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1322
1323private:
1324 std::string _name;
1325};
1326
1327/// @brief A query declared by a scope.
1328/// @tparam Action The action it runs.
1329/// @tparam Value The value's C++ type: the action's result for a call, the action itself for an evaluation.
1330// NOLINTNEXTLINE(readability-redundant-typename): MSVC needs it on a dependent default argument (C4346).
1331template <typename Action, typename Value = typename ::morph::model::ActionTraits<Action>::Result>
1333public:
1334 /// @brief The value's C++ type.
1335 using Result = Value;
1336
1337 /// @brief A query by name.
1338 /// @param name The query's name.
1339 explicit QueryHandle(std::string name) : _name{std::move(name)} {}
1340
1341 /// @brief The query's name.
1342 /// @return The name.
1343 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1344
1345 /// @brief The query's value; the last one is kept while a refetch is in flight.
1346 /// @return `{"ref": name}`.
1347 [[nodiscard]] Handle<Result> value() const { return author::ref<Result>(_name); }
1348
1349 /// @brief A field of the query's value.
1350 /// @tparam Class The result type.
1351 /// @tparam Member The member's type.
1352 /// @param member The member of the result type.
1353 /// @return The field read.
1354 template <typename Class, typename Member>
1355 requires std::same_as<Class, detail::UnwrappedT<Result>>
1356 [[nodiscard]] Handle<Member> at(Member Class::* member) const {
1357 return value().at(member);
1358 }
1359
1360 /// @brief Whether the query is in flight.
1361 /// @return `{"pending": name}`.
1362 [[nodiscard]] Handle<bool> pending() const { return Handle<bool>::raw(detail::single("pending", _name)); }
1363
1364 /// @brief The query's error text.
1365 /// @return `{"error": name}`.
1366 [[nodiscard]] Handle<std::string> error() const {
1367 return Handle<std::string>::raw(detail::single("error", _name));
1368 }
1369
1370 /// @brief The kind of the query's error.
1371 /// @return `{"errorKind": name}`.
1372 [[nodiscard]] Handle<std::string> errorKind() const {
1373 return Handle<std::string>::raw(detail::single("errorKind", _name));
1374 }
1375
1376 /// @brief Whether the signed-in principal may run the query.
1377 /// @return `{"can": name}`.
1378 [[nodiscard]] Handle<bool> can() const { return Handle<bool>::raw(detail::single("can", _name)); }
1379
1380 /// @brief The query without its action type.
1381 /// @return The reference.
1382 operator QueryRef() const { return QueryRef{_name}; }
1383
1384private:
1385 std::string _name;
1386};
1387
1388/// @brief A mutation's result: a `Handle` with no field reads.
1389///
1390/// A result is not a named read, so a field of it cannot be read in place: the screen binds it first,
1391/// `auto r = screen.let("r", m.result());`, and reads `r.at(...)`. The `at` overloads are deleted, so the mistake is
1392/// a compile error at the call rather than a refusal when the screen is registered. It converts to `Handle<T>`
1393/// wherever a handle is taken.
1394/// @tparam T The result's C++ type.
1395template <typename T>
1396class ResultHandle : public Handle<T> {
1397public:
1398 /// @brief The result read.
1399 /// @param handle The `{"result": name}` expression.
1400 explicit ResultHandle(Handle<T> const& handle) : Handle<T>{handle} {}
1401
1402 /// @brief Not available: bind the result with `let` first, `auto r = screen.let("r", m.result());`, then read
1403 /// `r.at(...)`.
1404 /// @tparam Class The class of the member.
1405 /// @tparam Member The member's type.
1406 /// @param member The member.
1407 /// @return Nothing; the overload is deleted.
1408 template <typename Class, typename Member>
1409 [[nodiscard]] Handle<Member> at(Member Class::* member) const = delete;
1410
1411 /// @brief Not available: bind the result with `let` first, `auto r = screen.let("r", m.result());`, then read
1412 /// `r.at(...)`.
1413 /// @param field The field.
1414 /// @return Nothing; the overload is deleted.
1415 [[nodiscard]] Handle<Any> at(std::string_view field) const = delete;
1416};
1417
1418/// @brief A mutation declared by a scope.
1419/// @tparam Action The action it runs; `::morph::model::ActionTraits<Action>::Result` is the result's type.
1420template <typename Action>
1422public:
1423 /// @brief The result's C++ type.
1425
1426 /// @brief A mutation by name.
1427 /// @param name The mutation's name.
1428 explicit MutationHandle(std::string name) : _name{std::move(name)} {}
1429
1430 /// @brief The mutation's name.
1431 /// @return The name.
1432 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1433
1434 /// @brief The last run's result; bind it with `let` to read its fields.
1435 /// @return `{"result": name}`, with no `at`.
1436 [[nodiscard]] ResultHandle<Result> result() const {
1438 }
1439
1440 /// @brief Whether a run is in flight.
1441 /// @return `{"pending": name}`.
1442 [[nodiscard]] Handle<bool> pending() const { return Handle<bool>::raw(detail::single("pending", _name)); }
1443
1444 /// @brief The last run's error text.
1445 /// @return `{"error": name}`.
1446 [[nodiscard]] Handle<std::string> error() const {
1447 return Handle<std::string>::raw(detail::single("error", _name));
1448 }
1449
1450 /// @brief The kind of the last run's error.
1451 /// @return `{"errorKind": name}`.
1452 [[nodiscard]] Handle<std::string> errorKind() const {
1453 return Handle<std::string>::raw(detail::single("errorKind", _name));
1454 }
1455
1456 /// @brief Whether the signed-in principal may run the mutation.
1457 /// @return `{"can": name}`.
1458 [[nodiscard]] Handle<bool> can() const { return Handle<bool>::raw(detail::single("can", _name)); }
1459
1460 /// @brief The mutation without its action type.
1461 /// @return The reference.
1462 operator MutationRef() const { return MutationRef{_name}; }
1463
1464private:
1465 std::string _name;
1466};
1467
1468/// @brief One field of an action body: a member of the action, and the expression that fills it.
1469/// @tparam Action The action.
1470template <typename Action>
1472public:
1473 /// @brief A field: a member pointer of the action, so a renamed field or another action's field is a compile
1474 /// error, and a value that can stand for the member's type.
1475 /// @tparam Member The member's type.
1476 /// @tparam V The value's C++ type: a handle or a constant.
1477 /// @param member The member.
1478 /// @param value The value.
1479 template <typename Member, typename V>
1481 BodyField(Member Action::* member, V const& value)
1482 : _name{detail::memberName(member)}, _json(detail::operandJson(value)) {}
1483
1484 /// @brief The field's JSON name.
1485 /// @return The name.
1486 [[nodiscard]] std::string const& name() const noexcept { return _name; }
1487
1488 /// @brief The field's expression.
1489 /// @return The JSON.
1490 [[nodiscard]] Json const& json() const noexcept { return _json; }
1491
1492private:
1493 std::string _name;
1494 Json _json = Json::object_t{};
1495};
1496
1497/// @brief An action body: the action's members with expressions in place of values (spec 5 §11). C++ cannot
1498/// synthesise the action's struct with its members retyped, so a body lists `{&Action::member, value}` pairs.
1499/// @tparam Action The action.
1500template <typename Action>
1501class Body {
1502public:
1503 /// @brief An empty body; the query or mutation sends an empty object.
1504 Body() = default;
1505
1506 /// @brief A body.
1507 /// @param fields The fields, in order.
1508 Body(std::initializer_list<BodyField<Action>> fields) : _fields{fields} {}
1509
1510 /// @brief Whether the body has no fields.
1511 /// @return True when it has none.
1512 [[nodiscard]] bool empty() const noexcept { return _fields.empty(); }
1513
1514 /// @brief The body's expression.
1515 /// @return `{"obj": {...}}`.
1516 [[nodiscard]] Json json() const {
1517 Json record = Json::object_t{};
1518 for (BodyField<Action> const& field : _fields) {
1519 detail::put(record, field.name(), field.json());
1520 }
1521 return detail::single("obj", std::move(record));
1522 }
1523
1524private:
1525 std::vector<BodyField<Action>> _fields;
1526};
1527
1528/// @brief A model alias of a scope.
1529/// @tparam Model The model type.
1530template <typename Model>
1532public:
1533 /// @brief An alias.
1534 /// @param alias The alias.
1535 explicit ModelHandle(std::string alias) : _alias{std::move(alias)} {}
1536
1537 /// @brief The alias.
1538 /// @return The alias.
1539 [[nodiscard]] std::string const& alias() const noexcept { return _alias; }
1540
1541private:
1542 std::string _alias;
1543};
1544
1545/// @brief How a model alias binds when no key expression is given (spec 5 §5).
1546enum class Instance : std::uint8_t {
1547 Default, ///< `shared` when the catalog marks the model stateless, `private` otherwise.
1548 Private, ///< An instance of the scope's own.
1549 Shared, ///< The model's singleton.
1550};
1551
1552/// @brief The opener's binding of an alias, for a screen opened as a dialog (`{"from": alias}`).
1553struct Borrow {
1554 /// @brief The opener's alias.
1555 std::string alias;
1556};
1557
1558/// @brief A model alias's `instance`: a binding mode, a key expression, or a borrowed binding.
1560public:
1561 /// @brief The default binding.
1562 InstanceSpec() = default;
1563
1564 /// @brief A binding mode.
1565 /// @param mode The mode.
1566 InstanceSpec(Instance mode) // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1567 : _mode{mode} {}
1568
1569 /// @brief A borrowed binding.
1570 /// @param borrow The opener's alias.
1571 InstanceSpec(Borrow borrow) // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1572 : _borrowed{std::move(borrow.alias)} {}
1573
1574 /// @brief A key expression: the shared instance the key selects. The model must be keyed.
1575 /// @tparam T The key's value type.
1576 /// @param key The key.
1577 template <typename T>
1578 InstanceSpec(Handle<T> const& key) // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1579 : _key{key} {}
1580
1581 /// @brief The `instance` JSON.
1582 /// @return The JSON, or nothing for the default.
1583 [[nodiscard]] std::optional<Json> json() const {
1584 if (_key.present()) {
1585 return _key.json();
1586 }
1587 if (!_borrowed.empty()) {
1588 return detail::single("from", _borrowed);
1589 }
1590 if (_mode == Instance::Default) {
1591 return std::nullopt;
1592 }
1593 if (_mode == Instance::Private) {
1594 return Json(std::string{"private"});
1595 }
1596 return Json(std::string{"shared"});
1597 }
1598
1599private:
1601 AnyHandle _key;
1602 std::string _borrowed;
1603};
1604
1605/// @brief Options of a model alias.
1607 /// @brief How the alias binds its instance.
1609};
1610
1611/// @brief Options of a query (spec 5 §5).
1613 /// @brief The gate; absent is always on.
1615 /// @brief How long the body must be stable before a fetch.
1616 std::chrono::milliseconds debounce{0};
1617 /// @brief The polling interval; zero does not poll.
1618 std::chrono::milliseconds refreshEvery{0};
1619 /// @brief How long a cached value counts as fresh.
1620 std::chrono::milliseconds staleTime{0};
1621};
1622
1623/// @brief What a mutation run does while another is in flight.
1624enum class Mode : std::uint8_t {
1625 Exclusive, ///< The run is refused (the default).
1626 Serial, ///< Runs queue, one queue per `serialKey`.
1627 Latest, ///< Every run is sent; only the newest reply applies.
1628 Parallel, ///< Every run is sent; replies apply as they arrive.
1629};
1630
1631/// @brief An `optimistic` write: the row a mutation run predicts before its reply.
1633 /// @brief The query whose cached list it writes.
1634 std::optional<QueryRef> query;
1635 /// @brief The member of the query's record value that holds the list; empty when the value is the list itself.
1636 std::string at;
1637 /// @brief The row key, read against the run's body bound as `body`.
1639 /// @brief The predicted row, read against the run's body bound as `body`.
1641};
1642
1643class Command;
1644
1645/// @brief A command list, run as one batch.
1647public:
1648 /// @brief No commands.
1649 Commands() = default;
1650
1651 /// @brief One command.
1652 /// @param command The command.
1653 Commands(Command const& command); // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1654
1655 /// @brief Commands in order.
1656 /// @param commands The commands.
1657 Commands(std::initializer_list<Command> commands);
1658
1659 /// @brief Whether the list is empty.
1660 /// @return True when it is.
1661 [[nodiscard]] bool empty() const noexcept { return _items.empty(); }
1662
1663 /// @brief The list's JSON.
1664 /// @return An array of commands.
1665 [[nodiscard]] Json json() const { return Json::array_t(_items.begin(), _items.end()); }
1666
1667private:
1668 std::vector<Json> _items;
1669};
1670
1671/// @brief One command of `ui/1` (spec 5 §8, spec 6).
1672class Command {
1673public:
1674 /// @brief A command over its JSON, for a form no builder spells.
1675 /// @param json The command.
1676 // Parentheses, not braces: a braced Json from one Json may take glaze's initializer-list constructor and wrap
1677 // it in an array.
1678 explicit Command(Json json) : _json(std::move(json)) {}
1679
1680 /// @brief The command's JSON.
1681 /// @return The JSON.
1682 [[nodiscard]] Json const& json() const noexcept { return _json; }
1683
1684private:
1685 Json _json = Json::object_t{};
1686};
1687
1688inline Commands::Commands(Command const& command) : _items{command.json()} {}
1689
1690inline Commands::Commands(std::initializer_list<Command> commands) {
1691 _items.reserve(commands.size());
1692 for (Command const& command : commands) {
1693 _items.push_back(command.json());
1694 }
1695}
1696
1697/// @brief Options of a mutation (spec 5 §5).
1698/// @tparam Action The action it runs.
1699template <typename Action>
1701 /// @brief The body; empty sends the body each `run` gives.
1703 /// @brief What a run does while another is in flight.
1705 /// @brief The queue key of `serial` runs.
1707 /// @brief The row a run predicts before its reply.
1708 std::optional<Optimistic> optimistic;
1709 /// @brief Commands run after a success.
1711 /// @brief Commands run after a failure.
1713 /// @brief Whether a run that cannot reach the server is queued offline.
1714 bool offlineQueue = false;
1715};
1716
1717/// @brief Which changes a watch reacts to.
1718enum class Source : std::uint8_t {
1719 Any, ///< Every change.
1720 User, ///< Only the user's own edits.
1721};
1722
1723/// @brief A validation across elements (spec 5 §6).
1725 /// @brief The check's id.
1726 std::string name;
1727 /// @brief The condition; `false` fails the check.
1729 /// @brief The message added to each target's errors.
1731 /// @brief The elements the message attaches to.
1732 std::vector<Element> targets;
1733 /// @brief Whether a failure blocks `ready`.
1734 bool blocking = false;
1735};
1736
1737class Node;
1738
1739/// @brief A list of view nodes.
1740class Nodes {
1741public:
1742 /// @brief No nodes.
1743 Nodes() = default;
1744
1745 /// @brief One node.
1746 /// @param node The node.
1747 Nodes(Node const& node); // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1748
1749 /// @brief Nodes in order.
1750 /// @param nodes The nodes.
1751 Nodes(std::initializer_list<Node> nodes);
1752
1753 /// @brief Nodes in order.
1754 /// @param nodes The nodes.
1755 Nodes(std::vector<Node> const& nodes); // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
1756
1757 /// @brief Whether the list is empty.
1758 /// @return True when it is.
1759 [[nodiscard]] bool empty() const noexcept { return _items.empty(); }
1760
1761 /// @brief The list's JSON.
1762 /// @return An array of nodes.
1763 [[nodiscard]] Json json() const { return Json::array_t(_items.begin(), _items.end()); }
1764
1765 /// @brief The catalog facts of the models and actions the nodes name.
1766 /// @return The facts.
1767 [[nodiscard]] CatalogFacts const& facts() const noexcept { return _facts; }
1768
1769private:
1770 void add(Node const& node);
1771
1772 std::vector<Json> _items;
1773 CatalogFacts _facts;
1774};
1775
1776/// @brief A view node (spec 5 §9). The kind's own properties come from its builder's options; the properties every
1777/// node takes come from the member functions, each of which returns a copy with the property set.
1778class Node {
1779public:
1780 /// @brief A node over its JSON, for a kind no builder spells.
1781 /// @param json The node.
1782 /// @param facts The catalog facts of the models and actions it names.
1783 explicit Node(Json json, CatalogFacts facts = {}) : _json(std::move(json)), _facts{std::move(facts)} {}
1784
1785 /// @brief The node's JSON.
1786 /// @return The JSON.
1787 [[nodiscard]] Json const& json() const noexcept { return _json; }
1788
1789 /// @brief The catalog facts of the models and actions the node names.
1790 /// @return The facts.
1791 [[nodiscard]] CatalogFacts const& facts() const noexcept { return _facts; }
1792
1793 /// @brief A property set to an expression; an absent expression leaves the node as it is.
1794 /// @param key The property.
1795 /// @param value The expression.
1796 /// @return The node with it.
1797 [[nodiscard]] Node with(std::string const& key, AnyHandle const& value) const {
1798 Node out = *this;
1799 if (value.present()) {
1800 detail::put(out._json, key, value.json());
1801 }
1802 return out;
1803 }
1804
1805 /// @brief A property set to a plain string.
1806 /// @param key The property.
1807 /// @param value The string.
1808 /// @return The node with it.
1809 [[nodiscard]] Node withText(std::string const& key, std::string const& value) const {
1810 Node out = *this;
1811 detail::put(out._json, key, value);
1812 return out;
1813 }
1814
1815 /// @brief The element id, by which relations read its values.
1816 /// @param element The element.
1817 /// @return The node with it.
1818 [[nodiscard]] Node id(Element const& element) const { return withText("id", element.name()); }
1819
1820 /// @brief Whether the node shows.
1821 /// @param condition The condition.
1822 /// @return The node with it.
1823 [[nodiscard]] Node visible(AnyHandle const& condition) const { return with("visible", condition); }
1824
1825 /// @brief Whether the node is enabled.
1826 /// @param condition The condition.
1827 /// @return The node with it.
1828 [[nodiscard]] Node enabled(AnyHandle const& condition) const { return with("enabled", condition); }
1829
1830 /// @brief A tooltip.
1831 /// @param text The text.
1832 /// @return The node with it.
1833 [[nodiscard]] Node tooltip(AnyHandle const& text) const { return with("tooltip", text); }
1834
1835 /// @brief The width's sizing: `"content"`, `{"fixed": n}` or `{"stretch": w}`, such as `content()`, `fixed(n)`
1836 /// or `stretch()` give.
1837 /// @param sizing The sizing.
1838 /// @return The node with it.
1839 [[nodiscard]] Node sizing(AnyHandle const& sizing) const { return with("sizing", sizing); }
1840
1841 /// @brief The sizing of both dimensions, as a `{width, height}` record; an absent one is content. Literal
1842 /// sizings (`content()`, `fixed(n)`, `stretch(w)`) make a literal record, which the loader checks; another
1843 /// is evaluated at mount.
1844 /// @param width The width's sizing.
1845 /// @param height The height's sizing.
1846 /// @return The node with it.
1847 [[nodiscard]] Node sizing(AnyHandle const& width, AnyHandle const& height) const {
1848 std::optional<Json> const literalWidth = width.present() ? detail::literalOf(width.json()) : Json{};
1849 std::optional<Json> const literalHeight = height.present() ? detail::literalOf(height.json()) : Json{};
1850 Json record = Json::object_t{};
1851 if (literalWidth && literalHeight) {
1852 if (width.present()) {
1853 detail::put(record, "width", *literalWidth);
1854 }
1855 if (height.present()) {
1856 detail::put(record, "height", *literalHeight);
1857 }
1858 return with("sizing", AnyHandle::raw(detail::single("lit", std::move(record))));
1859 }
1860 if (width.present()) {
1861 detail::put(record, "width", width.json());
1862 }
1863 if (height.present()) {
1864 detail::put(record, "height", height.json());
1865 }
1866 return with("sizing", AnyHandle::raw(detail::single("obj", std::move(record))));
1867 }
1868
1869 /// @brief Where the node sits in its row, column or grid, in place of the container's `align`.
1870 /// @param align The alignment.
1871 /// @return The node with it.
1872 [[nodiscard]] Node alignSelf(Align align) const { return withText("alignSelf", detail::alignName(align)); }
1873
1874 /// @brief The least width, in backend units.
1875 /// @param units The bound, 0 or more.
1876 /// @return The node with it.
1877 [[nodiscard]] Node minWidth(int units) const { return with("minWidth", AnyHandle{std::int64_t{units}}); }
1878
1879 /// @brief The most width, in backend units.
1880 /// @param units The bound, 0 or more.
1881 /// @return The node with it.
1882 [[nodiscard]] Node maxWidth(int units) const { return with("maxWidth", AnyHandle{std::int64_t{units}}); }
1883
1884 /// @brief The least height, in backend units.
1885 /// @param units The bound, 0 or more.
1886 /// @return The node with it.
1887 [[nodiscard]] Node minHeight(int units) const { return with("minHeight", AnyHandle{std::int64_t{units}}); }
1888
1889 /// @brief The most height, in backend units.
1890 /// @param units The bound, 0 or more.
1891 /// @return The node with it.
1892 [[nodiscard]] Node maxHeight(int units) const { return with("maxHeight", AnyHandle{std::int64_t{units}}); }
1893
1894 /// @brief The grid columns the node spans.
1895 /// @param columns The span.
1896 /// @return The node with it.
1897 [[nodiscard]] Node span(AnyHandle const& columns) const { return with("span", columns); }
1898
1899 /// @brief Whether the node takes focus when it mounts.
1900 /// @param focus The condition.
1901 /// @return The node with it.
1902 [[nodiscard]] Node autofocus(AnyHandle const& focus) const { return with("autofocus", focus); }
1903
1904 /// @brief A stable identifier for automated tests.
1905 /// @param name The identifier.
1906 /// @return The node with it.
1907 [[nodiscard]] Node testId(std::string const& name) const { return withText("testId", name); }
1908
1909 /// @brief A named token set for the subtree.
1910 /// @param name The token set.
1911 /// @return The node with it.
1912 [[nodiscard]] Node surface(std::string const& name) const { return withText("surface", name); }
1913
1914 /// @brief The accessible name and role.
1915 /// @param name The name.
1916 /// @param role The role; empty for the kind's own.
1917 /// @return The node with it.
1918 [[nodiscard]] Node a11y(AnyHandle const& name, std::string const& role = {}) const {
1919 Node out = *this;
1920 Json part = Json::object_t{};
1921 if (name.present()) {
1922 detail::put(part, "name", name.json());
1923 }
1924 if (!role.empty()) {
1925 detail::put(part, "role", role);
1926 }
1927 detail::put(out._json, "a11y", std::move(part));
1928 return out;
1929 }
1930
1931 /// @brief Makes the node draggable.
1932 /// @param payload The payload a drop receives.
1933 /// @return The node with it.
1934 [[nodiscard]] Node drag(AnyHandle const& payload) const {
1935 Node out = *this;
1936 detail::put(out._json, "drag", detail::single("payload", payload.json()));
1937 return out;
1938 }
1939
1940 /// @brief Makes the node a drop target.
1941 /// @param accepts The payloads it accepts.
1942 /// @param onDrop The commands run with the payload as `event`.
1943 /// @return The node with it.
1944 [[nodiscard]] Node drop(AnyHandle const& accepts, Commands const& onDrop) const {
1945 Node out = *this;
1946 Json part = Json::object_t{};
1947 if (accepts.present()) {
1948 detail::put(part, "accepts", accepts.json());
1949 }
1950 detail::put(part, "onDrop", onDrop.json());
1951 detail::put(out._json, "drop", std::move(part));
1952 return out;
1953 }
1954
1955 /// @brief A key binding.
1956 /// @param chord The chord, such as `Ctrl+S`.
1957 /// @param commands The commands.
1958 /// @return The node with it.
1959 [[nodiscard]] Node key(std::string const& chord, Commands const& commands) const {
1960 Node out = *this;
1961 auto const entry = std::get<Json::object_t>(out._json.data).try_emplace(std::string{"keys"}, Json::object_t{});
1962 detail::put(entry.first->second, chord, commands.json());
1963 return out;
1964 }
1965
1966 /// @brief Whether an input or text is read-only.
1967 /// @param condition The condition.
1968 /// @return The node with it.
1969 [[nodiscard]] Node readonly(AnyHandle const& condition) const { return with("readonly", condition); }
1970
1971 /// @brief Whether an input or text is required.
1972 /// @param condition The condition.
1973 /// @return The node with it.
1974 [[nodiscard]] Node required(AnyHandle const& condition) const { return with("required", condition); }
1975
1976 /// @brief Errors shown on an input or text.
1977 /// @param errors The errors.
1978 /// @return The node with them.
1979 [[nodiscard]] Node errors(AnyHandle const& errors) const { return with("errors", errors); }
1980
1981 /// @brief Whether an input or text shows a value that is being recalculated.
1982 /// @param condition The condition.
1983 /// @return The node with it.
1984 [[nodiscard]] Node stale(AnyHandle const& condition) const { return with("stale", condition); }
1985
1986private:
1987 Json _json = Json::object_t{};
1988 CatalogFacts _facts;
1989};
1990
1991inline Nodes::Nodes(Node const& node) { add(node); }
1992
1993inline Nodes::Nodes(std::initializer_list<Node> nodes) {
1994 _items.reserve(nodes.size());
1995 for (Node const& node : nodes) {
1996 add(node);
1997 }
1998}
1999
2000inline Nodes::Nodes(std::vector<Node> const& nodes) {
2001 _items.reserve(nodes.size());
2002 for (Node const& node : nodes) {
2003 add(node);
2004 }
2005}
2006
2007inline void Nodes::add(Node const& node) {
2008 _items.push_back(node.json());
2009 _facts.merge(node.facts());
2010}
2011
2012namespace detail {
2013
2014/// @brief A document type name for a C++ type, as `params` and `state` declare it.
2015/// @tparam T The C++ type.
2016/// @return `bool`, `int`, `string`, `decimal`, `quantity`, `timestamp`, `list<T>`, `T?`, or `record` for any
2017/// other class.
2018template <typename T>
2019[[nodiscard]] std::string typeName() {
2020 if constexpr (IsOptional<T>::value) {
2021 return typeName<typename T::value_type>() + "?";
2022 } else if constexpr (IsVector<T>::value) {
2023 return "list<" + typeName<typename T::value_type>() + ">";
2024 } else if constexpr (std::same_as<T, bool>) {
2025 return "bool";
2026 } else if constexpr (std::integral<T>) {
2027 return "int";
2028 } else if constexpr (std::same_as<T, std::string>) {
2029 return "string";
2030 } else if constexpr (std::same_as<T, math::Rational> || std::same_as<T, Decimal>) {
2031 return "decimal";
2032 } else if constexpr (std::same_as<T, Quantity>) {
2033 return "quantity";
2034 } else if constexpr (std::same_as<T, time::DateTime>) {
2035 return "timestamp";
2036 } else {
2037 return "record";
2038 }
2039}
2040
2041} // namespace detail
2042
2043/// @brief A scope's declarations (spec 5 §3): model aliases, state, `let`, queries, mutations, watches and checks.
2044/// A row, a dialog, a drawer and a `switch` case declare theirs on the scope their builder hands over.
2046public:
2047 /// @brief An empty scope.
2048 DeclScope() = default;
2049
2050 /// @brief A model alias.
2051 /// @tparam Model The model type; `::morph::model::ModelTraits<Model>` names it.
2052 /// @param alias The alias.
2053 /// @param options How it binds.
2054 /// @return The alias's handle.
2055 template <typename Model>
2056 ModelHandle<Model> model(std::string const& alias, ModelOptions const& options = {}) {
2057 Json decl = Json::object_t{};
2058 detail::put(decl, "type", std::string{::morph::model::ModelTraits<Model>::typeId()});
2059 if (auto instance = options.instance.json()) {
2060 detail::put(decl, "instance", std::move(*instance));
2061 }
2062 detail::put(_models, alias, std::move(decl));
2063 _facts.model<Model>();
2064 return ModelHandle<Model>{alias};
2065 }
2066
2067 /// @brief State with a declared type and no initial value; it starts `null`.
2068 /// @tparam T The state's type.
2069 /// @param name The state's name.
2070 /// @return The read of the state.
2071 template <typename T>
2072 Handle<T> state(std::string const& name) {
2073 return declareState<T>(name, {});
2074 }
2075
2076 /// @brief State initialised from an expression; its type is `T`'s.
2077 /// @tparam T The state's type.
2078 /// @param name The state's name.
2079 /// @param init The initial value.
2080 /// @return The read of the state.
2081 template <typename T>
2082 Handle<T> state(std::string const& name, Handle<T> const& init) {
2083 return declareState<T>(name, init);
2084 }
2085
2086 /// @brief State initialised from a constant; its type is the constant's.
2087 /// @tparam L The constant's C++ type.
2088 /// @param name The state's name.
2089 /// @param init The initial value.
2090 /// @return The read of the state.
2091 template <typename L>
2092 requires detail::Literal<L>
2093 Handle<detail::LiteralT<L>> state(std::string const& name, L const& init) {
2094 return declareState<detail::LiteralT<L>>(name, Handle<detail::LiteralT<L>>{init});
2095 }
2096
2097 /// @brief A derived value (`let`).
2098 /// @tparam T The value type.
2099 /// @param name The name.
2100 /// @param value The expression.
2101 /// @return The read of the name.
2102 template <typename T>
2103 Handle<T> let(std::string const& name, Handle<T> const& value) {
2104 detail::put(_lets, name, value.json());
2105 return author::ref<T>(name);
2106 }
2107
2108 /// @brief A query (spec 5 §5).
2109 /// @tparam Action The action it runs.
2110 /// @tparam Model The model type of the alias; it must run the action.
2111 /// @param name The query's name.
2112 /// @param model The alias it goes through.
2113 /// @param body The body, which is the query's key.
2114 /// @param options The gate and timing.
2115 /// @return The query's handle.
2116 template <typename Action, typename Model>
2117 requires Runs<Model, Action>
2118 QueryHandle<Action> query(std::string const& name, ModelHandle<Model> const& model, Body<Action> const& body = {},
2119 QueryOptions const& options = {}) {
2120 declareQuery<Model, Action>(name, model.alias(), "action", body, options);
2121 return QueryHandle<Action>{name};
2122 }
2123
2124 /// @brief A query that evaluates an action without running it: the server returns its calculated fields and
2125 /// field errors (spec 5 §7).
2126 /// @tparam Action The action it evaluates.
2127 /// @tparam Model The model type of the alias; it must run the action.
2128 /// @param name The query's name.
2129 /// @param model The alias it goes through.
2130 /// @param body The body, which is the query's key.
2131 /// @param options The gate and timing.
2132 /// @return The query's handle. Its value is the action body as the server reconciled it, computed fields
2133 /// filled, with `errors` and `ready` beside the fields.
2134 template <typename Action, typename Model>
2135 requires Runs<Model, Action>
2137 Body<Action> const& body = {}, QueryOptions const& options = {}) {
2138 declareQuery<Model, Action>(name, model.alias(), "evaluate", body, options);
2139 return QueryHandle<Action, Action>{name};
2140 }
2141
2142 /// @brief A mutation (spec 5 §5).
2143 /// @tparam Action The action it runs.
2144 /// @tparam Model The model type of the alias; it must run the action.
2145 /// @param name The mutation's name.
2146 /// @param model The alias it goes through.
2147 /// @param options Its body, concurrency, optimistic write and command lists.
2148 /// @return The mutation's handle.
2149 template <typename Action, typename Model>
2150 requires Runs<Model, Action>
2152 MutationOptions<Action> const& options = {}) {
2153 Json decl = Json::object_t{};
2154 detail::put(decl, "model", model.alias());
2155 detail::put(decl, "action", std::string{::morph::model::ActionTraits<Action>::typeId()});
2156 if (!options.body.empty()) {
2157 detail::put(decl, "body", options.body.json());
2158 }
2159 writeMutation(decl, options.concurrency, options.serialKey, options.optimistic, options.onSuccess,
2160 options.onError, options.offlineQueue);
2161 detail::put(_mutations, name, std::move(decl));
2162 _facts.action<Model, Action>();
2163 return MutationHandle<Action>{name};
2164 }
2165
2166 /// @brief A cascade: commands that run when a value changes (spec 5 §6).
2167 /// @param watched The value watched.
2168 /// @param commands The commands.
2169 /// @param source Which changes run it.
2170 void watch(AnyHandle const& watched, Commands const& commands, Source source = Source::Any) {
2171 Json decl = Json::object_t{};
2172 detail::put(decl, "on", watched.json());
2173 if (source == Source::User) {
2174 detail::put(decl, "source", std::string{"user"});
2175 }
2176 detail::put(decl, "do", commands.json());
2177 _watches.push_back(std::move(decl));
2178 }
2179
2180 /// @brief A validation across elements (spec 5 §6).
2181 /// @param options The check.
2182 void check(CheckOptions const& options) {
2183 Json decl = Json::object_t{};
2184 detail::put(decl, "id", options.name);
2185 detail::put(decl, "assert", options.assertion.json());
2186 if (options.message.present()) {
2187 detail::put(decl, "message", options.message.json());
2188 }
2189 if (!options.targets.empty()) {
2190 Json::array_t targets;
2191 targets.reserve(options.targets.size());
2192 for (Element const& target : options.targets) {
2193 targets.emplace_back(target.name());
2194 }
2195 detail::put(decl, "targets", std::move(targets));
2196 }
2197 if (options.blocking) {
2198 detail::put(decl, "blocking", true);
2199 }
2200 _checks.push_back(std::move(decl));
2201 }
2202
2203 /// @brief Whether the scope declares nothing.
2204 /// @return True when it declares nothing.
2205 [[nodiscard]] bool empty() const {
2206 std::size_t const declared = detail::members(_models) + detail::members(_state) + detail::members(_lets) +
2207 detail::members(_queries) + detail::members(_mutations) + _watches.size() +
2208 _checks.size();
2209 return declared == 0;
2210 }
2211
2212 /// @brief Writes the declarations into a document or a `"scope"` object, in the document's order.
2213 /// @param out The object to write into.
2214 void writeTo(Json& out) const {
2215 detail::putSection(out, "models", _models);
2216 detail::putSection(out, "state", _state);
2217 detail::putSection(out, "let", _lets);
2218 detail::putSection(out, "queries", _queries);
2219 detail::putSection(out, "mutations", _mutations);
2220 if (!_watches.empty()) {
2221 detail::put(out, "watch", _watches);
2222 }
2223 if (!_checks.empty()) {
2224 detail::put(out, "checks", _checks);
2225 }
2226 }
2227
2228 /// @brief The catalog facts of the models and actions the scope names.
2229 /// @return The facts.
2230 [[nodiscard]] CatalogFacts const& facts() const noexcept { return _facts; }
2231
2232protected:
2233 /// @brief The catalog facts, for a scope that records more.
2234 /// @return The facts.
2235 [[nodiscard]] CatalogFacts& recordedFacts() noexcept { return _facts; }
2236
2237private:
2238 template <typename T>
2239 Handle<T> declareState(std::string const& name, Handle<T> const& init) {
2240 Json decl = Json::object_t{};
2241 if constexpr (!std::same_as<T, Any>) {
2242 detail::put(decl, "type", detail::typeName<T>());
2243 }
2244 if (init.present()) {
2245 detail::put(decl, "init", init.json());
2246 }
2247 detail::put(_state, name, std::move(decl));
2248 return author::ref<T>(name);
2249 }
2250
2251 template <typename Model, typename Action>
2252 void declareQuery(std::string const& name, std::string const& alias, char const* actionKey,
2253 Body<Action> const& body, QueryOptions const& options) {
2254 Json decl = Json::object_t{};
2255 detail::put(decl, "model", alias);
2256 detail::put(decl, actionKey, std::string{::morph::model::ActionTraits<Action>::typeId()});
2257 if (!body.empty()) {
2258 detail::put(decl, "body", body.json());
2259 }
2260 if (options.when.present()) {
2261 detail::put(decl, "when", options.when.json());
2262 }
2263 auto duration = [&decl](char const* key, std::chrono::milliseconds value) {
2264 if (value.count() != 0) {
2265 detail::put(decl, key, value.count());
2266 }
2267 };
2268 duration("debounce", options.debounce);
2269 duration("refreshEvery", options.refreshEvery);
2270 duration("staleTime", options.staleTime);
2271 detail::put(_queries, name, std::move(decl));
2272 _facts.action<Model, Action>();
2273 }
2274 static void writeMutation(Json& decl, Mode concurrency, AnyHandle const& serialKey,
2275 std::optional<Optimistic> const& optimistic, Commands const& onSuccess,
2276 Commands const& onError, bool offlineQueue) {
2277 static constexpr std::array<char const*, 4> kModes = {"exclusive", "serial", "latest", "parallel"};
2278 if (concurrency != Mode::Exclusive) {
2279 detail::put(decl, "concurrency", std::string{kModes.at(static_cast<std::size_t>(concurrency))});
2280 }
2281 if (serialKey.present()) {
2282 detail::put(decl, "serialKey", serialKey.json());
2283 }
2284 if (optimistic) {
2285 if (!optimistic->query) {
2286 throw AuthoringError{"an `optimistic` write names the query whose row it writes"};
2287 }
2288 Json part = Json::object_t{};
2289 detail::put(part, "query", optimistic->query->name());
2290 if (!optimistic->at.empty()) {
2291 detail::put(part, "at", optimistic->at);
2292 }
2293 detail::put(part, "key", optimistic->key.json());
2294 detail::put(part, "with", optimistic->with.json());
2295 detail::put(decl, "optimistic", std::move(part));
2296 }
2297 if (!onSuccess.empty()) {
2298 detail::put(decl, "onSuccess", onSuccess.json());
2299 }
2300 if (!onError.empty()) {
2301 detail::put(decl, "onError", onError.json());
2302 }
2303 if (offlineQueue) {
2304 detail::put(decl, "offline", std::string{"queue"});
2305 }
2306 }
2307
2308 Json _models = Json::object_t{};
2309 Json _state = Json::object_t{};
2310 Json _lets = Json::object_t{};
2311 Json _queries = Json::object_t{};
2312 Json _mutations = Json::object_t{};
2313 Json::array_t _watches;
2314 Json::array_t _checks;
2315 CatalogFacts _facts;
2316};
2317
2318/// @brief The scope a row of a `table` or `forEach` declares its own state, queries and model aliases on.
2320
2321/// @brief When a scope's element errors show (spec 5 §6).
2322enum class ShowErrors : std::uint8_t {
2323 Touched, ///< Once the element has been touched.
2324 Submit, ///< Once the form or dialog has been submitted.
2325 Always, ///< At once.
2326};
2327
2328/// @brief The spelling of a `ShowErrors`.
2329/// @param when The policy.
2330/// @return `touched`, `submit` or `always`.
2331[[nodiscard]] inline std::string showErrorsName(ShowErrors when) {
2332 static constexpr std::array<char const*, 3> kNames = {"touched", "submit", "always"};
2333 return kNames.at(static_cast<std::size_t>(when));
2334}
2335
2336/// @brief A prop type of a component: how a value crosses into QML (spec 5 §10).
2337enum class PropType : std::uint8_t {
2338 String, ///< Text.
2339 Bool, ///< A boolean.
2340 Int, ///< An integer, as text.
2341 Exact, ///< An exact decimal or quantity, as text.
2342 Number, ///< A double, for charts.
2343 Json, ///< Structured data, exact values inside it as text.
2344};
2345
2346/// @brief A custom or host component (spec 5 §10).
2348 /// @brief The content hash of a delivered component, `sha256:<64 hex digits>`; empty for a host component.
2349 std::string qml;
2350 /// @brief The name of a host component the client carries; empty for a delivered one.
2351 std::string host;
2352 /// @brief The host component's version.
2353 std::int64_t version = 0;
2354 /// @brief The props and their types.
2355 std::vector<std::pair<std::string, PropType>> props;
2356 /// @brief The events and their payload types.
2357 std::vector<std::pair<std::string, std::string>> events;
2358 /// @brief The built-in subtree a renderer shows when it cannot load the component; it reads the props through
2359 /// `prop(name)`.
2360 std::optional<Node> fallback;
2361};
2362
2363/// @brief A declared component, for `custom` nodes.
2365public:
2366 /// @brief A component by name.
2367 /// @param name The component's name.
2368 explicit ComponentRef(std::string name) : _name{std::move(name)} {}
2369
2370 /// @brief The component's name.
2371 /// @return The name.
2372 [[nodiscard]] std::string const& name() const noexcept { return _name; }
2373
2374private:
2375 std::string _name;
2376};
2377
2378/// @brief A customization point (spec 5 §12b).
2379class Point {
2380public:
2381 /// @brief A point by name.
2382 /// @param name The point's name.
2383 explicit Point(std::string name) : _name{std::move(name)} {}
2384
2385 /// @brief The point's name.
2386 /// @return The name.
2387 [[nodiscard]] std::string const& name() const noexcept { return _name; }
2388
2389 /// @brief The user's choice at the point.
2390 /// @param path A path inside the choice; empty for the whole.
2391 /// @return `{"pref": "point.path"}`.
2392 [[nodiscard]] AnyHandle pref(std::string const& path = {}) const {
2393 return AnyHandle::raw(detail::single("pref", path.empty() ? _name : _name + "." + path));
2394 }
2395
2396private:
2397 std::string _name;
2398};
2399
2400/// @brief A status badge of the screen's head (`screen.status`).
2402 /// @brief The badge's text.
2404 /// @brief Its tone: `neutral`, `info`, `ok`, `warn` or `err`.
2406 /// @brief The name of an icon.
2408 /// @brief Whether it is shown; absent shows it.
2410};
2411
2412/// @brief A button of a screen's head (`screen.action`), or of a dialog's or drawer's footer.
2414 /// @brief The action's id, unique in its list: a name.
2415 std::string id;
2416 /// @brief The label; required.
2418 /// @brief The name of an icon.
2420 /// @brief How much it stands out; `Normal` writes no key.
2422 /// @brief Whether it can be run; absent enables it.
2424 /// @brief Whether it is shown; absent shows it.
2426 /// @brief Commands run when it is clicked, in the scope its list belongs to.
2428};
2429
2430namespace detail {
2431
2432/// @brief An expression key of a part, left out when absent.
2433/// @param json The part.
2434/// @param key The key.
2435/// @param value The expression.
2436inline void putPresent(Json& json, char const* key, AnyHandle const& value) {
2437 if (value.present()) {
2438 detail::put(json, key, value.json());
2439 }
2440}
2441
2442/// @brief A `headBadge` part's JSON.
2443/// @param badge The badge.
2444/// @return The JSON.
2445[[nodiscard]] inline Json headBadge(HeadBadgeOptions const& badge) {
2446 Json json = Json::object_t{};
2447 putPresent(json, "tone", badge.tone);
2448 putPresent(json, "text", badge.text);
2449 putPresent(json, "icon", badge.icon);
2450 putPresent(json, "visible", badge.visible);
2451 return json;
2452}
2453
2454/// @brief An `actionButton` part's JSON.
2455/// @param action The button.
2456/// @return The JSON.
2457[[nodiscard]] inline Json actionButton(ActionButtonOptions const& action) {
2458 Json json = Json::object_t{};
2459 detail::put(json, "id", action.id);
2460 putPresent(json, "label", action.label);
2461 putPresent(json, "icon", action.icon);
2462 if (action.emphasis == ButtonEmphasis::Primary) {
2463 detail::put(json, "emphasis", std::string{"primary"});
2464 } else if (action.emphasis == ButtonEmphasis::Danger) {
2465 detail::put(json, "emphasis", std::string{"danger"});
2466 } else if (action.emphasis == ButtonEmphasis::Ghost) {
2467 detail::put(json, "emphasis", std::string{"ghost"});
2468 }
2469 putPresent(json, "enabled", action.enabled);
2470 putPresent(json, "visible", action.visible);
2471 if (!action.onClick.empty()) {
2472 detail::put(json, "onClick", action.onClick.json());
2473 }
2474 return json;
2475}
2476
2477/// @brief Appends @p item to the array at @p key of @p json, making the array on first use.
2478/// @param json The object.
2479/// @param key The key.
2480/// @param item The item.
2481inline void append(Json& json, char const* key, Json item) {
2482 auto& object = std::get<Json::object_t>(json.data);
2483 Json::object_t::iterator found = object.find(key);
2484 if (found == object.end()) {
2485 detail::put(json, key, Json::array_t{});
2486 found = object.find(key);
2487 }
2488 std::get<Json::array_t>(found->second.data).push_back(std::move(item));
2489}
2490
2491} // namespace detail
2492
2493/// @brief The screen's own scope: its declarations, params, lifecycle keys, components and customization points.
2494class Scope : public DeclScope {
2495public:
2496 /// @brief A typed param, set by navigation.
2497 /// @tparam T The param's type.
2498 /// @param name The param's name.
2499 /// @return The read of the param.
2500 template <typename T>
2501 Handle<T> param(std::string const& name) {
2502 detail::put(_params, name, detail::typeName<T>());
2503 return author::ref<T>(name);
2504 }
2505
2506 /// @brief The screen's title.
2507 /// @param title The expression.
2508 void title(AnyHandle const& title) { detail::put(_head, "title", title.json()); }
2509
2510 /// @brief The screen's identity: two screens with equal identities are one.
2511 /// @param identity The expression.
2512 void identity(AnyHandle const& identity) { detail::put(_head, "identity", identity.json()); }
2513
2514 /// @brief Whether the screen has unsaved changes.
2515 /// @param dirty The condition.
2516 void dirty(AnyHandle const& dirty) { detail::put(_head, "dirty", dirty.json()); }
2517
2518 /// @brief The head's line under the title.
2519 /// @param subtitle The expression.
2520 void subtitle(AnyHandle const& subtitle) { detail::put(_head, "subtitle", subtitle.json()); }
2521
2522 /// @brief Appends a status badge to the head.
2523 /// @param badge The badge.
2525
2526 /// @brief Appends an action to the head, run in the screen scope.
2527 /// @param action The action; its id is unique among the head's actions.
2529
2530 /// @brief Commands run when the screen mounts.
2531 /// @param commands The commands.
2532 void onMount(Commands const& commands) { detail::put(_lifecycle, "onMount", commands.json()); }
2533
2534 /// @brief Commands run when the screen unmounts.
2535 /// @param commands The commands.
2536 void onUnmount(Commands const& commands) { detail::put(_lifecycle, "onUnmount", commands.json()); }
2537
2538 /// @brief Commands run when the screen is asked to close; `cancelClose()` keeps it open.
2539 /// @param commands The commands.
2540 void onClose(Commands const& commands) { detail::put(_lifecycle, "onClose", commands.json()); }
2541
2542 /// @brief Commands run when the host asks the screen to save.
2543 /// @param commands The commands.
2544 void onSave(Commands const& commands) { detail::put(_lifecycle, "onSave", commands.json()); }
2545
2546 /// @brief Commands run after a backend switch.
2547 /// @param commands The commands.
2548 void onBackendChange(Commands const& commands) { detail::put(_lifecycle, "onBackendChange", commands.json()); }
2549
2550 /// @brief A screen-wide key binding.
2551 /// @param chord The chord.
2552 /// @param commands The commands.
2553 void key(std::string const& chord, Commands const& commands) { detail::put(_keys, chord, commands.json()); }
2554
2555 /// @brief When the screen's element errors show.
2556 /// @param when The policy.
2557 void showErrors(ShowErrors when) { _showErrors = showErrorsName(when); }
2558
2559 /// @brief A query started before the screen mounts; its body reads only params.
2560 /// @param query The query.
2561 void prefetch(QueryRef const& query) { _prefetch.emplace_back(query.name()); }
2562
2563 /// @brief A custom or host component.
2564 /// @param name The component's name.
2565 /// @param options The component.
2566 /// @return The reference `custom` nodes name.
2567 ComponentRef component(std::string const& name, ComponentOptions const& options) {
2568 static constexpr std::array<char const*, 6> kPropTypes = {"string", "bool", "int", "exact", "number", "json"};
2569 Json decl = Json::object_t{};
2570 if (!options.qml.empty()) {
2571 detail::put(decl, "qml", options.qml);
2572 }
2573 if (!options.host.empty()) {
2574 detail::put(decl, "host", options.host);
2575 detail::put(decl, "version", options.version);
2576 }
2577 Json props = Json::object_t{};
2578 for (auto const& [prop, type] : options.props) {
2579 detail::put(props, prop, std::string{kPropTypes.at(static_cast<std::size_t>(type))});
2580 }
2581 detail::put(decl, "props", std::move(props));
2582 if (!options.events.empty()) {
2583 Json events = Json::object_t{};
2584 for (auto const& [event, type] : options.events) {
2585 detail::put(events, event, type);
2586 }
2587 detail::put(decl, "events", std::move(events));
2588 }
2589 if (options.fallback) {
2590 detail::put(decl, "fallback", options.fallback->json());
2591 recordedFacts().merge(options.fallback->facts());
2592 }
2593 detail::put(_components, name, std::move(decl));
2594 return ComponentRef{name};
2595 }
2596
2597 /// @brief A `columns` point: the user reorders, hides and resizes a table's columns.
2598 /// @param name The point's name.
2599 /// @param table The table.
2600 /// @return The point.
2601 Point columnsPoint(std::string const& name, Element const& table) {
2602 Json decl = Json::object_t{};
2603 detail::put(decl, "kind", std::string{"columns"});
2604 detail::put(decl, "of", table.name());
2605 return addPoint(name, std::move(decl));
2606 }
2607
2608 /// @brief An `arrange` point: the user reorders and hides the children of a column or row, each of which has an
2609 /// id.
2610 /// @param name The point's name.
2611 /// @param container The column or row.
2612 /// @return The point.
2613 Point arrangePoint(std::string const& name, Element const& container) {
2614 Json decl = Json::object_t{};
2615 detail::put(decl, "kind", std::string{"arrange"});
2616 detail::put(decl, "of", container.name());
2617 return addPoint(name, std::move(decl));
2618 }
2619
2620 /// @brief A `choice` point: one of a list of options.
2621 /// @param name The point's name.
2622 /// @param options The options.
2623 /// @param fallback The default; empty for none.
2624 /// @return The point.
2625 Point choicePoint(std::string const& name, std::vector<std::string> const& options,
2626 std::string const& fallback = {}) {
2627 Json decl = Json::object_t{};
2628 detail::put(decl, "kind", std::string{"choice"});
2629 detail::put(decl, "options", Json::array_t(options.begin(), options.end()));
2630 if (!fallback.empty()) {
2631 detail::put(decl, "default", fallback);
2632 }
2633 return addPoint(name, std::move(decl));
2634 }
2635
2636 /// @brief A `value` point: one typed setting.
2637 /// @tparam T The setting's type.
2638 /// @param name The point's name.
2639 /// @param fallback The default.
2640 /// @return The point.
2641 template <typename T>
2642 Point valuePoint(std::string const& name, Value const& fallback = {}) {
2643 Json decl = Json::object_t{};
2644 detail::put(decl, "kind", std::string{"value"});
2645 detail::put(decl, "type", detail::typeName<T>());
2646 detail::put(decl, "default", valueToJson(fallback));
2647 return addPoint(name, std::move(decl));
2648 }
2649
2650 /// @brief A `layout` or `saved` point.
2651 /// @param name The point's name.
2652 /// @param kind `layout` or `saved`.
2653 /// @return The point.
2654 Point point(std::string const& name, std::string const& kind) {
2655 Json decl = Json::object_t{};
2656 detail::put(decl, "kind", kind);
2657 return addPoint(name, std::move(decl));
2658 }
2659
2660 /// @brief The document's JSON with a view.
2661 /// @param documentId The document id.
2662 /// @param view The view.
2663 /// @return The document.
2664 [[nodiscard]] Json document(std::string const& documentId, Node const& view) const {
2665 Json out = Json::object_t{};
2666 detail::put(out, "vocab", std::string{kVocabulary});
2667 detail::put(out, "id", documentId);
2668 for (auto const& [key, value] : std::get<Json::object_t>(_head.data)) {
2669 detail::put(out, key, value);
2670 }
2671 detail::putSection(out, "params", _params);
2672 writeTo(out);
2673 for (auto const& [key, value] : std::get<Json::object_t>(_lifecycle.data)) {
2674 detail::put(out, key, value);
2675 }
2676 detail::putSection(out, "keys", _keys);
2677 if (!_showErrors.empty()) {
2678 detail::put(out, "showErrors", _showErrors);
2679 }
2680 if (!_prefetch.empty()) {
2681 detail::put(out, "prefetch", _prefetch);
2682 }
2683 detail::putSection(out, "components", _components);
2684 detail::putSection(out, "customize", _customize);
2685 detail::put(out, "view", view.json());
2686 return out;
2687 }
2688
2689private:
2690 Point addPoint(std::string const& name, Json decl) {
2691 detail::put(_customize, name, std::move(decl));
2692 return Point{name};
2693 }
2694
2695 Json _head = Json::object_t{};
2696 Json _params = Json::object_t{};
2697 Json _lifecycle = Json::object_t{};
2698 Json _keys = Json::object_t{};
2699 std::string _showErrors;
2700 Json::array_t _prefetch;
2701 Json _components = Json::object_t{};
2702 Json _customize = Json::object_t{};
2703};
2704
2705// ── Commands ─────────────────────────────────────────────────────────────────────────────────────────────────────
2706
2707namespace detail {
2708
2709/// @brief A command with one key.
2710/// @param key The command's key.
2711/// @param value The key's value.
2712/// @return The command.
2713[[nodiscard]] inline Command command(std::string_view key, Json value) {
2714 return Command{single(key, std::move(value))};
2715}
2716
2717} // namespace detail
2718
2719/// @brief `set`: writes a state, an input's value or an app name.
2720/// @param target The state or element, as its read.
2721/// @param value The value.
2722/// @return The command.
2723/// @throws AuthoringError when @p target is not a named read.
2724[[nodiscard]] inline Command set(AnyHandle const& target, AnyHandle const& value) {
2725 Json json = Json::object_t{};
2726 detail::put(json, "set", detail::refPath(target.json(), "`set`"));
2727 detail::put(json, "value", value.json());
2728 return Command{std::move(json)};
2729}
2730
2731/// @brief `reset`: a state to its init, or a dialog's scope.
2732/// @param target The state, as its read.
2733/// @return The command.
2734/// @throws AuthoringError when @p target is not a named read.
2735[[nodiscard]] inline Command reset(AnyHandle const& target) {
2736 return detail::command("reset", detail::refPath(target.json(), "`reset`"));
2737}
2738
2739/// @brief Options of `run`.
2741 /// @brief A list to run the mutation once per element of, as one batch.
2743 /// @brief With `each`, the element's name inside the body; without it, the name the reply is bound to for the
2744 /// later steps of the enclosing `seq`.
2745 std::string as;
2746};
2747
2748/// @brief `run`: runs a mutation.
2749/// @tparam Action The mutation's action.
2750/// @param mutation The mutation.
2751/// @param body The body; empty for the mutation's own.
2752/// @param options `each` and `as`.
2753/// @return The command.
2754template <typename Action>
2755[[nodiscard]] Command run(MutationHandle<Action> const& mutation, Body<Action> const& body = {},
2756 RunOptions const& options = {}) {
2757 Json json = Json::object_t{};
2758 detail::put(json, "run", mutation.name());
2759 if (!body.empty()) {
2760 detail::put(json, "body", body.json());
2761 }
2762 if (options.each.present()) {
2763 detail::put(json, "each", options.each.json());
2764 }
2765 if (!options.as.empty()) {
2766 detail::put(json, "as", options.as);
2767 }
2768 return Command{std::move(json)};
2769}
2770
2771/// @brief `refetch`: refetches a query.
2772/// @param query The query.
2773/// @return The command.
2774[[nodiscard]] inline Command refetch(QueryRef const& query) { return detail::command("refetch", query.name()); }
2775
2776/// @brief `open`: opens a dialog or drawer.
2777/// @param dialog The dialog.
2778/// @return The command.
2779[[nodiscard]] inline Command open(Element const& dialog) { return detail::command("open", dialog.name()); }
2780
2781/// @brief `close`: closes a dialog or drawer.
2782/// @param dialog The dialog.
2783/// @return The command.
2784[[nodiscard]] inline Command close(Element const& dialog) { return detail::command("close", dialog.name()); }
2785
2786/// @brief `close` of the dialog or drawer the command list runs in.
2787/// @param current `self`.
2788/// @return The command.
2789[[nodiscard]] inline Command close(Self current) {
2790 static_cast<void>(current);
2791 return detail::command("close", std::string{"self"});
2792}
2793
2794/// @brief `focus`: moves the keyboard focus to an element.
2795/// @param target The element.
2796/// @return The command.
2797[[nodiscard]] inline Command focus(Element const& target) { return detail::command("focus", target.name()); }
2798
2799/// @brief How `navigate` opens a screen.
2800enum class OpenAs : std::uint8_t {
2801 Default, ///< The host's choice.
2802 Tab, ///< A tab.
2803 Dialog, ///< A dialog, which returns through `onResult` or `onCancel`.
2804 Drawer, ///< A drawer.
2805 Window, ///< A window.
2806};
2807
2808/// @brief Options of `navigate`.
2810 /// @brief The screen's params, a record.
2812 /// @brief How the screen opens.
2814 /// @brief Commands run with the value a dialog returns, as `event`.
2816 /// @brief Commands run when a dialog is cancelled.
2818};
2819
2820namespace detail {
2821
2822/// @brief Writes `navigate`'s options.
2823/// @param json The command.
2824/// @param options The options.
2825/// @return The command.
2826[[nodiscard]] inline Command navigation(Json json, NavigateOptions const& options) {
2827 static constexpr std::array<char const*, 5> kModes = {"", "tab", "dialog", "drawer", "window"};
2828 if (options.params.present()) {
2829 detail::put(json, "params", options.params.json());
2830 }
2831 if (options.as != OpenAs::Default) {
2832 detail::put(json, "as", std::string{kModes.at(static_cast<std::size_t>(options.as))});
2833 }
2834 if (!options.onResult.empty()) {
2835 detail::put(json, "onResult", options.onResult.json());
2836 }
2837 if (!options.onCancel.empty()) {
2838 detail::put(json, "onCancel", options.onCancel.json());
2839 }
2840 return Command{std::move(json)};
2841}
2842
2843} // namespace detail
2844
2845/// @brief `navigate` to a screen by id.
2846/// @param screenId The screen.
2847/// @param options Params, how it opens, and what runs when it returns.
2848/// @return The command.
2849[[nodiscard]] inline Command navigate(std::string const& screenId, NavigateOptions const& options = {}) {
2850 return detail::navigation(detail::single("navigate", screenId), options);
2851}
2852
2853/// @brief `navigate` to a route.
2854/// @param route The route expression.
2855/// @param options Params, how it opens, and what runs when it returns.
2856/// @return The command.
2857[[nodiscard]] inline Command navigateRoute(AnyHandle const& route, NavigateOptions const& options = {}) {
2858 return detail::navigation(detail::single("navigate", detail::single("route", route.json())), options);
2859}
2860
2861/// @brief `return`: a dialog screen's result.
2862/// @param value The value.
2863/// @return The command.
2864[[nodiscard]] inline Command returnWith(AnyHandle const& value) { return detail::command("return", value.json()); }
2865
2866/// @brief `host`: a command the application registered with the client.
2867/// @param name The command.
2868/// @param args Its arguments; absent for none.
2869/// @return The command.
2870[[nodiscard]] inline Command host(std::string const& name, AnyHandle const& args = {}) {
2871 Json json = Json::object_t{};
2872 detail::put(json, "host", name);
2873 if (args.present()) {
2874 detail::put(json, "args", args.json());
2875 }
2876 return Command{std::move(json)};
2877}
2878
2879/// @brief `switchBackend`: switches to a named backend.
2880/// @param name The backend.
2881/// @return The command.
2882[[nodiscard]] inline Command switchBackend(std::string const& name) { return detail::command("switchBackend", name); }
2883
2884/// @brief `resetPrefs`: resets a customization point to the document's default.
2885/// @param point The point.
2886/// @return The command.
2887[[nodiscard]] inline Command resetPrefs(Point const& point) { return detail::command("resetPrefs", point.name()); }
2888
2889/// @brief `resetPrefs` of every point of the screen.
2890/// @return The command.
2891[[nodiscard]] inline Command resetAllPrefs() { return detail::command("resetPrefs", std::string{"all"}); }
2892
2893/// @brief `setLocale`: switches the client's locale.
2894/// @param locale The locale, such as `de`.
2895/// @return The command.
2896[[nodiscard]] inline Command setLocale(std::string const& locale) { return detail::command("setLocale", locale); }
2897
2898/// @brief The role of a notification.
2899enum class Role : std::uint8_t {
2900 Info, ///< Information.
2901 Success, ///< A success.
2902 Warning, ///< A warning.
2903 Error, ///< An error.
2904};
2905
2906/// @brief `notify`: shows a notification.
2907/// @param message The message.
2908/// @param role Its role.
2909/// @return The command.
2910[[nodiscard]] inline Command notify(AnyHandle const& message, Role role = Role::Info) {
2911 static constexpr std::array<char const*, 4> kRoles = {"info", "success", "warning", "error"};
2912 Json json = Json::object_t{};
2913 detail::put(json, "notify", message.json());
2914 if (role != Role::Info) {
2915 detail::put(json, "role", std::string{kRoles.at(static_cast<std::size_t>(role))});
2916 }
2917 return Command{std::move(json)};
2918}
2919
2920/// @brief `seq`: steps in order; a failed step stops the rest.
2921/// @param steps The steps.
2922/// @return The command.
2923[[nodiscard]] inline Command seq(Commands const& steps) { return detail::command("seq", steps.json()); }
2924
2925/// @brief `all`: steps started together.
2926/// @param steps The steps.
2927/// @return The command.
2928[[nodiscard]] inline Command all(Commands const& steps) { return detail::command("all", steps.json()); }
2929
2930/// @brief `if`: commands chosen by a condition.
2931/// @param condition The condition.
2932/// @param then The commands when it is true.
2933/// @param otherwise The commands otherwise; empty runs nothing.
2934/// @return The command.
2935[[nodiscard]] inline Command ifThen(AnyHandle const& condition, Commands const& then, Commands const& otherwise = {}) {
2936 Json::array_t parts{condition.json(), then.json()};
2937 if (!otherwise.empty()) {
2938 parts.push_back(otherwise.json());
2939 }
2940 return detail::command("if", std::move(parts));
2941}
2942
2943/// @brief `cancelClose`: keeps a screen open, inside `onClose`.
2944/// @return The command.
2945[[nodiscard]] inline Command cancelClose() { return detail::command("cancelClose", true); }
2946
2947/// @brief `retry`: refetches every failed query of a boundary, inside its `error`.
2948/// @return The command.
2949[[nodiscard]] inline Command retry() { return detail::command("retry", true); }
2950
2951/// @brief `signIn`: runs a sign-in mutation and installs the token it returns.
2952/// @param mutation The mutation.
2953/// @return The command.
2954[[nodiscard]] inline Command signIn(MutationRef const& mutation) {
2955 return detail::command("signIn", detail::single("from", mutation.name()));
2956}
2957
2958/// @brief `signOut`.
2959/// @return The command.
2960[[nodiscard]] inline Command signOut() { return detail::command("signOut", true); }
2961
2962/// @brief Options of `upload`.
2964 /// @brief The name the reply is bound to for later steps.
2965 std::string as;
2966 /// @brief The transfer id, whose `progress`, `pending` and `error` are element values.
2967 std::string transfer;
2968};
2969
2970/// @brief `upload`: sends a file through a mutation (`files/1`).
2971/// @param file The file reference.
2972/// @param mutation The mutation.
2973/// @param options `as` and the transfer id.
2974/// @return The command.
2975[[nodiscard]] inline Command upload(AnyHandle const& file, MutationRef const& mutation,
2976 UploadOptions const& options = {}) {
2977 Json json = Json::object_t{};
2978 detail::put(json, "upload", file.json());
2979 detail::put(json, "to", mutation.name());
2980 if (!options.as.empty()) {
2981 detail::put(json, "as", options.as);
2982 }
2983 if (!options.transfer.empty()) {
2984 detail::put(json, "id", options.transfer);
2985 }
2986 return Command{std::move(json)};
2987}
2988
2989/// @brief `download` to a file the user picks.
2990/// @param file The file reference.
2991/// @param name The suggested name; absent for none.
2992/// @return The command.
2993[[nodiscard]] inline Command downloadToFile(AnyHandle const& file, AnyHandle const& name = {}) {
2994 Json json = Json::object_t{};
2995 detail::put(json, "download", file.json());
2996 detail::put(json, "save", std::string{"dialog"});
2997 if (name.present()) {
2998 detail::put(json, "name", name.json());
2999 }
3000 return Command{std::move(json)};
3001}
3002
3003/// @brief `download` to a preview URL, bound to a state.
3004/// @param file The file reference.
3005/// @param state The state the URL is written to, as its read.
3006/// @return The command.
3007/// @throws AuthoringError when @p state is not a named read.
3008[[nodiscard]] inline Command downloadToPreview(AnyHandle const& file, AnyHandle const& state) {
3009 Json json = Json::object_t{};
3010 detail::put(json, "download", file.json());
3011 detail::put(json, "to", std::string{"preview"});
3012 detail::put(json, "as", detail::refPath(state.json(), "a download to preview"));
3013 return Command{std::move(json)};
3014}
3015
3016// ── View nodes ───────────────────────────────────────────────────────────────────────────────────────────────────
3017
3018namespace detail {
3019
3020/// @brief Builds a node's JSON key by key, leaving out what is absent.
3022public:
3023 /// @brief A node of a kind.
3024 /// @param kind The kind.
3025 explicit NodeWriter(std::string_view kind) { detail::put(_json, "kind", std::string{kind}); }
3026
3027 /// @brief An expression property.
3028 /// @param key The key.
3029 /// @param value The expression.
3030 /// @return This writer.
3031 NodeWriter& put(char const* key, AnyHandle const& value) {
3032 if (value.present()) {
3033 detail::put(_json, key, value.json());
3034 }
3035 return *this;
3036 }
3037
3038 /// @brief A command list.
3039 /// @param key The key.
3040 /// @param commands The commands.
3041 /// @return This writer.
3042 NodeWriter& put(char const* key, Commands const& commands) {
3043 if (!commands.empty()) {
3044 detail::put(_json, key, commands.json());
3045 }
3046 return *this;
3047 }
3048
3049 /// @brief A node list.
3050 /// @param key The key.
3051 /// @param nodes The nodes.
3052 /// @return This writer.
3053 NodeWriter& put(char const* key, Nodes const& nodes) {
3054 if (!nodes.empty()) {
3055 detail::put(_json, key, nodes.json());
3056 _facts.merge(nodes.facts());
3057 }
3058 return *this;
3059 }
3060
3061 /// @brief A plain string.
3062 /// @param key The key.
3063 /// @param value The string; empty leaves it out.
3064 /// @return This writer.
3065 NodeWriter& text(char const* key, std::string const& value) {
3066 if (!value.empty()) {
3067 detail::put(_json, key, value);
3068 }
3069 return *this;
3070 }
3071
3072 /// @brief Any JSON.
3073 /// @param key The key.
3074 /// @param value The JSON.
3075 /// @return This writer.
3076 NodeWriter& raw(char const* key, Json value) {
3077 detail::put(_json, key, std::move(value));
3078 return *this;
3079 }
3080
3081 /// @brief The declarations of the scope the node creates.
3082 /// @param scope The scope; one that declares nothing is left out.
3083 /// @return This writer.
3085 _facts.merge(scope.facts());
3086 if (!scope.empty()) {
3087 Json decls = Json::object_t{};
3088 scope.writeTo(decls);
3089 detail::put(_json, "scope", std::move(decls));
3090 }
3091 return *this;
3092 }
3093
3094 /// @brief Adds catalog facts.
3095 /// @param facts The facts.
3096 /// @return This writer.
3098 _facts.merge(facts);
3099 return *this;
3100 }
3101
3102 /// @brief The node.
3103 /// @return The node.
3104 [[nodiscard]] Node done() const { return Node{_json, _facts}; }
3105
3106private:
3107 Json _json = Json::object_t{};
3108 CatalogFacts _facts;
3109};
3110
3111} // namespace detail
3112
3113/// @brief Options of `text`.
3115 /// @brief The text.
3117 /// @brief The typographic role, such as `heading`.
3119 /// @brief Whether the shown value is being recalculated.
3121};
3122
3123/// @brief A `text` node.
3124/// @param options The text.
3125/// @return The node.
3126[[nodiscard]] inline Node text(TextOptions const& options) {
3127 return detail::NodeWriter{"text"}
3128 .put("text", options.text)
3129 .put("role", options.role)
3130 .put("stale", options.stale)
3131 .done();
3132}
3133
3134/// @brief Options of `button`.
3136 /// @brief The label: the button's text and its accessible name.
3138 /// @brief An icon of the theme's icon map; it never names the button.
3140 /// @brief How much it stands out; `Normal` writes no key.
3142 /// @brief Commands run on a click.
3144 /// @brief Commands run on activation by keyboard.
3146 /// @brief A push button or a list entry; absent writes no key, a push button.
3147 std::optional<ButtonStyle> style;
3148 /// @brief Whether a list entry is the current one, a condition; only with `ButtonStyle::Item`, which the loader
3149 /// checks.
3151};
3152
3153/// @brief A `button` node.
3154/// @param options The button.
3155/// @return The node.
3156[[nodiscard]] inline Node button(ButtonOptions const& options) {
3157 detail::NodeWriter writer{"button"};
3158 if (options.emphasis == ButtonEmphasis::Primary) {
3159 writer.put("emphasis", std::string{"primary"});
3160 } else if (options.emphasis == ButtonEmphasis::Danger) {
3161 writer.put("emphasis", std::string{"danger"});
3162 } else if (options.emphasis == ButtonEmphasis::Ghost) {
3163 writer.put("emphasis", std::string{"ghost"});
3164 }
3165 if (options.style) {
3166 writer.text("style", *options.style == ButtonStyle::Item ? "item" : "push");
3167 }
3168 return std::move(writer)
3169 .put("label", options.label)
3170 .put("icon", options.icon)
3171 .put("selected", options.selected)
3172 .put("onClick", options.onClick)
3173 .put("onActivate", options.onActivate)
3174 .done();
3175}
3176
3177/// @brief When a text input commits its draft.
3178enum class Commit : std::uint8_t {
3179 Change, ///< On every edit.
3180 Blur, ///< On loss of focus.
3181 Enter, ///< On Enter.
3182};
3183
3184/// @brief Options of `textInput`.
3186 /// @brief The value.
3188 /// @brief The label: the accessible name.
3190 /// @brief Placeholder text.
3192 /// @brief The input mode, such as `multiline` or `password`.
3194 /// @brief An icon of the theme's icon map, drawn at the input's leading edge; empty shows none. Refused on a
3195 /// multiline input.
3197 /// @brief When the draft commits.
3198 std::optional<Commit> commitOn;
3199 /// @brief Commands run on every edit.
3201 /// @brief Commands run on a commit.
3203 /// @brief Commands run on Enter in a single-line input.
3205};
3206
3207/// @brief A `textInput` node.
3208/// @param options The input.
3209/// @return The node.
3210[[nodiscard]] inline Node textInput(TextInputOptions const& options) {
3211 static constexpr std::array<char const*, 3> kCommits = {"change", "blur", "enter"};
3212 detail::NodeWriter node{"textInput"};
3213 node.put("value", options.value)
3214 .put("label", options.label)
3215 .put("placeholder", options.placeholder)
3216 .put("mode", options.mode)
3217 .put("icon", options.icon);
3218 if (options.commitOn) {
3219 node.text("commitOn", kCommits.at(static_cast<std::size_t>(*options.commitOn)));
3220 }
3221 return node.put("onChange", options.onChange)
3222 .put("onCommit", options.onCommit)
3223 .put("onSubmit", options.onSubmit)
3224 .done();
3225}
3226
3227/// @brief Options of `checkbox`.
3229 /// @brief The label.
3231 /// @brief Whether it is checked.
3233 /// @brief Commands run on a toggle.
3235 /// @brief Commands run on a change.
3237};
3238
3239/// @brief A `checkbox` node.
3240/// @param options The checkbox.
3241/// @return The node.
3242[[nodiscard]] inline Node checkbox(CheckboxOptions const& options) {
3243 return detail::NodeWriter{"checkbox"}
3244 .put("label", options.label)
3245 .put("checked", options.checked)
3246 .put("onToggle", options.onToggle)
3247 .put("onChange", options.onChange)
3248 .done();
3249}
3250
3251/// @brief What a select does with a selection its new options no longer hold.
3252enum class OnStale : std::uint8_t {
3253 Clear, ///< Clears it.
3254 Keep, ///< Keeps it.
3255};
3256
3257/// @brief Options of `select`.
3259 /// @brief The options: a list of `{key, label}` records.
3261 /// @brief The selected key.
3263 /// @brief The label: the accessible name.
3265 /// @brief The presentation, such as `radio`.
3267 /// @brief What happens to a selection the options no longer hold.
3268 std::optional<OnStale> onStale;
3269 /// @brief Commands run with the chosen key.
3271 /// @brief Commands run on a change.
3273};
3274
3275/// @brief A `select` node.
3276/// @param options The select.
3277/// @return The node.
3278[[nodiscard]] inline Node select(SelectOptions const& options) {
3279 detail::NodeWriter node{"select"};
3280 node.put("options", options.options)
3281 .put("selected", options.selected)
3282 .put("label", options.label)
3283 .put("style", options.style);
3284 if (options.onStale) {
3285 node.text("onStale", *options.onStale == OnStale::Clear ? "clear" : "keep");
3286 }
3287 return node.put("onChoose", options.onChoose).put("onChange", options.onChange).done();
3288}
3289
3290/// @brief An item of a `menu`; `items` nests a submenu.
3291struct MenuItem {
3292 /// @brief The label.
3294 /// @brief An icon.
3296 /// @brief The shortcut shown beside it.
3297 std::string keys;
3298 /// @brief Whether a checkable item is checked.
3300 /// @brief Whether it is enabled.
3302 /// @brief Commands run when it is chosen.
3304 /// @brief A submenu.
3305 std::vector<MenuItem> items;
3306};
3307
3308namespace detail {
3309
3310/// @brief A menu item's JSON.
3311/// @param item The item.
3312/// @return The JSON.
3313// NOLINTNEXTLINE(misc-no-recursion): a submenu is written item by item; the nesting is the document's own.
3314[[nodiscard]] inline Json menuItem(MenuItem const& item) {
3315 Json json = Json::object_t{};
3316 auto put = [&json](char const* key, AnyHandle const& value) {
3317 if (value.present()) {
3318 detail::put(json, key, value.json());
3319 }
3320 };
3321 put("label", item.label);
3322 put("icon", item.icon);
3323 if (!item.keys.empty()) {
3324 detail::put(json, "keys", item.keys);
3325 }
3326 put("checked", item.checked);
3327 put("enabled", item.enabled);
3328 if (!item.onClick.empty()) {
3329 detail::put(json, "onClick", item.onClick.json());
3330 }
3331 if (!item.items.empty()) {
3332 Json::array_t items;
3333 items.reserve(item.items.size());
3334 for (MenuItem const& child : item.items) {
3335 items.push_back(menuItem(child));
3336 }
3337 detail::put(json, "items", std::move(items));
3338 }
3339 return json;
3340}
3341
3342} // namespace detail
3343
3344/// @brief A `menu` node.
3345/// @param items The items.
3346/// @return The node.
3347[[nodiscard]] inline Node menu(std::vector<MenuItem> const& items) {
3348 Json::array_t array;
3349 array.reserve(items.size());
3350 for (MenuItem const& item : items) {
3351 array.push_back(detail::menuItem(item));
3352 }
3353 return detail::NodeWriter{"menu"}.raw("items", std::move(array)).done();
3354}
3355
3356/// @brief Options of `column`, `row` and `grid`.
3358 /// @brief The gap between children: a spacing step, 0 to `kMaxSpacingStep`.
3360 /// @brief A grid's columns; adaptive layouts read `env("widthClass")`.
3362 /// @brief Where the children sit across the axis; absent stretches them.
3363 std::optional<Align> align;
3364 /// @brief A row's or column's spare length along its axis; absent packs at the start. Not on a grid, whose
3365 /// columns share its width.
3366 std::optional<Justify> justify;
3367};
3368
3369namespace detail {
3370
3371/// @brief A row or column with its options.
3372/// @param kind `row` or `column`.
3373/// @param children The children.
3374/// @param options The gap and the alignment.
3375/// @return The node.
3376[[nodiscard]] inline Node stackNode(char const* kind, Nodes const& children, ContainerOptions const& options) {
3377 NodeWriter node{kind};
3378 node.put("children", children).put("gap", options.gap);
3379 if (options.align) {
3380 node.text("align", alignName(*options.align));
3381 }
3382 if (options.justify) {
3383 node.text("justify", justifyName(*options.justify));
3384 }
3385 return node.done();
3386}
3387
3388} // namespace detail
3389
3390/// @brief A `column` node.
3391/// @param children The children.
3392/// @param options The gap and the alignment.
3393/// @return The node.
3394[[nodiscard]] inline Node column(Nodes const& children, ContainerOptions const& options = {}) {
3395 return detail::stackNode("column", children, options);
3396}
3397
3398/// @brief A `row` node.
3399/// @param children The children.
3400/// @param options The gap and the alignment.
3401/// @return The node.
3402[[nodiscard]] inline Node row(Nodes const& children, ContainerOptions const& options = {}) {
3403 return detail::stackNode("row", children, options);
3404}
3405
3406/// @brief A `grid` node.
3407/// @param children The children.
3408/// @param options The gap, columns and alignment; a `justify` is written, and the loader refuses it.
3409/// @return The node.
3410[[nodiscard]] inline Node grid(Nodes const& children, ContainerOptions const& options = {}) {
3411 detail::NodeWriter node{"grid"};
3412 node.put("children", children).put("gap", options.gap).put("columns", options.columns);
3413 if (options.align) {
3414 node.text("align", detail::alignName(*options.align));
3415 }
3416 if (options.justify) {
3417 node.text("justify", detail::justifyName(*options.justify));
3418 }
3419 return node.done();
3420}
3421
3422/// @brief A `spacer` node.
3423/// @return The node.
3424[[nodiscard]] inline Node spacer() { return detail::NodeWriter{"spacer"}.done(); }
3425
3426/// @brief Options of `panel`.
3428 /// @brief The title.
3430 /// @brief Nodes shown beside the title.
3432 /// @brief The padding.
3434 /// @brief Whether the user may collapse it.
3436 /// @brief Whether it is collapsed.
3438 /// @brief Commands run on a collapse or expand.
3440};
3441
3442/// @brief A `panel` node.
3443/// @param children The children.
3444/// @param options The title, header and collapsing.
3445/// @return The node.
3446[[nodiscard]] inline Node panel(Nodes const& children, PanelOptions const& options = {}) {
3447 return detail::NodeWriter{"panel"}
3448 .put("title", options.title)
3449 .put("header", options.header)
3450 .put("padding", options.padding)
3451 .put("collapsible", options.collapsible)
3452 .put("collapsed", options.collapsed)
3453 .put("onToggle", options.onToggle)
3454 .put("children", children)
3455 .done();
3456}
3457
3458/// @brief A `scroll` node.
3459/// @param children The children.
3460/// @param axis The axis; absent for the kind's own.
3461/// @return The node.
3462[[nodiscard]] inline Node scroll(Nodes const& children, AnyHandle const& axis = {}) {
3463 return detail::NodeWriter{"scroll"}.put("axis", axis).put("children", children).done();
3464}
3465
3466/// @brief A scope's builder: declares on the scope and returns the nodes that read it.
3467using ScopeBuilder = std::function<Nodes(DeclScope&)>;
3468
3469/// @brief A case of a `switch` node, a scope of its own.
3470class Case {
3471public:
3472 /// @brief A case.
3473 /// @param when The value the `switch` value is compared with.
3474 /// @param children The nodes shown when it matches.
3475 Case(AnyHandle when, Nodes children) : _when{std::move(when)}, _children{std::move(children)} {}
3476
3477 /// @brief A case that declares on its own scope.
3478 /// @param when The value the `switch` value is compared with.
3479 /// @param builder Declares on the case's scope and returns its nodes.
3480 Case(AnyHandle when, ScopeBuilder const& builder) : _when{std::move(when)}, _children{builder(_scope)} {}
3481
3482 /// @brief The case's JSON and the facts it names.
3483 /// @return The part.
3484 [[nodiscard]] Node part() const {
3485 Json json = Json::object_t{};
3486 detail::put(json, "when", _when.json());
3487 CatalogFacts facts = _scope.facts();
3488 facts.merge(_children.facts());
3489 if (!_scope.empty()) {
3490 Json decls = Json::object_t{};
3491 _scope.writeTo(decls);
3492 detail::put(json, "scope", std::move(decls));
3493 }
3494 if (!_children.empty()) {
3495 detail::put(json, "children", _children.json());
3496 }
3497 return Node{std::move(json), std::move(facts)};
3498 }
3499
3500private:
3501 AnyHandle _when;
3502 DeclScope _scope;
3503 Nodes _children;
3504};
3505
3506/// @brief A `switch` node: shows the first case whose `when` equals its value.
3507/// @param value The value the cases are compared with.
3508/// @param cases The cases.
3509/// @param fallback The nodes shown when no case matches.
3510/// @return The node.
3511[[nodiscard]] inline Node switchOn(AnyHandle const& value, std::vector<Case> const& cases,
3512 Nodes const& fallback = {}) {
3513 detail::NodeWriter node{"switch"};
3514 node.put("on", value);
3515 Json::array_t parts;
3516 parts.reserve(cases.size());
3517 for (Case const& item : cases) {
3518 Node const written = item.part();
3519 parts.push_back(written.json());
3520 node.facts(written.facts());
3521 }
3522 return node.raw("cases", std::move(parts)).put("default", fallback).done();
3523}
3524
3525/// @brief A page of a `tabs` node.
3526struct Tab {
3527 /// @brief The label.
3529 /// @brief The page's nodes.
3531};
3532
3533/// @brief A `tabs` node.
3534/// @param tabs The pages.
3535/// @return The node.
3536[[nodiscard]] inline Node tabs(std::vector<Tab> const& tabs) {
3537 detail::NodeWriter node{"tabs"};
3538 Json::array_t parts;
3539 parts.reserve(tabs.size());
3540 for (Tab const& tab : tabs) {
3541 Json part = Json::object_t{};
3542 detail::put(part, "label", tab.label.json());
3543 detail::put(part, "children", tab.children.json());
3544 node.facts(tab.children.facts());
3545 parts.push_back(std::move(part));
3546 }
3547 return node.raw("tabs", std::move(parts)).done();
3548}
3549
3550/// @brief Options of `dialog` and `drawer`.
3552 /// @brief The title: the accessible name.
3554 /// @brief Whether it is open; absent for `open` and `close` commands alone.
3556 /// @brief When its element errors show.
3557 std::optional<ShowErrors> showErrors;
3558 /// @brief A drawer's side, `start` or `end`.
3560 /// @brief Commands run on Enter.
3562 /// @brief Commands run on Escape.
3564 /// @brief Commands run when the user dismisses it.
3566 /// @brief The buttons of its footer, run in its scope; each id once.
3567 std::vector<ActionButtonOptions> actions;
3568};
3569
3570namespace detail {
3571
3572/// @brief A dialog or drawer.
3573/// @param kind `dialog` or `drawer`.
3574/// @param element Its element.
3575/// @param options Its options.
3576/// @param builder Declares on its scope and returns its children.
3577/// @return The node.
3578[[nodiscard]] inline Node dialogNode(std::string_view kind, Element const& element, DialogOptions const& options,
3579 ScopeBuilder const& builder) {
3580 DeclScope scope;
3581 Nodes const children = builder(scope);
3582 NodeWriter node{kind};
3583 node.text("id", element.name()).put("title", options.title).put("open", options.open);
3584 if (options.showErrors) {
3585 node.text("showErrors", showErrorsName(*options.showErrors));
3586 }
3587 if (!options.actions.empty()) {
3588 Json::array_t actions;
3589 actions.reserve(options.actions.size());
3590 for (ActionButtonOptions const& action : options.actions) {
3591 actions.push_back(actionButton(action));
3592 }
3593 node.raw("actions", std::move(actions));
3594 }
3595 return node.put("side", options.side)
3596 .scope(scope)
3597 .put("children", children)
3598 .put("default", options.defaultAction)
3599 .put("cancel", options.cancel)
3600 .put("onDismiss", options.onDismiss)
3601 .done();
3602}
3603
3604} // namespace detail
3605
3606/// @brief A `dialog` node, a scope of its own; `open` and `close` name it by its element.
3607/// @param element The dialog's element.
3608/// @param children The children.
3609/// @param options The title and command lists.
3610/// @return The node.
3611[[nodiscard]] inline Node dialog(Element const& element, Nodes const& children, DialogOptions const& options = {}) {
3612 return detail::dialogNode("dialog", element, options, [&children](DeclScope&) { return children; });
3613}
3614
3615/// @brief A `dialog` node that declares on its own scope.
3616/// @param element The dialog's element.
3617/// @param options The title and command lists.
3618/// @param builder Declares on the dialog's scope and returns its children.
3619/// @return The node.
3620[[nodiscard]] inline Node dialog(Element const& element, DialogOptions const& options, ScopeBuilder const& builder) {
3621 return detail::dialogNode("dialog", element, options, builder);
3622}
3623
3624/// @brief A `drawer` node, a scope of its own.
3625/// @param element The drawer's element.
3626/// @param children The children.
3627/// @param options The title, side and command lists.
3628/// @return The node.
3629[[nodiscard]] inline Node drawer(Element const& element, Nodes const& children, DialogOptions const& options = {}) {
3630 return detail::dialogNode("drawer", element, options, [&children](DeclScope&) { return children; });
3631}
3632
3633/// @brief A `busy` node.
3634/// @param active Whether it shows.
3635/// @param label Its label.
3636/// @return The node.
3637[[nodiscard]] inline Node busy(AnyHandle const& active, AnyHandle const& label = {}) {
3638 return detail::NodeWriter{"busy"}.put("active", active).put("label", label).done();
3639}
3640
3641/// @brief Options of `forEach`.
3643 /// @brief The row key, read in the row scope.
3645 /// @brief The axis.
3647 /// @brief The gap between rows: a spacing step, 0 to `kMaxSpacingStep`.
3649};
3650
3651/// @brief A `forEach` node: its children once per row, each row a scope of its own.
3652/// @param rows The list.
3653/// @param options The key, axis and gap.
3654/// @param builder Declares on a row's scope and returns its nodes.
3655/// @return The node.
3656[[nodiscard]] inline Node forEach(AnyHandle const& rows, ForEachOptions const& options, ScopeBuilder const& builder) {
3657 DeclScope scope;
3658 Nodes const children = builder(scope);
3659 return detail::NodeWriter{"forEach"}
3660 .put("rows", rows)
3661 .put("key", options.key)
3662 .put("axis", options.axis)
3663 .put("gap", options.gap)
3664 .scope(scope)
3665 .put("children", children)
3666 .done();
3667}
3668
3669/// @brief A column of a `table`.
3671 /// @brief The column's id, which a `columns` customization point names.
3672 std::string name;
3673 /// @brief The header label.
3675 /// @brief The width.
3677 /// @brief The column's text role; absent writes no key, `Normal`.
3679 /// @brief The cell, read in the row scope.
3681};
3682
3683/// @brief Columns from cells alone, without ids or labels.
3684/// @param cells The cells, one per column.
3685/// @return The columns.
3686[[nodiscard]] inline std::vector<TableColumn> cells(std::vector<Node> const& cells) {
3687 std::vector<TableColumn> columns;
3688 columns.reserve(cells.size());
3689 for (Node const& cell : cells) {
3690 columns.push_back(TableColumn{.name = {}, .label = {}, .width = {}, .cell = Nodes{cell}});
3691 }
3692 return columns;
3693}
3694
3695/// @brief How a table's selection works.
3696enum class Selection : std::uint8_t {
3697 None, ///< No selection.
3698 Single, ///< One row.
3699 Multiple, ///< Several rows.
3700};
3701
3702/// @brief Where a model-backed table sorts, filters and pages.
3703enum class TableMode : std::uint8_t {
3704 Client, ///< In the client, over every row.
3705 Server, ///< In the server, through the action's table query.
3706};
3707
3708/// @brief Options of `table` and `dataTable`.
3710 /// @brief The row key, read in the row scope.
3712 /// @brief The columns; a model-backed table derives them from the row type when none are given.
3713 std::vector<TableColumn> columns;
3714 /// @brief Whether the header row is drawn. Several tables sharing one visual header set this `false` on every
3715 /// instance but the first.
3716 bool showHeader = true;
3717 /// @brief Below this many rows, every row's widget stays realized regardless of scroll position; a backend
3718 /// that cannot virtualize ignores this and `virtualizeOverscan` both.
3719 std::size_t virtualizeThreshold = 2000;
3720 /// @brief How many rows beyond the visible range, on each side, keep a realized widget.
3721 std::size_t virtualizeOverscan = 10;
3722 /// @brief How the selection works.
3723 std::optional<Selection> selection;
3724 /// @brief Where a model-backed table works; ignored by a table over rows.
3725 std::optional<TableMode> mode;
3726 /// @brief The sort.
3728 /// @brief The filters.
3730 /// @brief A row's tone, read in the row scope.
3732 /// @brief The key a linked chart highlights.
3734 /// @brief Commands run when a row is activated, in its row scope.
3736 /// @brief Shown instead of the table while its rows are an empty list, in the table's scope; empty shows the
3737 /// table always.
3739};
3740
3741namespace detail {
3742
3743/// @brief Writes a table's options after its source.
3744/// @param node The table so far.
3745/// @param options The options.
3746/// @param scope The row scope.
3747/// @param columns The columns.
3748/// @return The node.
3749[[nodiscard]] inline Node tableNode(NodeWriter& node, TableOptions const& options, DeclScope const& scope,
3750 std::vector<TableColumn> const& columns) {
3751 static constexpr std::array<char const*, 3> kSelections = {"none", "single", "multiple"};
3752 node.put("key", options.key);
3753 if (!columns.empty()) {
3754 Json::array_t parts;
3755 parts.reserve(columns.size());
3756 for (TableColumn const& column : columns) {
3757 Json part = Json::object_t{};
3758 if (!column.name.empty()) {
3759 detail::put(part, "id", column.name);
3760 }
3761 if (column.label.present()) {
3762 detail::put(part, "label", column.label.json());
3763 }
3764 if (column.width.present()) {
3765 detail::put(part, "width", column.width.json());
3766 }
3767 if (column.role.present()) {
3768 detail::put(part, "role", column.role.json());
3769 }
3770 if (!column.cell.empty()) {
3771 detail::put(part, "cell", column.cell.json());
3772 node.facts(column.cell.facts());
3773 }
3774 parts.push_back(std::move(part));
3775 }
3776 node.raw("columns", std::move(parts));
3777 }
3778 if (options.selection) {
3779 node.raw("selectionMode", std::string{kSelections.at(static_cast<std::size_t>(*options.selection))});
3780 }
3781 if (!options.showHeader) {
3782 node.raw("showHeader", false);
3783 }
3784 if (options.virtualizeThreshold != 2000) {
3785 node.raw("virtualizeThreshold", static_cast<std::int64_t>(options.virtualizeThreshold));
3786 }
3787 if (options.virtualizeOverscan != 10) {
3788 node.raw("virtualizeOverscan", static_cast<std::int64_t>(options.virtualizeOverscan));
3789 }
3790 return node.put("sort", options.sort)
3791 .put("filters", options.filters)
3792 .put("rowTone", options.rowTone)
3793 .put("highlightKey", options.highlightKey)
3794 .put("onActivate", options.onActivate)
3795 .put("empty", options.empty)
3796 .scope(scope)
3797 .done();
3798}
3799
3800} // namespace detail
3801
3802/// @brief A `table` over a list, with the columns in its options.
3803/// @param rows The rows.
3804/// @param options The key, columns, selection and command lists.
3805/// @return The node.
3806[[nodiscard]] inline Node table(AnyHandle const& rows, TableOptions const& options) {
3807 detail::NodeWriter node{"table"};
3808 node.put("rows", rows);
3809 return detail::tableNode(node, options, DeclScope{}, options.columns);
3810}
3811
3812/// @brief A `table` over a list whose rows declare on their own scope.
3813/// @param rows The rows.
3814/// @param options The key, selection and command lists.
3815/// @param builder Declares on a row's scope and returns the columns.
3816/// @return The node.
3817[[nodiscard]] inline Node table(AnyHandle const& rows, TableOptions const& options,
3818 std::function<std::vector<TableColumn>(DeclScope&)> const& builder) {
3819 DeclScope scope;
3820 std::vector<TableColumn> const columns = builder(scope);
3821 detail::NodeWriter node{"table"};
3822 node.put("rows", rows);
3823 return detail::tableNode(node, options, scope, columns);
3824}
3825
3826/// @brief A `table` declared from a model's list action (spec 7): the client derives its columns from the row type
3827/// and the table engine sorts, filters, selects and pages.
3828/// @tparam Action The list action.
3829/// @tparam Model The model type of the alias; it must run the action.
3830/// @param model The alias.
3831/// @param body The action's own parameters.
3832/// @param options The mode, selection, columns and command lists.
3833/// @return The node.
3834template <typename Action, typename Model>
3835 requires Runs<Model, Action>
3836[[nodiscard]] Node dataTable(ModelHandle<Model> const& model, Body<Action> const& body,
3837 TableOptions const& options = {}) {
3838 CatalogFacts facts;
3839 facts.action<Model, Action>();
3840 detail::NodeWriter node{"table"};
3841 node.text("model", model.alias()).text("action", std::string{::morph::model::ActionTraits<Action>::typeId()});
3842 if (!body.empty()) {
3843 node.raw("body", body.json());
3844 }
3845 if (options.mode) {
3846 node.raw("mode", std::string{*options.mode == TableMode::Server ? "server" : "client"});
3847 }
3848 node.facts(facts);
3849 return detail::tableNode(node, options, DeclScope{}, options.columns);
3850}
3851
3852/// @brief A `table` declared from a model's list action that takes no parameters of its own.
3853/// @tparam Action The list action.
3854/// @tparam Model The model type of the alias; it must run the action.
3855/// @param model The alias.
3856/// @param options The mode, selection, columns and command lists.
3857/// @return The node.
3858template <typename Action, typename Model>
3859 requires Runs<Model, Action>
3860[[nodiscard]] Node dataTable(ModelHandle<Model> const& model, TableOptions const& options = {}) {
3861 return dataTable<Action>(model, Body<Action>{}, options);
3862}
3863
3864/// @brief A `dateTimeInput` node.
3865/// @param value The value.
3866/// @param label The label.
3867/// @param commit Commands run on a commit.
3868/// @return The node.
3869[[nodiscard]] inline Node dateTimeInput(AnyHandle const& value, AnyHandle const& label, Commands const& commit = {}) {
3870 return detail::NodeWriter{"dateTimeInput"}.put("value", value).put("label", label).put("onCommit", commit).done();
3871}
3872
3873/// @brief Options of `dateTimeInput`.
3875 /// @brief The value.
3877 /// @brief The label: the accessible name.
3879 /// @brief The input mode, such as `date` or `time`.
3881 /// @brief Commands run on every edit.
3883 /// @brief Commands run on a commit.
3885};
3886
3887/// @brief A `dateTimeInput` node.
3888/// @param options The input.
3889/// @return The node.
3890[[nodiscard]] inline Node dateTimeInput(DateTimeInputOptions const& options) {
3891 return detail::NodeWriter{"dateTimeInput"}
3892 .put("value", options.value)
3893 .put("label", options.label)
3894 .put("mode", options.mode)
3895 .put("onChange", options.onChange)
3896 .put("onCommit", options.onCommit)
3897 .done();
3898}
3899
3900/// @brief Options of `slider`.
3902 /// @brief The value.
3904 /// @brief The label: the accessible name.
3906 /// @brief The minimum.
3908 /// @brief The maximum.
3910 /// @brief The step.
3912 /// @brief Commands run on a change.
3914 /// @brief Commands run on a commit.
3916};
3917
3918/// @brief A `slider` node.
3919/// @param options The slider.
3920/// @return The node.
3921[[nodiscard]] inline Node slider(SliderOptions const& options) {
3922 return detail::NodeWriter{"slider"}
3923 .put("value", options.value)
3924 .put("label", options.label)
3925 .put("min", options.min)
3926 .put("max", options.max)
3927 .put("step", options.step)
3928 .put("onChange", options.onChange)
3929 .put("onCommit", options.onCommit)
3930 .done();
3931}
3932
3933/// @brief Options of `filePicker`.
3935 /// @brief The label: the accessible name.
3937 /// @brief The accepted types.
3939 /// @brief Whether several files may be picked.
3941 /// @brief `open` or `save`.
3943 /// @brief Commands run with the picked files as `event`.
3945};
3946
3947/// @brief A `filePicker` node.
3948/// @param options The picker.
3949/// @return The node.
3950[[nodiscard]] inline Node filePicker(FilePickerOptions const& options) {
3951 return detail::NodeWriter{"filePicker"}
3952 .put("label", options.label)
3953 .put("accept", options.accept)
3954 .put("multiple", options.multiple)
3955 .put("mode", options.mode)
3956 .put("onPick", options.onPick)
3957 .done();
3958}
3959
3960/// @brief The action of a `banner` or `emptyState`.
3962 /// @brief The label.
3964 /// @brief Commands run when it is chosen.
3966};
3967
3968namespace detail {
3969
3970/// @brief Writes an action part.
3971/// @param node The node.
3972/// @param action The action; absent leaves it out.
3973inline void actionPart(NodeWriter& node, std::optional<ActionPart> const& action) {
3974 if (!action) {
3975 return;
3976 }
3977 Json part = Json::object_t{};
3978 if (action->label.present()) {
3979 detail::put(part, "label", action->label.json());
3980 }
3981 if (!action->onClick.empty()) {
3982 detail::put(part, "onClick", action->onClick.json());
3983 }
3984 node.raw("action", std::move(part));
3985}
3986
3987} // namespace detail
3988
3989/// @brief Options of `banner`.
3991 /// @brief The tone.
3993 /// @brief The text.
3995 /// @brief Whether the user may dismiss it.
3997 /// @brief Its action.
3998 std::optional<ActionPart> action;
3999 /// @brief Commands run when it is dismissed.
4001};
4002
4003/// @brief A `banner` node.
4004/// @param options The banner.
4005/// @return The node.
4006[[nodiscard]] inline Node banner(BannerOptions const& options) {
4007 detail::NodeWriter node{"banner"};
4008 node.put("tone", options.tone).put("text", options.text).put("dismissible", options.dismissible);
4009 detail::actionPart(node, options.action);
4010 return node.put("onDismiss", options.onDismiss).done();
4011}
4012
4013/// @brief A `badge` node.
4014/// @param text The text.
4015/// @param tone The tone.
4016/// @param icon An icon.
4017/// @return The node.
4018[[nodiscard]] inline Node badge(AnyHandle const& text, AnyHandle const& tone = {}, AnyHandle const& icon = {}) {
4019 return detail::NodeWriter{"badge"}.put("tone", tone).put("text", text).put("icon", icon).done();
4020}
4021
4022/// @brief A `progress` node.
4023/// @param value The fraction from 0 to 1; `null` is indeterminate.
4024/// @param label The label.
4025/// @return The node.
4026[[nodiscard]] inline Node progress(AnyHandle const& value, AnyHandle const& label = {}) {
4027 return detail::NodeWriter{"progress"}.put("value", value).put("label", label).done();
4028}
4029
4030/// @brief A `steps` node.
4031/// @param items The steps, `[{label, state}]`.
4032/// @param current The current step.
4033/// @return The node.
4034[[nodiscard]] inline Node steps(AnyHandle const& items, AnyHandle const& current = {}) {
4035 return detail::NodeWriter{"steps"}.put("items", items).put("current", current).done();
4036}
4037
4038/// @brief A `keyValue` node.
4039/// @param items The pairs, `[{label, value}]`.
4040/// @return The node.
4041[[nodiscard]] inline Node keyValue(AnyHandle const& items) {
4042 return detail::NodeWriter{"keyValue"}.put("items", items).done();
4043}
4044
4045/// @brief Options of `emptyState`.
4047 /// @brief The title.
4049 /// @brief The text.
4051 /// @brief An icon.
4053 /// @brief Its action.
4054 std::optional<ActionPart> action;
4055};
4056
4057/// @brief An `emptyState` node.
4058/// @param options The empty state.
4059/// @return The node.
4060[[nodiscard]] inline Node emptyState(EmptyStateOptions const& options) {
4061 detail::NodeWriter node{"emptyState"};
4062 node.put("title", options.title).put("text", options.text).put("icon", options.icon);
4063 detail::actionPart(node, options.action);
4064 return node.done();
4065}
4066
4067/// @brief A `splitter` node.
4068/// @param children The panes.
4069/// @param orientation The orientation.
4070/// @param sizes The initial sizes.
4071/// @return The node.
4072[[nodiscard]] inline Node splitter(Nodes const& children, AnyHandle const& orientation = {},
4073 AnyHandle const& sizes = {}) {
4074 return detail::NodeWriter{"splitter"}
4075 .put("orientation", orientation)
4076 .put("sizes", sizes)
4077 .put("children", children)
4078 .done();
4079}
4080
4081/// @brief Options of `collapsible`.
4083 /// @brief The title.
4085 /// @brief Whether it is open.
4087 /// @brief Nodes shown beside the title.
4089 /// @brief Commands run on a toggle.
4091};
4092
4093/// @brief A `collapsible` node.
4094/// @param children The content.
4095/// @param options The title, header and toggle.
4096/// @return The node.
4097[[nodiscard]] inline Node collapsible(Nodes const& children, CollapsibleOptions const& options = {}) {
4098 return detail::NodeWriter{"collapsible"}
4099 .put("title", options.title)
4100 .put("open", options.open)
4101 .put("header", options.header)
4102 .put("children", children)
4103 .put("onToggle", options.onToggle)
4104 .done();
4105}
4106
4107/// @brief Options of `dropZone`.
4109 /// @brief The accepted types.
4111 /// @brief Whether several files may be dropped.
4113 /// @brief Commands run with the dropped files as `event`.
4115};
4116
4117/// @brief A `dropZone` node.
4118/// @param children The content.
4119/// @param options The accepted files and the drop.
4120/// @return The node.
4121[[nodiscard]] inline Node dropZone(Nodes const& children, DropZoneOptions const& options = {}) {
4122 return detail::NodeWriter{"dropZone"}
4123 .put("accept", options.accept)
4124 .put("multiple", options.multiple)
4125 .put("children", children)
4126 .put("onDrop", options.onDrop)
4127 .done();
4128}
4129
4130/// @brief A `customize` node: the editor of a customization point.
4131/// @param point The point.
4132/// @return The node.
4133[[nodiscard]] inline Node customize(Point const& point) {
4134 return detail::NodeWriter{"customize"}.text("point", point.name()).done();
4135}
4136
4137/// @brief A `boundary` node: `loading` until its queries have values, `error` when one failed without one.
4138/// @param children The content.
4139/// @param loading The nodes shown while loading.
4140/// @param error The nodes shown on a failure; `ref("boundary.error")` and `retry()` are valid in them.
4141/// @return The node.
4142[[nodiscard]] inline Node boundary(Nodes const& children, Nodes const& loading = {}, Nodes const& error = {}) {
4143 return detail::NodeWriter{"boundary"}.put("loading", loading).put("error", error).put("children", children).done();
4144}
4145
4146/// @brief An overlay on one field of a form (spec 6 §10), applied to the parsed form model.
4148 /// @brief Leaves the field out.
4150 /// @brief Hides the field.
4152 /// @brief The label.
4154 /// @brief The display unit.
4156 /// @brief The display places.
4158 /// @brief The widget.
4160 /// @brief What an empty input is sent as.
4162};
4163
4164/// @brief One field of a form's `fields` overlay, by member pointer.
4165/// @tparam Action The form's action.
4166template <typename Action>
4168public:
4169 /// @brief An overlay.
4170 /// @tparam Member The member's type.
4171 /// @param member The member.
4172 /// @param settings The overlay.
4173 template <typename Member>
4174 FieldOverlay(Member Action::* member, FieldSettings settings)
4175 : _name{detail::memberName(member)}, _settings{std::move(settings)} {}
4176
4177 /// @brief The overlay's JSON.
4178 /// @return The field name and its settings.
4179 [[nodiscard]] std::pair<std::string, Json> json() const {
4180 Json settings = Json::object_t{};
4181 auto put = [&settings](char const* key, AnyHandle const& value) {
4182 if (value.present()) {
4183 detail::put(settings, key, value.json());
4184 }
4185 };
4186 put("omit", _settings.omit);
4187 put("hidden", _settings.hidden);
4188 put("label", _settings.label);
4189 put("unit", _settings.unit);
4190 put("decimals", _settings.decimals);
4191 put("widget", _settings.widget);
4192 put("blankAs", _settings.blankAs);
4193 return {_name, std::move(settings)};
4194 }
4195
4196private:
4197 std::string _name;
4198 FieldSettings _settings;
4199};
4200
4201/// @brief How a `form`, `collection` or `wizard` presents its forms: the keys every one of them takes.
4203 /// @brief A heading over the fields; on a collection or a wizard, the view's heading in place of the schema's.
4204 /// Absent shows none on a form.
4206 /// @brief The explicit-mode button's label, which the status line names; absent is `Submit`.
4208 /// @brief Whether the status line shows; absent shows it.
4209 std::optional<bool> showStatus;
4210 /// @brief The columns of every group's grid, 1 to 12; absent keeps the engine's.
4211 std::optional<int> columns;
4212 /// @brief Where every cell puts its caption; absent stacks it over the control.
4213 std::optional<::morph::forms::FieldLayout> fieldLayout;
4214};
4215
4216namespace detail {
4217
4218/// @brief Writes @p presentation's keys on @p node.
4219/// @param node The node being written.
4220/// @param presentation The keys.
4221inline void present(NodeWriter& node, FormPresentation const& presentation) {
4222 node.put("title", presentation.title).put("submitLabel", presentation.submitLabel);
4223 if (presentation.showStatus) {
4224 node.raw("showStatus", AnyHandle{*presentation.showStatus}.json());
4225 }
4226 if (presentation.columns) {
4227 node.raw("columns", AnyHandle{std::int64_t{*presentation.columns}}.json());
4228 }
4229 if (presentation.fieldLayout) {
4230 node.text("fieldLayout",
4231 *presentation.fieldLayout == ::morph::forms::FieldLayout::Inline ? "inline" : "stacked");
4232 }
4233}
4234
4235} // namespace detail
4236
4237/// @brief Options of `form`.
4238/// @tparam Action The form's action.
4239template <typename Action>
4241 /// @brief The values the form starts from.
4243 /// @brief The identity of the record `prefill` holds: a new value of the same key keeps the fields the user
4244 /// changed, a new key loads every field. Without one, only the first value keeps them.
4246 /// @brief How it submits, such as `explicit`.
4247 std::string submit;
4248 /// @brief When its errors show.
4249 std::optional<ShowErrors> showErrors;
4250 /// @brief The `fields` overlay.
4251 std::vector<FieldOverlay<Action>> fields;
4252 /// @brief Commands run on submit.
4254 /// @brief Commands run after a successful submit.
4256 /// @brief Commands run after a failed submit.
4258 /// @brief The title, button label, status line and columns.
4260};
4261
4262/// @brief A `form` node: a schema form of the forms engine over an action (spec 2); each field takes its label
4263/// from the schema or the overlay.
4264/// @tparam Action The action.
4265/// @tparam Model The model type of the alias; it must run the action.
4266/// @param model The alias.
4267/// @param options The prefill, overlay and command lists.
4268/// @return The node.
4269template <typename Action, typename Model>
4270 requires Runs<Model, Action>
4271[[nodiscard]] Node form(ModelHandle<Model> const& model, FormOptions<Action> const& options = {}) {
4272 CatalogFacts facts;
4273 facts.action<Model, Action>();
4274 detail::NodeWriter node{"form"};
4275 node.text("model", model.alias())
4276 .text("action", std::string{::morph::model::ActionTraits<Action>::typeId()})
4277 .put("prefill", options.prefill)
4278 .put("key", options.key)
4279 .text("submit", options.submit);
4280 if (options.showErrors) {
4281 node.text("showErrors", showErrorsName(*options.showErrors));
4282 }
4283 if (!options.fields.empty()) {
4284 Json overlay = Json::object_t{};
4285 for (FieldOverlay<Action> const& field : options.fields) {
4286 auto [name, settings] = field.json();
4287 detail::put(overlay, name, std::move(settings));
4288 }
4289 node.raw("fields", std::move(overlay));
4290 }
4291 detail::present(node, options.presentation);
4292 return node.put("onSubmit", options.onSubmit)
4293 .put("onSuccess", options.onSuccess)
4294 .put("onError", options.onError)
4295 .facts(facts)
4296 .done();
4297}
4298
4299/// @brief A `collection` node: the forms engine's list screen over an action.
4300/// @tparam Action The action.
4301/// @tparam Model The model type of the alias; it must run the action.
4302/// @param model The alias.
4303/// @param presentation The keys that reach every form the view draws; its title replaces the view's heading.
4304/// @return The node.
4305template <typename Action, typename Model>
4306 requires Runs<Model, Action>
4307[[nodiscard]] Node collection(ModelHandle<Model> const& model, FormPresentation const& presentation = {}) {
4308 CatalogFacts facts;
4309 facts.action<Model, Action>();
4310 detail::NodeWriter node{"collection"};
4311 node.text("model", model.alias()).text("action", std::string{::morph::model::ActionTraits<Action>::typeId()});
4312 detail::present(node, presentation);
4313 return node.facts(facts).done();
4314}
4315
4316/// @brief A `wizard` node: the forms engine's wizard over an action.
4317/// @tparam Action The action.
4318/// @tparam Model The model type of the alias; it must run the action.
4319/// @param model The alias.
4320/// @param presentation The keys that reach every step's form; its title replaces the view's heading.
4321/// @return The node.
4322template <typename Action, typename Model>
4323 requires Runs<Model, Action>
4324[[nodiscard]] Node wizard(ModelHandle<Model> const& model, FormPresentation const& presentation = {}) {
4325 CatalogFacts facts;
4326 facts.action<Model, Action>();
4327 detail::NodeWriter node{"wizard"};
4328 node.text("model", model.alias()).text("action", std::string{::morph::model::ActionTraits<Action>::typeId()});
4329 detail::present(node, presentation);
4330 return node.facts(facts).done();
4331}
4332
4333/// @brief Options of `custom`.
4335 /// @brief The props, by name.
4336 std::vector<std::pair<std::string, AnyHandle>> props;
4337 /// @brief The component's events and the commands they run.
4338 std::vector<std::pair<std::string, Commands>> on;
4339};
4340
4341/// @brief A `custom` node: a declared component with its props and events.
4342/// @param component The component.
4343/// @param options The props and events.
4344/// @return The node.
4345[[nodiscard]] inline Node custom(ComponentRef const& component, CustomOptions const& options = {}) {
4346 detail::NodeWriter node{"custom"};
4347 node.text("name", component.name());
4348 if (!options.props.empty()) {
4349 Json props = Json::object_t{};
4350 for (auto const& [name, value] : options.props) {
4351 detail::put(props, name, value.json());
4352 }
4353 node.raw("props", std::move(props));
4354 }
4355 if (!options.on.empty()) {
4356 Json events = Json::object_t{};
4357 for (auto const& [name, commands] : options.on) {
4358 detail::put(events, name, commands.json());
4359 }
4360 node.raw("on", std::move(events));
4361 }
4362 return node.done();
4363}
4364
4365// ── Accessibility ────────────────────────────────────────────────────────────────────────────────────────────────
4366
4367namespace detail {
4368
4369/// @brief Whether a node has a property that names it: present, and not the constant `""` or `null`.
4370/// @param node The node.
4371/// @param key The property.
4372/// @return Whether it names the node.
4373[[nodiscard]] inline bool names(DocNode const& node, std::string_view key) {
4374 ExprPtr const value = node.prop(key);
4375 if (value == nullptr) {
4376 return false;
4377 }
4378 if (value->kind != ExprKind::Literal) {
4379 return true;
4380 }
4381 std::string const* const text = value->literal.asString();
4382 return !value->literal.isNull() && (text == nullptr || !text->empty());
4383}
4384
4385/// @brief Whether a node has an `a11y.name` that names it.
4386/// @param node The node.
4387/// @return Whether it has.
4388[[nodiscard]] inline bool hasA11yName(DocNode const& node) {
4389 auto const parts = node.partList("a11y");
4390 return !parts.empty() && names(*parts.front(), "name");
4391}
4392
4393/// @brief The accessibility rule a node breaks, if any.
4394/// @param node The node.
4395/// @return The rule, or empty.
4396[[nodiscard]] inline std::string brokenRule(DocNode const& node) {
4397 static constexpr std::array<std::string_view, 6> kInputs = {"textInput", "dateTimeInput", "slider",
4398 "select", "checkbox", "filePicker"};
4399 std::string_view const kind = node.kind;
4400 if (std::ranges::find(kInputs, kind) != kInputs.end()) {
4401 if (names(node, "label") || hasA11yName(node)) {
4402 return {};
4403 }
4404 return "accessibility: a `" + node.kind + "` has a `label` or an `a11y.name`";
4405 }
4406 if (kind == "button") {
4407 if (names(node, "label") || hasA11yName(node)) {
4408 return {};
4409 }
4410 return "accessibility: a button without text has an `a11y.name`; an icon alone does not name it";
4411 }
4412 if (kind == "dialog" || kind == "drawer") {
4413 if (names(node, "title") || hasA11yName(node)) {
4414 return {};
4415 }
4416 return "accessibility: a `" + node.kind + "` has a `title` or an `a11y.name`";
4417 }
4418 if ((kind == "table" || kind == "custom") && !hasA11yName(node)) {
4419 return "accessibility: a `" + node.kind + "` has an `a11y.name`";
4420 }
4421 return {};
4422}
4423
4424// NOLINTBEGIN(misc-no-recursion): the view is walked node by node; its depth is bounded by kMaxJsonDepth.
4425
4426/// @brief The first accessibility rule a node or a node under it breaks.
4427/// @param node The node.
4428/// @param document The document's id.
4429/// @return The refusal, or nothing.
4430[[nodiscard]] inline std::optional<Refusal> accessibilityOf(DocNode const& node, std::string const& document);
4431
4432/// @brief The first accessibility rule the nodes of a list break.
4433/// @param nodes The nodes.
4434/// @param document The document's id.
4435/// @return The refusal, or nothing.
4436[[nodiscard]] inline std::optional<Refusal> accessibilityOf(std::span<DocNodePtr const> nodes,
4437 std::string const& document) {
4438 for (DocNodePtr const& child : nodes) {
4439 if (auto refusal = accessibilityOf(*child, document)) {
4440 return refusal;
4441 }
4442 }
4443 return std::nullopt;
4444}
4445
4446inline std::optional<Refusal> accessibilityOf(DocNode const& node, std::string const& document) {
4447 if (std::string rule = brokenRule(node); !rule.empty()) {
4448 return Refusal{.document = document, .path = node.path, .reason = std::move(rule)};
4449 }
4450 for (auto const& [key, nodes] : node.children) {
4451 if (auto refusal = accessibilityOf(std::span<DocNodePtr const>{nodes}, document)) {
4452 return refusal;
4453 }
4454 }
4455 for (auto const& [key, parts] : node.parts) {
4456 for (DocNodePtr const& part : parts) {
4457 for (auto const& [childKey, nodes] : part->children) {
4458 if (auto refusal = accessibilityOf(std::span<DocNodePtr const>{nodes}, document)) {
4459 return refusal;
4460 }
4461 }
4462 }
4463 }
4464 return std::nullopt;
4465}
4466
4467// NOLINTEND(misc-no-recursion)
4468
4469} // namespace detail
4470
4471/// @brief The accessibility rules of spec 5 §11, which registration runs after the load-time validation.
4472///
4473/// Every text input, date-time input, slider, select, checkbox and file picker has a `label` or an `a11y.name`; a
4474/// button without a `label`, every `custom` node and every `table` has an `a11y.name`; every `dialog` and `drawer`
4475/// has a `title` or an `a11y.name`. An `icon` never counts, and neither does a label or name written as the
4476/// constant `""` or `null`. A form field takes its field's label, so a `form` is not checked. The view and every
4477/// component's fallback are walked; the client does not run these rules.
4478/// @param document The loaded document.
4479/// @return The first rule broken, with the node's path, or nothing.
4480[[nodiscard]] inline std::optional<Refusal> checkAccessibility(Document const& document) {
4481 if (document.view) {
4482 if (auto refusal = detail::accessibilityOf(*document.view, document.id)) {
4483 return refusal;
4484 }
4485 }
4486 for (auto const& [name, component] : document.components) {
4487 if (component.fallback) {
4488 if (auto refusal = detail::accessibilityOf(*component.fallback, document.id)) {
4489 return refusal;
4490 }
4491 }
4492 }
4493 return std::nullopt;
4494}
4495
4496// ── Screens and registration ─────────────────────────────────────────────────────────────────────────────────────
4497
4498/// @brief A screen's emitted document: its JSON, the text that is served, and the catalog facts of its types.
4499struct Emitted {
4500 /// @brief The document.
4501 Json json = Json::object_t{};
4502 /// @brief The document as compact JSON text.
4503 std::string text;
4504 /// @brief The catalog facts of the models and actions it names.
4506};
4507
4508/// @brief A screen definition: an id and the builder that writes the document.
4509class Screen {
4510public:
4511 /// @brief Declares on the screen's scope and returns the view.
4512 using Builder = std::function<Node(Scope&)>;
4513
4514 /// @brief A screen.
4515 /// @param screenId The document id.
4516 /// @param builder The builder.
4517 Screen(std::string screenId, Builder builder) : _id{std::move(screenId)}, _builder{std::move(builder)} {}
4518
4519 /// @brief The document id.
4520 /// @return The id.
4521 [[nodiscard]] std::string const& id() const noexcept { return _id; }
4522
4523 /// @brief Runs the builder and writes the document.
4524 /// @return The document, or a refusal at the root naming what the builder could not write.
4525 [[nodiscard]] std::expected<Emitted, Refusal> write() const {
4526 try {
4527 Scope scope;
4528 Node const view = _builder(scope);
4529 Emitted out;
4530 out.json = scope.document(_id, view);
4531 out.facts = scope.facts();
4532 out.facts.merge(view.facts());
4533 // Writing a JSON tree cannot fail; empty text would be refused by the loader as not JSON.
4534 out.text = glz::write_json(out.json).value_or(std::string{});
4535 return out;
4536 } catch (std::exception const& error) {
4537 return std::unexpected(Refusal{.document = _id, .path = "", .reason = error.what()});
4538 } catch (...) {
4539 return std::unexpected(Refusal{.document = _id,
4540 .path = "",
4541 .reason = "the screen's builder threw an exception that is not a "
4542 "std::exception"});
4543 }
4544 }
4545
4546private:
4547 std::string _id;
4548 Builder _builder;
4549};
4550
4551/// @brief A screen with its id as a template argument, as spec 5 §11 writes it.
4552/// @tparam Id The document id.
4553/// @param builder The builder.
4554/// @return The screen.
4555template <morph::detail::FixedString Id>
4556[[nodiscard]] Screen screen(Screen::Builder builder) {
4557 return Screen{std::string{Id.view()}, std::move(builder)};
4558}
4559
4560/// @brief A screen.
4561/// @param screenId The document id.
4562/// @param builder The builder.
4563/// @return The screen.
4564[[nodiscard]] inline Screen screen(std::string screenId, Screen::Builder builder) {
4565 return Screen{std::move(screenId), std::move(builder)};
4566}
4567
4568/// @brief A registered screen: the text that is served and the document it loads as.
4570 /// @brief The document id.
4571 std::string id;
4572 /// @brief The document as compact JSON text.
4573 std::string text;
4574 /// @brief The loaded document.
4576};
4577
4578/// @brief The screens of an application, each validated once when it is registered.
4579///
4580/// Registration emits a screen's JSON once, loads it with `loadDocumentText` and the registry's catalog — the facts
4581/// of every model and action the registered screens name, the screen's own first — and then runs
4582/// `checkAccessibility`. A screen that fails is not registered.
4584public:
4585 /// @brief Registers a screen.
4586 /// @param screen The screen.
4587 /// @return The registered screen, or the first refusal: the builder's, the load-time validation's, an
4588 /// accessibility rule's, or a duplicate id.
4589 std::expected<RegisteredScreen const*, Refusal> add(Screen const& screen) {
4590 if (find(screen.id()) != nullptr) {
4591 return std::unexpected(
4592 Refusal{.document = screen.id(), .path = "/id", .reason = "a screen with this id is registered"});
4593 }
4594 auto emitted = screen.write();
4595 if (!emitted) {
4596 return std::unexpected(std::move(emitted.error()));
4597 }
4598 CatalogFacts facts = emitted->facts;
4599 facts.merge(_facts);
4600 auto loaded = loadDocumentText(emitted->text, LoadOptions{.catalog = facts.catalog()});
4601 if (!loaded) {
4602 return std::unexpected(std::move(loaded.error()));
4603 }
4604 if (auto refusal = checkAccessibility(*loaded)) {
4605 return std::unexpected(std::move(*refusal));
4606 }
4607 for (LoadWarning const& warning : loaded->warnings) {
4608 morph::log::logWarn(warning.message(loaded->id));
4609 }
4610 _facts = std::move(facts);
4611 _screens.push_back(std::make_unique<RegisteredScreen>(
4612 RegisteredScreen{.id = screen.id(), .text = std::move(emitted->text), .document = std::move(*loaded)}));
4613 return _screens.back().get();
4614 }
4615
4616 /// @brief Registers a screen and keeps its refusal rather than returning it, for registration at static
4617 /// initialisation, where nothing could report it; a server's test asserts `refusals()` is empty and
4618 /// `lost()` is zero.
4619 ///
4620 /// Never throws, because its caller is a static initialiser, where an exception cannot be caught. A builder's
4621 /// own exceptions are already refusals; anything thrown while making the screen or keeping its refusal, such
4622 /// as an allocation failure, is counted by `lost()`.
4623 /// @tparam Make A callable that returns the `Screen`.
4624 /// @param make Makes the screen; it is called inside the registration, so its exceptions are caught too.
4625 template <std::invocable Make>
4626 void enroll(Make const& make) noexcept {
4627 try {
4628 Screen const screen = make();
4629 if (auto added = add(screen); !added) {
4630 _refusals.push_back(std::move(added.error()));
4631 }
4632 } catch (...) {
4633 ++_lost;
4634 }
4635 }
4636
4637 /// @brief Registers a screen and keeps its refusal rather than returning it.
4638 /// @param screen The screen.
4639 void enroll(Screen const& screen) noexcept {
4640 enroll([&screen] { return screen; });
4641 }
4642
4643 /// @brief How many enrolments failed without a refusal to keep, because something threw outside the builder.
4644 /// @return The count.
4645 [[nodiscard]] std::size_t lost() const noexcept { return _lost; }
4646
4647 /// @brief A registered screen by id.
4648 /// @param screenId The id.
4649 /// @return The screen, or `nullptr`.
4650 [[nodiscard]] RegisteredScreen const* find(std::string_view screenId) const {
4651 for (auto const& registered : _screens) {
4652 if (registered->id == screenId) {
4653 return registered.get();
4654 }
4655 }
4656 return nullptr;
4657 }
4658
4659 /// @brief How many screens are registered.
4660 /// @return The count.
4661 [[nodiscard]] std::size_t size() const noexcept { return _screens.size(); }
4662
4663 /// @brief The refusals `enroll` kept.
4664 /// @return The refusals, in order.
4665 [[nodiscard]] std::vector<Refusal> const& refusals() const noexcept { return _refusals; }
4666
4667 /// @brief The catalog of every model and action the registered screens name, for a local-mode client.
4668 /// @return The catalog.
4669 [[nodiscard]] Catalog catalog() const { return _facts.catalog(); }
4670
4671private:
4672 std::vector<std::unique_ptr<RegisteredScreen>> _screens;
4673 std::vector<Refusal> _refusals;
4674 std::size_t _lost = 0;
4675 CatalogFacts _facts;
4676};
4677
4678/// @brief The process's screen registry, which `MORPH_REGISTER_SCREEN` enrols into.
4679/// @return The registry.
4680[[nodiscard]] inline ScreenRegistry& screenRegistry() noexcept {
4681 static ScreenRegistry registry;
4682 return registry;
4683}
4684
4685/// @brief Enrols a screen into the process's registry; what `MORPH_REGISTER_SCREEN` runs.
4686/// @tparam Make A callable that returns the `Screen`.
4687/// @param make Makes the screen, inside the registration.
4688/// @return `true`, for the static initialiser that runs it.
4689template <std::invocable Make>
4690bool enrollScreen(Make const& make) noexcept {
4691 screenRegistry().enroll(make);
4692 return true;
4693}
4694
4695// NOLINTEND(bugprone-easily-swappable-parameters)
4696
4697} // namespace morph::ui::author
4698
4699/// @brief Concatenates two tokens after expanding them.
4700// NOLINTBEGIN(cppcoreguidelines-macro-usage): registration at static initialisation is a macro, as for models.
4701#define MORPH_UI_AUTHOR_CAT_(a, b) a##b
4702/// @brief Concatenates two tokens after expanding them.
4703#define MORPH_UI_AUTHOR_CAT(a, b) MORPH_UI_AUTHOR_CAT_(a, b)
4704
4705// The registrar is a namespace-scope `const bool` initialised before main. `enrollScreen` is noexcept and keeps
4706// any failure as a refusal, but clang-tidy does not see through the template call and attributes a possible throw
4707// to every expansion site, so the check is suppressed here, once.
4708// NOLINTBEGIN(bugprone-throwing-static-initialization,cert-err58-cpp)
4709/// @brief Enrols a screen into the process's registry at static initialisation. A refusal is kept in
4710/// `screenRegistry().refusals()`, which a server's test asserts is empty.
4711/// @param SCREEN The screen: an expression, such as a call of a function that returns it, or an object defined
4712/// before the macro in the same translation unit.
4713#define MORPH_REGISTER_SCREEN(SCREEN) \
4714 namespace { \
4715 [[maybe_unused]] bool const MORPH_UI_AUTHOR_CAT(morphUiScreen_, \
4716 __COUNTER__) = ::morph::ui::author::enrollScreen([] { \
4717 return (SCREEN); \
4718 }); \
4719 }
4720// NOLINTEND(bugprone-throwing-static-initialization,cert-err58-cpp)
4721// NOLINTEND(cppcoreguidelines-macro-usage)
morph::ui::Align and morph::ui::Justify: where a container places its children across its axis and al...
Justify
Where a stack places its spare length along its axis, while no shown child stretches.
Definition alignment.hpp:23
Align
Where a child is placed across its container's axis: a column's horizontally, a row's vertically,...
Definition alignment.hpp:15
std::string typeName()
A document type name for a C++ type, as params and state declare it.
Definition author.hpp:2019
Node text(TextOptions const &options)
A text node.
Definition author.hpp:3126
Handle< detail::ArithmeticT< L, R > > operator+(L const &lhs, R const &rhs)
add.
Definition author.hpp:779
Handle< R > call(std::string_view function, std::vector< AnyHandle > const &args)
A call of any expr/1 library function, for the functions without a builder of their own.
Definition author.hpp:940
Mode
What a mutation run does while another is in flight.
Definition author.hpp:1624
@ Exclusive
The run is refused (the default).
Definition author.hpp:1625
bool hasA11yName(DocNode const &node)
Whether a node has an a11y.name that names it.
Definition author.hpp:4388
Command signOut()
signOut.
Definition author.hpp:2960
Element element(std::string name)
An element of the view.
Definition author.hpp:1289
Node spacer()
A spacer node.
Definition author.hpp:3424
Node dateTimeInput(AnyHandle const &value, AnyHandle const &label, Commands const &commit={})
A dateTimeInput node.
Definition author.hpp:3869
AnyHandle list(std::vector< AnyHandle > const &items)
{"list": [...]}: a list built from expressions.
Definition author.hpp:1123
LiteralOf< std::remove_cvref_t< L > >::type LiteralT
The value type a constant stands for.
Definition author.hpp:398
Json actionButton(ActionButtonOptions const &action)
An actionButton part's JSON.
Definition author.hpp:2457
Handle< T > ref(std::string const &path)
A named read, {"ref": "name.path"}: a param, state, let, query value, element value or bound name.
Definition author.hpp:1137
std::string refPath(Json const &json, std::string_view what)
The name a named read reads.
Definition author.hpp:603
Command resetPrefs(Point const &point)
resetPrefs: resets a customization point to the document's default.
Definition author.hpp:2887
Command notify(AnyHandle const &message, Role role=Role::Info)
notify: shows a notification.
Definition author.hpp:2910
Node scroll(Nodes const &children, AnyHandle const &axis={})
A scroll node.
Definition author.hpp:3462
OnStale
What a select does with a selection its new options no longer hold.
Definition author.hpp:3252
AnyHandle env(std::string const &name)
A client environment value, {"env": name}: widthClass, locale, platform, online, backend.
Definition author.hpp:1209
Node emptyState(EmptyStateOptions const &options)
An emptyState node.
Definition author.hpp:4060
Command command(std::string_view key, Json value)
A command with one key.
Definition author.hpp:2713
Node collapsible(Nodes const &children, CollapsibleOptions const &options={})
A collapsible node.
Definition author.hpp:4097
Handle< T > cond(Handle< bool > const &condition, Handle< T > const &then, Handle< T > const &otherwise)
{"if": [c, a, b]}: a choice between two values.
Definition author.hpp:1085
Command ifThen(AnyHandle const &condition, Commands const &then, Commands const &otherwise={})
if: commands chosen by a condition.
Definition author.hpp:2935
Command set(AnyHandle const &target, AnyHandle const &value)
set: writes a state, an input's value or an app name.
Definition author.hpp:2724
Node panel(Nodes const &children, PanelOptions const &options={})
A panel node.
Definition author.hpp:3446
Node column(Nodes const &children, ContainerOptions const &options={})
A column node.
Definition author.hpp:3394
AnyHandle sum(AnyHandle const &list)
sum of a list of numbers.
Definition author.hpp:1005
AnyHandle hostValue(std::string const &name)
A value the application registered with the client, {"host": name}.
Definition author.hpp:1221
Screen screen(Screen::Builder builder)
A screen with its id as a template argument, as spec 5 §11 writes it.
Definition author.hpp:4556
OpenAs
How navigate opens a screen.
Definition author.hpp:2800
@ Default
The host's choice.
Definition author.hpp:2801
Selection
How a table's selection works.
Definition author.hpp:3696
Command all(Commands const &steps)
all: steps started together.
Definition author.hpp:2928
void putPresent(Json &json, char const *key, AnyHandle const &value)
An expression key of a part, left out when absent.
Definition author.hpp:2436
Unwrapped< T >::type UnwrappedT
The type with one std::optional removed.
Definition author.hpp:299
Node drawer(Element const &element, Nodes const &children, DialogOptions const &options={})
A drawer node, a scope of its own.
Definition author.hpp:3629
Handle< std::string > format(AnyHandle const &pattern, std::vector< AnyHandle > args)
format: a template with {0}, {1}, … placeholders.
Definition author.hpp:970
consteval bool assignable()
Whether an expression of one value type can stand where a value of another is expected....
Definition author.hpp:341
Command downloadToFile(AnyHandle const &file, AnyHandle const &name={})
download to a file the user picks.
Definition author.hpp:2993
Command navigate(std::string const &screenId, NavigateOptions const &options={})
navigate to a screen by id.
Definition author.hpp:2849
Command returnWith(AnyHandle const &value)
return: a dialog screen's result.
Definition author.hpp:2864
Source
Which changes a watch reacts to.
Definition author.hpp:1718
@ User
Only the user's own edits.
Definition author.hpp:1720
Json single(std::string_view key, Json value)
A one-key JSON object.
Definition author.hpp:510
Node button(ButtonOptions const &options)
A button node.
Definition author.hpp:3156
Handle< T > row()
The innermost row, {"row": ""}.
Definition author.hpp:1145
Command host(std::string const &name, AnyHandle const &args={})
host: a command the application registered with the client.
Definition author.hpp:2870
bool enrollScreen(Make const &make) noexcept
Enrols a screen into the process's registry; what MORPH_REGISTER_SCREEN runs.
Definition author.hpp:4690
std::string showErrorsName(ShowErrors when)
The spelling of a ShowErrors.
Definition author.hpp:2331
Json menuItem(MenuItem const &item)
A menu item's JSON.
Definition author.hpp:3314
Json fieldOf(Json const &json, std::string_view field)
A field read below a named read.
Definition author.hpp:582
TableMode
Where a model-backed table sorts, filters and pages.
Definition author.hpp:3703
@ Server
In the server, through the action's table query.
Definition author.hpp:3705
Command run(MutationHandle< Action > const &mutation, Body< Action > const &body={}, RunOptions const &options={})
run: runs a mutation.
Definition author.hpp:2755
Node select(SelectOptions const &options)
A select node.
Definition author.hpp:3278
Handle< std::string > quantity(AnyHandle const &value)
quantity(x): a quantity for display in its own unit.
Definition author.hpp:1046
Command seq(Commands const &steps)
seq: steps in order; a failed step stops the rest.
Definition author.hpp:2923
Node busy(AnyHandle const &active, AnyHandle const &label={})
A busy node.
Definition author.hpp:3637
Handle< bool > eq(L const &lhs, R const &rhs)
eq: equality of magnitude. A function, not ==: an == that returns an expression rather than a bool br...
Definition author.hpp:918
Handle< detail::ArithmeticT< L, R > > operator-(L const &lhs, R const &rhs)
sub.
Definition author.hpp:791
Handle< bool > operator||(L const &lhs, R const &rhs)
or, with Kleene's three-valued logic.
Definition author.hpp:887
Command upload(AnyHandle const &file, MutationRef const &mutation, UploadOptions const &options={})
upload: sends a file through a mutation (files/1).
Definition author.hpp:2975
ShowErrors
When a scope's element errors show (spec 5 §6).
Definition author.hpp:2322
@ Touched
Once the element has been touched.
Definition author.hpp:2323
@ Submit
Once the form or dialog has been submitted.
Definition author.hpp:2324
Handle< bool > ne(L const &lhs, R const &rhs)
ne.
Definition author.hpp:930
Command retry()
retry: refetches every failed query of a boundary, inside its error.
Definition author.hpp:2949
std::conditional_t< IsHandle< L >::value, typename ValueOf< L >::type, typename ValueOf< R >::type > ArithmeticT
The value type of an arithmetic expression: the left handle's, else the right's.
Definition author.hpp:429
Command cancelClose()
cancelClose: keeps a screen open, inside onClose.
Definition author.hpp:2945
Handle< bool > engaged(AnyHandle const &value)
engaged: whether a value is not null.
Definition author.hpp:952
std::optional< Refusal > checkAccessibility(Document const &document)
The accessibility rules of spec 5 §11, which registration runs after the load-time validation.
Definition author.hpp:4480
AnyHandle prop(std::string const &name)
A prop of a component, inside the component's fallback.
Definition author.hpp:1228
AnyHandle session(std::string const &name)
A session attribute, {"session": name}.
Definition author.hpp:1214
AnyHandle content()
The content sizing, "content": as much as the content needs.
Definition author.hpp:1104
Command switchBackend(std::string const &name)
switchBackend: switches to a named backend.
Definition author.hpp:2882
Handle< Any > AnyHandle
An expression whose value type is not tracked: the type of every node property.
Definition author.hpp:739
std::function< Nodes(DeclScope &)> ScopeBuilder
A scope's builder: declares on the scope and returns the nodes that read it.
Definition author.hpp:3467
AnyHandle obj(std::vector< std::pair< std::string, AnyHandle > > const &fields)
{"obj": {...}}: a record built from expressions.
Definition author.hpp:1094
Handle< detail::ArithmeticT< L, R > > operator*(L const &lhs, R const &rhs)
mul.
Definition author.hpp:803
DeclScope RowScope
The scope a row of a table or forEach declares its own state, queries and model aliases on.
Definition author.hpp:2319
bool names(DocNode const &node, std::string_view key)
Whether a node has a property that names it: present, and not the constant "" or null.
Definition author.hpp:4373
Handle< bool > isEmpty(AnyHandle const &value)
isEmpty: an empty string or list.
Definition author.hpp:1018
std::vector< TableColumn > cells(std::vector< Node > const &cells)
Columns from cells alone, without ids or labels.
Definition author.hpp:3686
Node dialog(Element const &element, Nodes const &children, DialogOptions const &options={})
A dialog node, a scope of its own; open and close name it by its element.
Definition author.hpp:3611
std::optional< Refusal > accessibilityOf(DocNode const &node, std::string const &document)
The first accessibility rule a node or a node under it breaks.
Definition author.hpp:4446
Value scalarValue(L const &literal)
A C++ scalar as a document value.
Definition author.hpp:436
Role
The role of a notification.
Definition author.hpp:2899
Node collection(ModelHandle< Model > const &model, FormPresentation const &presentation={})
A collection node: the forms engine's list screen over an action.
Definition author.hpp:4307
Node switchOn(AnyHandle const &value, std::vector< Case > const &cases, Nodes const &fallback={})
A switch node: shows the first case whose when equals its value.
Definition author.hpp:3511
AnyHandle map(AnyHandle const &source, std::string const &name, std::type_identity_t< std::function< AnyHandle(Handle< E > const &)> > const &body)
A comprehension over a list, {"map": e, "as": "x", "to": e}.
Definition author.hpp:1171
Node customize(Point const &point)
A customize node: the editor of a customization point.
Definition author.hpp:4133
Command open(Element const &dialog)
open: opens a dialog or drawer.
Definition author.hpp:2779
Handle< bool > operator>=(L const &lhs, R const &rhs)
ge.
Definition author.hpp:863
Node steps(AnyHandle const &items, AnyHandle const &current={})
A steps node.
Definition author.hpp:4034
Instance
How a model alias binds when no key expression is given (spec 5 §5).
Definition author.hpp:1546
@ Private
An instance of the scope's own.
Definition author.hpp:1548
@ Default
shared when the catalog marks the model stateless, private otherwise.
Definition author.hpp:1547
Command focus(Element const &target)
focus: moves the keyboard focus to an element.
Definition author.hpp:2797
Node dataTable(ModelHandle< Model > const &model, Body< Action > const &body, TableOptions const &options={})
A table declared from a model's list action (spec 7): the client derives its columns from the row typ...
Definition author.hpp:3836
std::optional< std::pair< std::string, std::string > > namedRead(Json const &json)
The dotted path a named read reads, when the expression is one.
Definition author.hpp:564
Node badge(AnyHandle const &text, AnyHandle const &tone={}, AnyHandle const &icon={})
A badge node.
Definition author.hpp:4018
Node textInput(TextInputOptions const &options)
A textInput node.
Definition author.hpp:3210
Node banner(BannerOptions const &options)
A banner node.
Definition author.hpp:4006
AnyHandle parseDecimal(AnyHandle const &text)
parseDecimal(text): typed text, in the client's locale, as an exact decimal; null while it does not s...
Definition author.hpp:1032
std::optional< std::vector< ActionEffect > > effectsInfo(std::string_view actionId)
The catalog's form of an action's declared effects.
Definition author.hpp:126
Command reset(AnyHandle const &target)
reset: a state to its init, or a dialog's scope.
Definition author.hpp:2735
Node filePicker(FilePickerOptions const &options)
A filePicker node.
Definition author.hpp:3950
Node progress(AnyHandle const &value, AnyHandle const &label={})
A progress node.
Definition author.hpp:4026
PropType
A prop type of a component: how a value crosses into QML (spec 5 §10).
Definition author.hpp:2337
@ Exact
An exact decimal or quantity, as text.
Definition author.hpp:2341
@ Int
An integer, as text.
Definition author.hpp:2340
@ Json
Structured data, exact values inside it as text.
Definition author.hpp:2343
Node boundary(Nodes const &children, Nodes const &loading={}, Nodes const &error={})
A boundary node: loading until its queries have values, error when one failed without one.
Definition author.hpp:4142
void put(Json &object, std::string_view key, Json value)
Sets a member of a JSON object, replacing one of the same name and keeping the order of the others.
Definition author.hpp:473
Node keyValue(AnyHandle const &items)
A keyValue node.
Definition author.hpp:4041
Handle< Result > binary(std::string_view function, L const &lhs, R const &rhs)
A two-operand call.
Definition author.hpp:765
Handle< bool > operator&&(L const &lhs, R const &rhs)
and, with Kleene's three-valued logic. Both operands are written: a document has no short circuit.
Definition author.hpp:875
Json headBadge(HeadBadgeOptions const &badge)
A headBadge part's JSON.
Definition author.hpp:2445
std::string brokenRule(DocNode const &node)
The accessibility rule a node breaks, if any.
Definition author.hpp:4396
AnyHandle coalesce(std::vector< AnyHandle > const &values)
coalesce: the first value that is not null.
Definition author.hpp:957
AnyHandle round(AnyHandle const &value, std::int64_t places)
round(x, dp), half to even.
Definition author.hpp:1024
Handle< std::int64_t > count(AnyHandle const &list)
count: a list's length.
Definition author.hpp:1000
std::vector< ActionRecordInfo > recordsInfo(std::string_view actionId)
The catalog's form of the records an action's result holds.
Definition author.hpp:169
std::string alignName(Align align)
An alignment's document name.
Definition author.hpp:519
Json operandJson(X const &operand)
An operand's JSON.
Definition author.hpp:748
Node grid(Nodes const &children, ContainerOptions const &options={})
A grid node.
Definition author.hpp:3410
std::string justifyName(Justify justify)
A justification's document name.
Definition author.hpp:527
AnyHandle event()
The payload of the event whose command list runs.
Definition author.hpp:1232
void actionPart(NodeWriter &node, std::optional< ActionPart > const &action)
Writes an action part.
Definition author.hpp:3973
consteval bool scalarAssignable()
Whether a scalar value of one type can stand where a value of another is expected.
Definition author.hpp:327
Node tabs(std::vector< Tab > const &tabs)
A tabs node.
Definition author.hpp:3536
ScreenRegistry & screenRegistry() noexcept
The process's screen registry, which MORPH_REGISTER_SCREEN enrols into.
Definition author.hpp:4680
std::optional< Json > literalOf(Json const &expression)
The value of a constant expression, as constant writes one.
Definition author.hpp:535
AnyHandle concatLists(std::vector< AnyHandle > const &lists)
concatLists: the lists, one after another; null while any of them is null.
Definition author.hpp:981
Command refetch(QueryRef const &query)
refetch: refetches a query.
Definition author.hpp:2774
Node slider(SliderOptions const &options)
A slider node.
Definition author.hpp:3921
Node wizard(ModelHandle< Model > const &model, FormPresentation const &presentation={})
A wizard node: the forms engine's wizard over an action.
Definition author.hpp:4324
AnyHandle stretch(int weight=1)
A stretch sizing, {"lit": {"stretch": weight}}: a share of the leftover space.
Definition author.hpp:1116
Node menu(std::vector< MenuItem > const &items)
A menu node.
Definition author.hpp:3347
Node tableNode(NodeWriter &node, TableOptions const &options, DeclScope const &scope, std::vector< TableColumn > const &columns)
Writes a table's options after its source.
Definition author.hpp:3749
Handle< std::string > t(std::string const &key, std::string const &fallback, std::vector< std::pair< std::string, AnyHandle > > const &args={})
t(key, fallback, args): a translated MessageFormat 2 message.
Definition author.hpp:1063
Value literalValue(L const &literal)
A C++ constant as a document value.
Definition author.hpp:454
Command close(Element const &dialog)
close: closes a dialog or drawer.
Definition author.hpp:2784
Node dialogNode(std::string_view kind, Element const &element, DialogOptions const &options, ScopeBuilder const &builder)
A dialog or drawer.
Definition author.hpp:3578
Node custom(ComponentRef const &component, CustomOptions const &options={})
A custom node: a declared component with its props and events.
Definition author.hpp:4345
void putSection(Json &object, std::string_view key, Json const &section)
Sets a member of a JSON object to another object, unless that one is empty.
Definition author.hpp:481
Command resetAllPrefs()
resetPrefs of every point of the screen.
Definition author.hpp:2891
Handle< std::string > concat(std::vector< AnyHandle > const &parts)
concat.
Definition author.hpp:962
Command navigateRoute(AnyHandle const &route, NavigateOptions const &options={})
navigate to a route.
Definition author.hpp:2857
Handle< bool > operator!(Handle< T > const &operand)
not.
Definition author.hpp:896
std::size_t members(Json const &object)
How many members a JSON object has.
Definition author.hpp:490
Command downloadToPreview(AnyHandle const &file, AnyHandle const &state)
download to a preview URL, bound to a state.
Definition author.hpp:3008
AnyHandle fixed(int units)
A fixed sizing, {"lit": {"fixed": units}}.
Definition author.hpp:1109
Json call(std::string_view function, std::vector< Json > args)
A call of an expr/1 library function.
Definition author.hpp:553
Handle< bool > operator>(L const &lhs, R const &rhs)
gt.
Definition author.hpp:851
constexpr Self self
The dialog or drawer a command list runs in.
Definition author.hpp:1295
AnyHandle filter(AnyHandle const &source, std::string const &name, std::type_identity_t< std::function< Handle< bool >(Handle< E > const &)> > const &where)
A filter over a list, {"filter": e, "as": "x", "where": e}.
Definition author.hpp:1187
Node splitter(Nodes const &children, AnyHandle const &orientation={}, AnyHandle const &sizes={})
A splitter node.
Definition author.hpp:4072
Command setLocale(std::string const &locale)
setLocale: switches the client's locale.
Definition author.hpp:2896
Node table(AnyHandle const &rows, TableOptions const &options)
A table over a list, with the columns in its options.
Definition author.hpp:3806
std::optional< std::vector< std::string > > listFields()
The members of an action result that hold a list, when the result is a record.
Definition author.hpp:102
Handle< bool > includes(AnyHandle const &list, AnyHandle const &value)
includes: whether a list holds a value.
Definition author.hpp:1011
Node form(ModelHandle< Model > const &model, FormOptions< Action > const &options={})
A form node: a schema form of the forms engine over an action (spec 2); each field takes its label fr...
Definition author.hpp:4271
Node stackNode(char const *kind, Nodes const &children, ContainerOptions const &options)
A row or column with its options.
Definition author.hpp:3376
Handle< detail::ArithmeticT< L, R > > operator/(L const &lhs, R const &rhs)
div.
Definition author.hpp:815
Command navigation(Json json, NavigateOptions const &options)
Writes navigate's options.
Definition author.hpp:2826
Commit
When a text input commits its draft.
Definition author.hpp:3178
@ Blur
On loss of focus.
Definition author.hpp:3180
@ Change
On every edit.
Definition author.hpp:3179
Node dropZone(Nodes const &children, DropZoneOptions const &options={})
A dropZone node.
Definition author.hpp:4121
Json constant(Value const &value)
A document value as a constant expression: a scalar as itself, a list, record, decimal or quantity wr...
Definition author.hpp:496
void append(Json &json, char const *key, Json item)
Appends item to the array at key of json, making the array on first use.
Definition author.hpp:2481
Node checkbox(CheckboxOptions const &options)
A checkbox node.
Definition author.hpp:3242
void present(NodeWriter &node, FormPresentation const &presentation)
Writes presentation's keys on node.
Definition author.hpp:4221
std::string memberName(Member Class::*member)
The reflected name of a data member, found by its address in a value-initialised object of its class.
Definition author.hpp:618
Node forEach(AnyHandle const &rows, ForEachOptions const &options, ScopeBuilder const &builder)
A forEach node: its children once per row, each row a scope of its own.
Definition author.hpp:3656
Command signIn(MutationRef const &mutation)
signIn: runs a sign-in mutation and installs the token it returns.
Definition author.hpp:2954
morph::ui::ButtonEmphasis: how much a button stands out. The view tree, the authoring API and the bac...
ButtonEmphasis
How much a button stands out; each backend maps it to its theme.
@ Primary
The screen's main action.
@ Ghost
An action embedded in other content, with no chrome of its own.
@ Normal
An ordinary action.
@ Danger
An action that destroys or cannot be undone.
morph::ui::ButtonStyle: whether a button is drawn as a push button or as an entry of a list....
@ Item
An entry of a list: as wide as its line, its label at the start, unframed, and marked while selected.
void addDerivedChanges(Resolved const &resolved, std::string const &source, Changes &changes)
Adds a whole-type change for every record derived, directly or through others, from source.
Definition changes.hpp:137
The document's typed value (spec 5 §3): what every expression evaluates to.
Definition value.hpp:157
What a builder throws when it is given something a document cannot say, such as a field read from a v...
Definition author.hpp:61
One field of an action body: a member of the action, and the expression that fills it.
Definition author.hpp:1471
Json const & json() const noexcept
The field's expression.
Definition author.hpp:1490
std::string const & name() const noexcept
The field's JSON name.
Definition author.hpp:1486
BodyField(Member Action::*member, V const &value)
A field: a member pointer of the action, so a renamed field or another action's field is a compile er...
Definition author.hpp:1481
An action body: the action's members with expressions in place of values (spec 5 §11)....
Definition author.hpp:1501
Json json() const
The body's expression.
Definition author.hpp:1516
Body(std::initializer_list< BodyField< Action > > fields)
A body.
Definition author.hpp:1508
bool empty() const noexcept
Whether the body has no fields.
Definition author.hpp:1512
Body()=default
An empty body; the query or mutation sends an empty object.
Node part() const
The case's JSON and the facts it names.
Definition author.hpp:3484
Case(AnyHandle when, ScopeBuilder const &builder)
A case that declares on its own scope.
Definition author.hpp:3480
Case(AnyHandle when, Nodes children)
A case.
Definition author.hpp:3475
One command of ui/1 (spec 5 §8, spec 6).
Definition author.hpp:1672
Json const & json() const noexcept
The command's JSON.
Definition author.hpp:1682
Command(Json json)
A command over its JSON, for a form no builder spells.
Definition author.hpp:1678
A command list, run as one batch.
Definition author.hpp:1646
bool empty() const noexcept
Whether the list is empty.
Definition author.hpp:1661
Commands()=default
No commands.
Json json() const
The list's JSON.
Definition author.hpp:1665
A declared component, for custom nodes.
Definition author.hpp:2364
ComponentRef(std::string name)
A component by name.
Definition author.hpp:2368
std::string const & name() const noexcept
The component's name.
Definition author.hpp:2372
A scope's declarations (spec 5 §3): model aliases, state, let, queries, mutations,...
Definition author.hpp:2045
MutationHandle< Action > mutation(std::string const &name, ModelHandle< Model > const &model, MutationOptions< Action > const &options={})
A mutation (spec 5 §5).
Definition author.hpp:2151
void writeTo(Json &out) const
Writes the declarations into a document or a "scope" object, in the document's order.
Definition author.hpp:2214
ModelHandle< Model > model(std::string const &alias, ModelOptions const &options={})
A model alias.
Definition author.hpp:2056
QueryHandle< Action > query(std::string const &name, ModelHandle< Model > const &model, Body< Action > const &body={}, QueryOptions const &options={})
A query (spec 5 §5).
Definition author.hpp:2118
Handle< T > let(std::string const &name, Handle< T > const &value)
A derived value (let).
Definition author.hpp:2103
Handle< T > state(std::string const &name)
State with a declared type and no initial value; it starts null.
Definition author.hpp:2072
void watch(AnyHandle const &watched, Commands const &commands, Source source=Source::Any)
A cascade: commands that run when a value changes (spec 5 §6).
Definition author.hpp:2170
CatalogFacts & recordedFacts() noexcept
The catalog facts, for a scope that records more.
Definition author.hpp:2235
DeclScope()=default
An empty scope.
bool empty() const
Whether the scope declares nothing.
Definition author.hpp:2205
void check(CheckOptions const &options)
A validation across elements (spec 5 §6).
Definition author.hpp:2182
CatalogFacts const & facts() const noexcept
The catalog facts of the models and actions the scope names.
Definition author.hpp:2230
QueryHandle< Action, Action > evaluate(std::string const &name, ModelHandle< Model > const &model, Body< Action > const &body={}, QueryOptions const &options={})
A query that evaluates an action without running it: the server returns its calculated fields and fie...
Definition author.hpp:2136
Handle< detail::LiteralT< L > > state(std::string const &name, L const &init)
State initialised from a constant; its type is the constant's.
Definition author.hpp:2093
Handle< T > state(std::string const &name, Handle< T > const &init)
State initialised from an expression; its type is T's.
Definition author.hpp:2082
An element of the view, named by its id, whose live values relations read (spec 5 §6).
Definition author.hpp:1235
Handle< bool > checked() const
A checkbox's state.
Definition author.hpp:1260
AnyHandle get(std::string const &value) const
One of the element's values.
Definition author.hpp:1248
AnyHandle selected() const
A select's chosen key.
Definition author.hpp:1264
Handle< bool > open() const
Whether a dialog or drawer is open.
Definition author.hpp:1272
Element(std::string name)
An element.
Definition author.hpp:1239
AnyHandle value() const
An input's committed value.
Definition author.hpp:1252
Handle< bool > valid() const
Whether a form, dialog or element is valid.
Definition author.hpp:1276
std::string const & name() const noexcept
The element's id.
Definition author.hpp:1243
Handle< bool > ready() const
Whether a form or dialog is ready to submit.
Definition author.hpp:1280
AnyHandle draft() const
An input's text while it is edited.
Definition author.hpp:1256
AnyHandle selection() const
A table's selection.
Definition author.hpp:1268
One field of a form's fields overlay, by member pointer.
Definition author.hpp:4167
std::pair< std::string, Json > json() const
The overlay's JSON.
Definition author.hpp:4179
FieldOverlay(Member Action::*member, FieldSettings settings)
An overlay.
Definition author.hpp:4174
An expression of a document, typed by the C++ value it evaluates to (spec 5 §11).
Definition author.hpp:648
Handle(L const &literal)
A constant.
Definition author.hpp:661
static Handle raw(Json json)
A handle over an expression's JSON, for a form no builder spells.
Definition author.hpp:675
Handle< Member > at(Member Class::*member) const
A field of the value, by member pointer: a renamed field is a compile error.
Definition author.hpp:703
Handle()=default
An absent handle.
Json const & json() const
Definition author.hpp:688
Handle orElse(Handle const &fallback) const
The value, or a fallback when it is null.
Definition author.hpp:718
bool present() const noexcept
Whether the handle holds an expression.
Definition author.hpp:683
T ValueType
The C++ type of the value.
Definition author.hpp:651
Handle< Any > at(std::string_view field) const
A field of the value, by name.
Definition author.hpp:711
Handle(Handle< U > const &other)
A handle of another value type that can stand for this one.
Definition author.hpp:669
A model alias's instance: a binding mode, a key expression, or a borrowed binding.
Definition author.hpp:1559
InstanceSpec(Borrow borrow)
A borrowed binding.
Definition author.hpp:1571
InstanceSpec(Handle< T > const &key)
A key expression: the shared instance the key selects. The model must be keyed.
Definition author.hpp:1578
std::optional< Json > json() const
The instance JSON.
Definition author.hpp:1583
InstanceSpec()=default
The default binding.
InstanceSpec(Instance mode)
A binding mode.
Definition author.hpp:1566
A model alias of a scope.
Definition author.hpp:1531
ModelHandle(std::string alias)
An alias.
Definition author.hpp:1535
std::string const & alias() const noexcept
The alias.
Definition author.hpp:1539
A mutation declared by a scope.
Definition author.hpp:1421
Handle< bool > can() const
Whether the signed-in principal may run the mutation.
Definition author.hpp:1458
std::string const & name() const noexcept
The mutation's name.
Definition author.hpp:1432
Handle< bool > pending() const
Whether a run is in flight.
Definition author.hpp:1442
MutationHandle(std::string name)
A mutation by name.
Definition author.hpp:1428
Handle< std::string > error() const
The last run's error text.
Definition author.hpp:1446
Handle< std::string > errorKind() const
The kind of the last run's error.
Definition author.hpp:1452
ResultHandle< Result > result() const
The last run's result; bind it with let to read its fields.
Definition author.hpp:1436
::morph::model::ActionTraits< Action >::Result Result
The result's C++ type.
Definition author.hpp:1424
A mutation of a scope, without its action type: what signIn, upload and can name.
Definition author.hpp:1313
std::string const & name() const noexcept
The mutation's name.
Definition author.hpp:1321
MutationRef(std::string name)
A mutation by name.
Definition author.hpp:1317
A view node (spec 5 §9). The kind's own properties come from its builder's options; the properties ev...
Definition author.hpp:1778
Node withText(std::string const &key, std::string const &value) const
A property set to a plain string.
Definition author.hpp:1809
Node readonly(AnyHandle const &condition) const
Whether an input or text is read-only.
Definition author.hpp:1969
Node drag(AnyHandle const &payload) const
Makes the node draggable.
Definition author.hpp:1934
Json const & json() const noexcept
The node's JSON.
Definition author.hpp:1787
Node stale(AnyHandle const &condition) const
Whether an input or text shows a value that is being recalculated.
Definition author.hpp:1984
CatalogFacts const & facts() const noexcept
The catalog facts of the models and actions the node names.
Definition author.hpp:1791
Node span(AnyHandle const &columns) const
The grid columns the node spans.
Definition author.hpp:1897
Node tooltip(AnyHandle const &text) const
A tooltip.
Definition author.hpp:1833
Node autofocus(AnyHandle const &focus) const
Whether the node takes focus when it mounts.
Definition author.hpp:1902
Node alignSelf(Align align) const
Where the node sits in its row, column or grid, in place of the container's align.
Definition author.hpp:1872
Node surface(std::string const &name) const
A named token set for the subtree.
Definition author.hpp:1912
Node testId(std::string const &name) const
A stable identifier for automated tests.
Definition author.hpp:1907
Node visible(AnyHandle const &condition) const
Whether the node shows.
Definition author.hpp:1823
Node a11y(AnyHandle const &name, std::string const &role={}) const
The accessible name and role.
Definition author.hpp:1918
Node errors(AnyHandle const &errors) const
Errors shown on an input or text.
Definition author.hpp:1979
Node maxWidth(int units) const
The most width, in backend units.
Definition author.hpp:1882
Node id(Element const &element) const
The element id, by which relations read its values.
Definition author.hpp:1818
Node required(AnyHandle const &condition) const
Whether an input or text is required.
Definition author.hpp:1974
Node key(std::string const &chord, Commands const &commands) const
A key binding.
Definition author.hpp:1959
Node(Json json, CatalogFacts facts={})
A node over its JSON, for a kind no builder spells.
Definition author.hpp:1783
Node sizing(AnyHandle const &width, AnyHandle const &height) const
The sizing of both dimensions, as a {width, height} record; an absent one is content....
Definition author.hpp:1847
Node minHeight(int units) const
The least height, in backend units.
Definition author.hpp:1887
Node maxHeight(int units) const
The most height, in backend units.
Definition author.hpp:1892
Node minWidth(int units) const
The least width, in backend units.
Definition author.hpp:1877
Node drop(AnyHandle const &accepts, Commands const &onDrop) const
Makes the node a drop target.
Definition author.hpp:1944
Node enabled(AnyHandle const &condition) const
Whether the node is enabled.
Definition author.hpp:1828
Node sizing(AnyHandle const &sizing) const
The width's sizing: "content", {"fixed": n} or {"stretch": w}, such as content(), fixed(n) or stretch...
Definition author.hpp:1839
Node with(std::string const &key, AnyHandle const &value) const
A property set to an expression; an absent expression leaves the node as it is.
Definition author.hpp:1797
A list of view nodes.
Definition author.hpp:1740
Nodes()=default
No nodes.
Json json() const
The list's JSON.
Definition author.hpp:1763
CatalogFacts const & facts() const noexcept
The catalog facts of the models and actions the nodes name.
Definition author.hpp:1767
bool empty() const noexcept
Whether the list is empty.
Definition author.hpp:1759
A customization point (spec 5 §12b).
Definition author.hpp:2379
std::string const & name() const noexcept
The point's name.
Definition author.hpp:2387
Point(std::string name)
A point by name.
Definition author.hpp:2383
AnyHandle pref(std::string const &path={}) const
The user's choice at the point.
Definition author.hpp:2392
A query declared by a scope.
Definition author.hpp:1332
Handle< bool > can() const
Whether the signed-in principal may run the query.
Definition author.hpp:1378
Handle< Result > value() const
The query's value; the last one is kept while a refetch is in flight.
Definition author.hpp:1347
Value Result
The value's C++ type.
Definition author.hpp:1335
Handle< bool > pending() const
Whether the query is in flight.
Definition author.hpp:1362
std::string const & name() const noexcept
The query's name.
Definition author.hpp:1343
Handle< Member > at(Member Class::*member) const
A field of the query's value.
Definition author.hpp:1356
Handle< std::string > errorKind() const
The kind of the query's error.
Definition author.hpp:1372
Handle< std::string > error() const
The query's error text.
Definition author.hpp:1366
QueryHandle(std::string name)
A query by name.
Definition author.hpp:1339
A query of a scope, without its action type: what refetch, prefetch and optimistic name.
Definition author.hpp:1298
std::string const & name() const noexcept
The query's name.
Definition author.hpp:1306
QueryRef(std::string name)
A query by name.
Definition author.hpp:1302
A mutation's result: a Handle with no field reads.
Definition author.hpp:1396
ResultHandle(Handle< T > const &handle)
The result read.
Definition author.hpp:1400
Handle< Member > at(Member Class::*member) const =delete
Not available: bind the result with let first, auto r = screen.let("r", m.result());,...
Handle< Any > at(std::string_view field) const =delete
Not available: bind the result with let first, auto r = screen.let("r", m.result());,...
The screen's own scope: its declarations, params, lifecycle keys, components and customization points...
Definition author.hpp:2494
Point choicePoint(std::string const &name, std::vector< std::string > const &options, std::string const &fallback={})
A choice point: one of a list of options.
Definition author.hpp:2625
void title(AnyHandle const &title)
The screen's title.
Definition author.hpp:2508
void onUnmount(Commands const &commands)
Commands run when the screen unmounts.
Definition author.hpp:2536
Point valuePoint(std::string const &name, Value const &fallback={})
A value point: one typed setting.
Definition author.hpp:2642
void onMount(Commands const &commands)
Commands run when the screen mounts.
Definition author.hpp:2532
Json document(std::string const &documentId, Node const &view) const
The document's JSON with a view.
Definition author.hpp:2664
Handle< T > param(std::string const &name)
A typed param, set by navigation.
Definition author.hpp:2501
void status(HeadBadgeOptions const &badge)
Appends a status badge to the head.
Definition author.hpp:2524
Point columnsPoint(std::string const &name, Element const &table)
A columns point: the user reorders, hides and resizes a table's columns.
Definition author.hpp:2601
void action(ActionButtonOptions const &action)
Appends an action to the head, run in the screen scope.
Definition author.hpp:2528
void identity(AnyHandle const &identity)
The screen's identity: two screens with equal identities are one.
Definition author.hpp:2512
void onBackendChange(Commands const &commands)
Commands run after a backend switch.
Definition author.hpp:2548
void key(std::string const &chord, Commands const &commands)
A screen-wide key binding.
Definition author.hpp:2553
void onSave(Commands const &commands)
Commands run when the host asks the screen to save.
Definition author.hpp:2544
ComponentRef component(std::string const &name, ComponentOptions const &options)
A custom or host component.
Definition author.hpp:2567
Point point(std::string const &name, std::string const &kind)
A layout or saved point.
Definition author.hpp:2654
void onClose(Commands const &commands)
Commands run when the screen is asked to close; cancelClose() keeps it open.
Definition author.hpp:2540
void subtitle(AnyHandle const &subtitle)
The head's line under the title.
Definition author.hpp:2520
void dirty(AnyHandle const &dirty)
Whether the screen has unsaved changes.
Definition author.hpp:2516
void showErrors(ShowErrors when)
When the screen's element errors show.
Definition author.hpp:2557
Point arrangePoint(std::string const &name, Element const &container)
An arrange point: the user reorders and hides the children of a column or row, each of which has an i...
Definition author.hpp:2613
void prefetch(QueryRef const &query)
A query started before the screen mounts; its body reads only params.
Definition author.hpp:2561
The screens of an application, each validated once when it is registered.
Definition author.hpp:4583
RegisteredScreen const * find(std::string_view screenId) const
A registered screen by id.
Definition author.hpp:4650
std::size_t lost() const noexcept
How many enrolments failed without a refusal to keep, because something threw outside the builder.
Definition author.hpp:4645
Catalog catalog() const
The catalog of every model and action the registered screens name, for a local-mode client.
Definition author.hpp:4669
std::vector< Refusal > const & refusals() const noexcept
The refusals enroll kept.
Definition author.hpp:4665
std::expected< RegisteredScreen const *, Refusal > add(Screen const &screen)
Registers a screen.
Definition author.hpp:4589
std::size_t size() const noexcept
How many screens are registered.
Definition author.hpp:4661
void enroll(Make const &make) noexcept
Registers a screen and keeps its refusal rather than returning it, for registration at static initial...
Definition author.hpp:4626
void enroll(Screen const &screen) noexcept
Registers a screen and keeps its refusal rather than returning it.
Definition author.hpp:4639
A screen definition: an id and the builder that writes the document.
Definition author.hpp:4509
Screen(std::string screenId, Builder builder)
A screen.
Definition author.hpp:4517
std::function< Node(Scope &)> Builder
Declares on the screen's scope and returns the view.
Definition author.hpp:4512
std::expected< Emitted, Refusal > write() const
Runs the builder and writes the document.
Definition author.hpp:4525
std::string const & id() const noexcept
The document id.
Definition author.hpp:4521
Builds a node's JSON key by key, leaving out what is absent.
Definition author.hpp:3021
NodeWriter & scope(DeclScope const &scope)
The declarations of the scope the node creates.
Definition author.hpp:3084
NodeWriter & raw(char const *key, Json value)
Any JSON.
Definition author.hpp:3076
NodeWriter & put(char const *key, Commands const &commands)
A command list.
Definition author.hpp:3042
NodeWriter & facts(CatalogFacts const &facts)
Adds catalog facts.
Definition author.hpp:3097
NodeWriter & text(char const *key, std::string const &value)
A plain string.
Definition author.hpp:3065
NodeWriter & put(char const *key, Nodes const &nodes)
A node list.
Definition author.hpp:3053
NodeWriter(std::string_view kind)
A node of a kind.
Definition author.hpp:3025
NodeWriter & put(char const *key, AnyHandle const &value)
An expression property.
Definition author.hpp:3031
Satisfied when Model runs Action: the model has an execute that takes it.
Definition author.hpp:94
Satisfied when two operands are operands and at least one is a handle, so the builders' operators nev...
Definition author.hpp:423
Satisfied by a C++ value a builder writes as a document constant: a scalar, a list of scalars,...
Definition author.hpp:361
Satisfied by an operand of an expression operator: a handle or a constant.
Definition author.hpp:418
Satisfied by a C++ scalar a builder writes as a document constant.
Definition author.hpp:353
UTC timestamps for morph actions.
The UI document (spec 5 §3): its model — declarations, commands, view nodes, components and customiza...
constexpr std::string_view kVocabulary
The vocabulary a document declares; the only one this client reads.
Definition document.hpp:37
std::function< std::optional< std::string >(std::string_view)> documentLookup(std::map< std::string, std::string, std::less<> > documents)
A Catalog::view or Catalog::wizard lookup over documents by action id.
Definition document.hpp:127
std::expected< Document, Refusal > loadDocumentText(std::string_view text, LoadOptions const &options={})
Parses JSON text as a UI document and runs its load-time validation.
std::shared_ptr< DocNode const > DocNodePtr
A shared, immutable view node.
Definition document.hpp:387
Resolved const & resolved()
The process registries, matched and validated on the first call.
Definition effects.hpp:511
std::optional< std::vector< Effect > > effectsOf(std::string_view actionId)
The effects an action declared.
Definition effects.hpp:539
@ Updates
updates<R>
Definition effects.hpp:91
@ Changes
changes<R>
Definition effects.hpp:92
@ ChangesInstance
changesInstance
Definition effects.hpp:93
std::vector< ActionRecord > recordsOf(std::string_view actionId)
The records an action's result holds, with the paths to each.
Definition effects.hpp:550
@ Literal
A JSON scalar, or {"lit": v}.
std::shared_ptr< Expr const > ExprPtr
A shared, immutable expression node.
morph::forms::FieldLayout: where a form's cell puts a field's caption. The forms engine and the autho...
@ Inline
The label beside the control, at a fixed width, centred on it; the help under the control.
Shared compile-time, NTTP-capable fixed string.
Exact rational arithmetic for morph model/action values.
The value glaze reads and writes under a reflected key.
constexpr decltype(auto) reflectedMember(T &object)
The value glaze reads and writes under glz::reflect<T>::keys[I].
What one successful execute changed.
std::vector< RecordChange > records
Record topics, one entry per record type.
Traits specialisation that maps an action type to its id, JSON codec, and result type.
Definition registry.hpp:48
Traits specialisation that maps a model type to its string type-id.
Definition registry.hpp:40
One effect an action declares, as the catalog states it.
Definition document.hpp:48
What the catalog says about an action.
Definition document.hpp:78
A record an action's result holds, as the catalog states it.
Definition document.hpp:62
The facts load-time validation needs from the application's catalog. Local mode answers them from the...
Definition document.hpp:105
std::function< std::optional< std::string >(std::string_view actionId)> wizard
The w-* wizard document whose first step is an action, as flows::wizardSchemaJson<W>() emits it: what...
Definition document.hpp:121
std::function< std::optional< ModelInfo >(std::string_view typeId)> model
Looks up a model type by its registered type id.
Definition document.hpp:107
std::function< std::optional< std::string >(std::string_view actionId)> view
The v-* view document whose v-query is an action, as views::viewSchemaJson<V>() emits it: what a coll...
Definition document.hpp:117
std::function< std::optional< ActionInfo >(std::string_view actionId)> action
Looks up an action by its id.
Definition document.hpp:109
An exact decimal: a math::Rational whose DecimalPlaces are the value's declared places.
Definition value.hpp:95
A modal overlay; its content is mounted only while it is open.
Definition view.hpp:694
A view node of the document (spec 5 §9), or a structured part of one (a table column,...
Definition document.hpp:394
std::map< std::string, std::vector< DocNodePtr >, std::less<> > children
Child node lists, by key (children, header, loading, error, default, cell, fallback).
Definition document.hpp:406
std::string path
The node's JSON Pointer.
Definition document.hpp:400
std::map< std::string, std::vector< DocNodePtr >, std::less<> > parts
Structured parts, by key (columns, tabs, cases, items, a11y, action, fields, …).
Definition document.hpp:408
std::string kind
The node kind (text, table, …), or for a part its part kind (column, tab, case, …).
Definition document.hpp:396
std::span< DocNodePtr const > partList(std::string_view key) const
A list of parts.
Definition document.hpp:447
ExprPtr prop(std::string_view key) const
An expression property.
Definition document.hpp:423
A loaded and validated UI document: a screen, or the app document (id app, spec 6 §5).
Definition document.hpp:536
std::string id
The document id, such as lims.results.
Definition document.hpp:540
std::map< std::string, Component, std::less<> > components
Custom components, by name.
Definition document.hpp:566
DocNodePtr view
The view; nullptr for the app document.
Definition document.hpp:570
A panel that slides in from one edge over the content: a Dialog with a side, and the same rules.
Definition view.hpp:1072
Options of loadDocument.
Definition document.hpp:140
A finding of load-time validation that does not refuse the document.
Definition document.hpp:514
std::string message(std::string_view document) const
The warning as one line of text.
Definition document.hpp:525
What the catalog says about a model type.
Definition document.hpp:40
Why a document, an expression or a value was refused: where, and the reason.
Definition value.hpp:38
A button of a screen's head (screen.action), or of a dialog's or drawer's footer.
Definition author.hpp:2413
std::string id
The action's id, unique in its list: a name.
Definition author.hpp:2415
AnyHandle enabled
Whether it can be run; absent enables it.
Definition author.hpp:2423
AnyHandle icon
The name of an icon.
Definition author.hpp:2419
AnyHandle label
The label; required.
Definition author.hpp:2417
ButtonEmphasis emphasis
How much it stands out; Normal writes no key.
Definition author.hpp:2421
Commands onClick
Commands run when it is clicked, in the scope its list belongs to.
Definition author.hpp:2427
AnyHandle visible
Whether it is shown; absent shows it.
Definition author.hpp:2425
Catalog facts of an action that its C++ type does not show. Specialise it for an action whose reply r...
Definition author.hpp:79
static std::vector< std::string > secretFields()
The reply fields the server marks secret.
Definition author.hpp:89
static std::vector< std::string > calculatedFields()
The reply-row fields the server calculates; an optimistic write may not name them.
Definition author.hpp:85
static constexpr bool credential
Whether the action sends a credential.
Definition author.hpp:81
The action of a banner or emptyState.
Definition author.hpp:3961
AnyHandle label
The label.
Definition author.hpp:3963
Commands onClick
Commands run when it is chosen.
Definition author.hpp:3965
The value type of a handle whose value the screen cannot know when it is written: a query result read...
Definition author.hpp:57
AnyHandle dismissible
Whether the user may dismiss it.
Definition author.hpp:3996
std::optional< ActionPart > action
Its action.
Definition author.hpp:3998
Commands onDismiss
Commands run when it is dismissed.
Definition author.hpp:4000
The opener's binding of an alias, for a screen opened as a dialog ({"from": alias}...
Definition author.hpp:1553
std::string alias
The opener's alias.
Definition author.hpp:1555
Commands onActivate
Commands run on activation by keyboard.
Definition author.hpp:3145
std::optional< ButtonStyle > style
A push button or a list entry; absent writes no key, a push button.
Definition author.hpp:3147
Commands onClick
Commands run on a click.
Definition author.hpp:3143
AnyHandle label
The label: the button's text and its accessible name.
Definition author.hpp:3137
AnyHandle icon
An icon of the theme's icon map; it never names the button.
Definition author.hpp:3139
AnyHandle selected
Whether a list entry is the current one, a condition; only with ButtonStyle::Item,...
Definition author.hpp:3150
ButtonEmphasis emphasis
How much it stands out; Normal writes no key.
Definition author.hpp:3141
The catalog facts the builders gathered from the model and action types a screen names.
Definition author.hpp:180
Catalog catalog() const
The facts as a ui::Catalog.
Definition author.hpp:237
std::map< std::string, ActionInfo, std::less<> > actions
Action facts by action id.
Definition author.hpp:184
std::map< std::string, std::string, std::less<> > wizards
The w-* wizard documents wizard nodes build from, by the action of their first step.
Definition author.hpp:188
void model()
Records a model type.
Definition author.hpp:206
std::map< std::string, std::string, std::less<> > views
The v-* view documents collection nodes build from, by the action of their v-query.
Definition author.hpp:186
std::map< std::string, ModelInfo, std::less<> > models
Model facts by registered type id.
Definition author.hpp:182
void merge(CatalogFacts const &other)
Adds another set of facts; a fact already present is kept.
Definition author.hpp:192
void action()
Records an action as one of a model type's.
Definition author.hpp:220
A validation across elements (spec 5 §6).
Definition author.hpp:1724
AnyHandle message
The message added to each target's errors.
Definition author.hpp:1730
std::string name
The check's id.
Definition author.hpp:1726
std::vector< Element > targets
The elements the message attaches to.
Definition author.hpp:1732
bool blocking
Whether a failure blocks ready.
Definition author.hpp:1734
Handle< bool > assertion
The condition; false fails the check.
Definition author.hpp:1728
Commands onChange
Commands run on a change.
Definition author.hpp:3236
Commands onToggle
Commands run on a toggle.
Definition author.hpp:3234
AnyHandle checked
Whether it is checked.
Definition author.hpp:3232
Nodes header
Nodes shown beside the title.
Definition author.hpp:4088
Commands onToggle
Commands run on a toggle.
Definition author.hpp:4090
AnyHandle open
Whether it is open.
Definition author.hpp:4086
A custom or host component (spec 5 §10).
Definition author.hpp:2347
std::string host
The name of a host component the client carries; empty for a delivered one.
Definition author.hpp:2351
std::vector< std::pair< std::string, PropType > > props
The props and their types.
Definition author.hpp:2355
std::vector< std::pair< std::string, std::string > > events
The events and their payload types.
Definition author.hpp:2357
std::optional< Node > fallback
The built-in subtree a renderer shows when it cannot load the component; it reads the props through p...
Definition author.hpp:2360
std::string qml
The content hash of a delivered component, sha256:<64 hex digits>; empty for a host component.
Definition author.hpp:2349
std::int64_t version
The host component's version.
Definition author.hpp:2353
Options of column, row and grid.
Definition author.hpp:3357
std::optional< Align > align
Where the children sit across the axis; absent stretches them.
Definition author.hpp:3363
AnyHandle gap
The gap between children: a spacing step, 0 to kMaxSpacingStep.
Definition author.hpp:3359
AnyHandle columns
A grid's columns; adaptive layouts read env("widthClass").
Definition author.hpp:3361
std::optional< Justify > justify
A row's or column's spare length along its axis; absent packs at the start. Not on a grid,...
Definition author.hpp:3366
std::vector< std::pair< std::string, Commands > > on
The component's events and the commands they run.
Definition author.hpp:4338
std::vector< std::pair< std::string, AnyHandle > > props
The props, by name.
Definition author.hpp:4336
Options of dateTimeInput.
Definition author.hpp:3874
AnyHandle label
The label: the accessible name.
Definition author.hpp:3878
AnyHandle mode
The input mode, such as date or time.
Definition author.hpp:3880
Commands onCommit
Commands run on a commit.
Definition author.hpp:3884
Commands onChange
Commands run on every edit.
Definition author.hpp:3882
Options of dialog and drawer.
Definition author.hpp:3551
std::optional< ShowErrors > showErrors
When its element errors show.
Definition author.hpp:3557
AnyHandle title
The title: the accessible name.
Definition author.hpp:3553
AnyHandle side
A drawer's side, start or end.
Definition author.hpp:3559
AnyHandle open
Whether it is open; absent for open and close commands alone.
Definition author.hpp:3555
Commands defaultAction
Commands run on Enter.
Definition author.hpp:3561
Commands onDismiss
Commands run when the user dismisses it.
Definition author.hpp:3565
std::vector< ActionButtonOptions > actions
The buttons of its footer, run in its scope; each id once.
Definition author.hpp:3567
Commands cancel
Commands run on Escape.
Definition author.hpp:3563
AnyHandle multiple
Whether several files may be dropped.
Definition author.hpp:4112
Commands onDrop
Commands run with the dropped files as event.
Definition author.hpp:4114
AnyHandle accept
The accepted types.
Definition author.hpp:4110
A screen's emitted document: its JSON, the text that is served, and the catalog facts of its types.
Definition author.hpp:4499
std::string text
The document as compact JSON text.
Definition author.hpp:4503
Json json
The document.
Definition author.hpp:4501
CatalogFacts facts
The catalog facts of the models and actions it names.
Definition author.hpp:4505
std::optional< ActionPart > action
Its action.
Definition author.hpp:4054
An overlay on one field of a form (spec 6 §10), applied to the parsed form model.
Definition author.hpp:4147
AnyHandle label
The label.
Definition author.hpp:4153
AnyHandle unit
The display unit.
Definition author.hpp:4155
AnyHandle omit
Leaves the field out.
Definition author.hpp:4149
AnyHandle hidden
Hides the field.
Definition author.hpp:4151
AnyHandle widget
The widget.
Definition author.hpp:4159
AnyHandle decimals
The display places.
Definition author.hpp:4157
AnyHandle blankAs
What an empty input is sent as.
Definition author.hpp:4161
AnyHandle multiple
Whether several files may be picked.
Definition author.hpp:3940
AnyHandle mode
open or save.
Definition author.hpp:3942
AnyHandle label
The label: the accessible name.
Definition author.hpp:3936
AnyHandle accept
The accepted types.
Definition author.hpp:3938
Commands onPick
Commands run with the picked files as event.
Definition author.hpp:3944
AnyHandle key
The row key, read in the row scope.
Definition author.hpp:3644
AnyHandle gap
The gap between rows: a spacing step, 0 to kMaxSpacingStep.
Definition author.hpp:3648
Commands onError
Commands run after a failed submit.
Definition author.hpp:4257
std::string submit
How it submits, such as explicit.
Definition author.hpp:4247
AnyHandle key
The identity of the record prefill holds: a new value of the same key keeps the fields the user chang...
Definition author.hpp:4245
AnyHandle prefill
The values the form starts from.
Definition author.hpp:4242
FormPresentation presentation
The title, button label, status line and columns.
Definition author.hpp:4259
Commands onSubmit
Commands run on submit.
Definition author.hpp:4253
std::vector< FieldOverlay< Action > > fields
The fields overlay.
Definition author.hpp:4251
std::optional< ShowErrors > showErrors
When its errors show.
Definition author.hpp:4249
Commands onSuccess
Commands run after a successful submit.
Definition author.hpp:4255
How a form, collection or wizard presents its forms: the keys every one of them takes.
Definition author.hpp:4202
std::optional< int > columns
The columns of every group's grid, 1 to 12; absent keeps the engine's.
Definition author.hpp:4211
std::optional< bool > showStatus
Whether the status line shows; absent shows it.
Definition author.hpp:4209
AnyHandle submitLabel
The explicit-mode button's label, which the status line names; absent is Submit.
Definition author.hpp:4207
AnyHandle title
A heading over the fields; on a collection or a wizard, the view's heading in place of the schema's....
Definition author.hpp:4205
std::optional<::morph::forms::FieldLayout > fieldLayout
Where every cell puts its caption; absent stacks it over the control.
Definition author.hpp:4213
A status badge of the screen's head (screen.status).
Definition author.hpp:2401
AnyHandle text
The badge's text.
Definition author.hpp:2403
AnyHandle icon
The name of an icon.
Definition author.hpp:2407
AnyHandle tone
Its tone: neutral, info, ok, warn or err.
Definition author.hpp:2405
AnyHandle visible
Whether it is shown; absent shows it.
Definition author.hpp:2409
An item of a menu; items nests a submenu.
Definition author.hpp:3291
std::vector< MenuItem > items
A submenu.
Definition author.hpp:3305
std::string keys
The shortcut shown beside it.
Definition author.hpp:3297
AnyHandle enabled
Whether it is enabled.
Definition author.hpp:3301
AnyHandle label
The label.
Definition author.hpp:3293
AnyHandle icon
An icon.
Definition author.hpp:3295
AnyHandle checked
Whether a checkable item is checked.
Definition author.hpp:3299
Commands onClick
Commands run when it is chosen.
Definition author.hpp:3303
Catalog facts of a model type that its C++ type does not show. Specialise it for a model whose shared...
Definition author.hpp:70
static constexpr bool stateless
Whether a shared singleton is equivalent to a private instance.
Definition author.hpp:72
Options of a model alias.
Definition author.hpp:1606
InstanceSpec instance
How the alias binds its instance.
Definition author.hpp:1608
Options of a mutation (spec 5 §5).
Definition author.hpp:1700
std::optional< Optimistic > optimistic
The row a run predicts before its reply.
Definition author.hpp:1708
Commands onSuccess
Commands run after a success.
Definition author.hpp:1710
bool offlineQueue
Whether a run that cannot reach the server is queued offline.
Definition author.hpp:1714
Commands onError
Commands run after a failure.
Definition author.hpp:1712
AnyHandle serialKey
The queue key of serial runs.
Definition author.hpp:1706
Body< Action > body
The body; empty sends the body each run gives.
Definition author.hpp:1702
Mode concurrency
What a run does while another is in flight.
Definition author.hpp:1704
OpenAs as
How the screen opens.
Definition author.hpp:2813
Commands onResult
Commands run with the value a dialog returns, as event.
Definition author.hpp:2815
Commands onCancel
Commands run when a dialog is cancelled.
Definition author.hpp:2817
AnyHandle params
The screen's params, a record.
Definition author.hpp:2811
An optimistic write: the row a mutation run predicts before its reply.
Definition author.hpp:1632
AnyHandle key
The row key, read against the run's body bound as body.
Definition author.hpp:1638
AnyHandle with
The predicted row, read against the run's body bound as body.
Definition author.hpp:1640
std::string at
The member of the query's record value that holds the list; empty when the value is the list itself.
Definition author.hpp:1636
std::optional< QueryRef > query
The query whose cached list it writes.
Definition author.hpp:1634
Commands onToggle
Commands run on a collapse or expand.
Definition author.hpp:3439
Nodes header
Nodes shown beside the title.
Definition author.hpp:3431
AnyHandle collapsible
Whether the user may collapse it.
Definition author.hpp:3435
AnyHandle padding
The padding.
Definition author.hpp:3433
AnyHandle title
The title.
Definition author.hpp:3429
AnyHandle collapsed
Whether it is collapsed.
Definition author.hpp:3437
Options of a query (spec 5 §5).
Definition author.hpp:1612
AnyHandle when
The gate; absent is always on.
Definition author.hpp:1614
std::chrono::milliseconds debounce
How long the body must be stable before a fetch.
Definition author.hpp:1616
std::chrono::milliseconds refreshEvery
The polling interval; zero does not poll.
Definition author.hpp:1618
std::chrono::milliseconds staleTime
How long a cached value counts as fresh.
Definition author.hpp:1620
A registered screen: the text that is served and the document it loads as.
Definition author.hpp:4569
std::string id
The document id.
Definition author.hpp:4571
std::string text
The document as compact JSON text.
Definition author.hpp:4573
Document document
The loaded document.
Definition author.hpp:4575
std::string as
With each, the element's name inside the body; without it, the name the reply is bound to for the lat...
Definition author.hpp:2745
AnyHandle each
A list to run the mutation once per element of, as one batch.
Definition author.hpp:2742
Commands onChoose
Commands run with the chosen key.
Definition author.hpp:3270
std::optional< OnStale > onStale
What happens to a selection the options no longer hold.
Definition author.hpp:3268
AnyHandle selected
The selected key.
Definition author.hpp:3262
Commands onChange
Commands run on a change.
Definition author.hpp:3272
AnyHandle label
The label: the accessible name.
Definition author.hpp:3264
AnyHandle options
The options: a list of {key, label} records.
Definition author.hpp:3260
AnyHandle style
The presentation, such as radio.
Definition author.hpp:3266
The dialog or drawer a command list runs in, for close.
Definition author.hpp:1292
AnyHandle min
The minimum.
Definition author.hpp:3907
Commands onCommit
Commands run on a commit.
Definition author.hpp:3915
AnyHandle value
The value.
Definition author.hpp:3903
AnyHandle label
The label: the accessible name.
Definition author.hpp:3905
Commands onChange
Commands run on a change.
Definition author.hpp:3913
AnyHandle max
The maximum.
Definition author.hpp:3909
A page of a tabs node.
Definition author.hpp:3526
Nodes children
The page's nodes.
Definition author.hpp:3530
AnyHandle label
The label.
Definition author.hpp:3528
A column of a table.
Definition author.hpp:3670
AnyHandle label
The header label.
Definition author.hpp:3674
AnyHandle role
The column's text role; absent writes no key, Normal.
Definition author.hpp:3678
std::string name
The column's id, which a columns customization point names.
Definition author.hpp:3672
Nodes cell
The cell, read in the row scope.
Definition author.hpp:3680
AnyHandle width
The width.
Definition author.hpp:3676
Options of table and dataTable.
Definition author.hpp:3709
bool showHeader
Whether the header row is drawn. Several tables sharing one visual header set this false on every ins...
Definition author.hpp:3716
std::size_t virtualizeThreshold
Below this many rows, every row's widget stays realized regardless of scroll position; a backend that...
Definition author.hpp:3719
std::vector< TableColumn > columns
The columns; a model-backed table derives them from the row type when none are given.
Definition author.hpp:3713
AnyHandle rowTone
A row's tone, read in the row scope.
Definition author.hpp:3731
std::optional< Selection > selection
How the selection works.
Definition author.hpp:3723
std::optional< TableMode > mode
Where a model-backed table works; ignored by a table over rows.
Definition author.hpp:3725
Commands onActivate
Commands run when a row is activated, in its row scope.
Definition author.hpp:3735
std::size_t virtualizeOverscan
How many rows beyond the visible range, on each side, keep a realized widget.
Definition author.hpp:3721
AnyHandle filters
The filters.
Definition author.hpp:3729
AnyHandle key
The row key, read in the row scope.
Definition author.hpp:3711
AnyHandle highlightKey
The key a linked chart highlights.
Definition author.hpp:3733
Nodes empty
Shown instead of the table while its rows are an empty list, in the table's scope; empty shows the ta...
Definition author.hpp:3738
AnyHandle placeholder
Placeholder text.
Definition author.hpp:3191
Commands onSubmit
Commands run on Enter in a single-line input.
Definition author.hpp:3204
AnyHandle icon
An icon of the theme's icon map, drawn at the input's leading edge; empty shows none....
Definition author.hpp:3196
Commands onCommit
Commands run on a commit.
Definition author.hpp:3202
Commands onChange
Commands run on every edit.
Definition author.hpp:3200
AnyHandle mode
The input mode, such as multiline or password.
Definition author.hpp:3193
std::optional< Commit > commitOn
When the draft commits.
Definition author.hpp:3198
AnyHandle label
The label: the accessible name.
Definition author.hpp:3189
AnyHandle stale
Whether the shown value is being recalculated.
Definition author.hpp:3120
AnyHandle text
The text.
Definition author.hpp:3116
AnyHandle role
The typographic role, such as heading.
Definition author.hpp:3118
std::string transfer
The transfer id, whose progress, pending and error are element values.
Definition author.hpp:2967
std::string as
The name the reply is bound to for later steps.
Definition author.hpp:2965
Whether a type is a Handle.
Definition author.hpp:315
Whether a type is a std::optional.
Definition author.hpp:273
Whether a type is a std::vector.
Definition author.hpp:304
The value type a constant stands for.
Definition author.hpp:367
L type
The constant's own type.
Definition author.hpp:369
A type with one std::optional removed.
Definition author.hpp:283
The value type of an operand: a handle's value type, or the type its constant stands for.
Definition author.hpp:403
LiteralT< X > type
The constant's value type.
Definition author.hpp:405
HandlerResult< T >::type HandlerResultT
The action result a handler returning T produces.
The UI document's typed values (ui::Value), its types (ui::Type), their JSON form,...
glz::generic_u64 Json
The JSON tree the document layer reads and writes: glaze's generic value in its integer-preserving mo...
Definition value.hpp:35
Json valueToJson(Value const &value)
Writes a value in its JSON form: the inverse of valueFromJson, with a timestamp as ISO-8601 text.
Definition value.hpp:668
std::vector< Value > ValueList
A list value.
Definition value.hpp:126
std::map< std::string, Value, std::less<> > ValueRecord
A record value: field name to value, ordered by name.
Definition value.hpp:129
std::function< void()> Action
A widget event handler taking no arguments.
Definition view.hpp:33