@protobase/schema 0.15.5 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,8 @@ export declare const addressWords: {
15
15
  readonly filterLayout: "layout";
16
16
  /** Where a list's map display looks: `latitude,longitude,zoom`. */
17
17
  readonly map: "map";
18
+ /** The saved search a list was opened from, by its key. */
19
+ readonly savedSearch: "saved_search";
18
20
  /** The second path segment that opens a resource's create form instead of a record. */
19
21
  readonly create: "new";
20
22
  };
@@ -15,6 +15,8 @@ const addressWords = {
15
15
  filterLayout: "layout",
16
16
  /** Where a list's map display looks: `latitude,longitude,zoom`. */
17
17
  map: "map",
18
+ /** The saved search a list was opened from, by its key. */
19
+ savedSearch: "saved_search",
18
20
  /** The second path segment that opens a resource's create form instead of a record. */
19
21
  create: "new"
20
22
  };
@@ -22,7 +24,8 @@ const listWords = [
22
24
  "display",
23
25
  "filter",
24
26
  "orderBy",
25
- "filterLayout"
27
+ "filterLayout",
28
+ "savedSearch"
26
29
  ];
27
30
  const mapText = (view) => `${view.latitude.toFixed(5)},${view.longitude.toFixed(5)},${view.zoom.toFixed(2)}`;
28
31
  const inRange = (value, limit, low = -limit) => Number.isFinite(value) && value >= low && value <= limit;
@@ -101,6 +104,7 @@ const readAppPath = (pathAndQuery, pages) => {
101
104
  const orderBy = params.get(addressWords.orderBy) || void 0;
102
105
  const layout = filterLayout(params.get(addressWords.filterLayout));
103
106
  const map = readMap(params.get(addressWords.map));
107
+ const savedSearch = params.get(addressWords.savedSearch) || void 0;
104
108
  return {
105
109
  kind: "list",
106
110
  resource: first,
@@ -108,7 +112,8 @@ const readAppPath = (pathAndQuery, pages) => {
108
112
  ...filter && { filter },
109
113
  ...orderBy && { orderBy },
110
114
  ...layout && { filterLayout: layout },
111
- ...map && { map }
115
+ ...map && { map },
116
+ ...savedSearch && { savedSearch }
112
117
  };
113
118
  };
114
119
  //#endregion
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ import { BottomBarNames, bottomBarErrors } from "./bottom-bar-errors.js";
20
20
  import { layout } from "./layout.js";
21
21
  import { KeyType, keyTypes } from "./keys.js";
22
22
  import { addressWords, appPath, readAppPath } from "./app-address.js";
23
+ import { SignInAddress, SignInStep, readSignInAddress, signInParams, signInPath, signInRoute, signInSteps } from "./sign-in-address.js";
23
24
  import { FilterLimits, FilterParse, parseFilter } from "./filter/parse.js";
24
25
  import { CheckResult, checkFilter } from "./filter/check.js";
25
26
  import { EvaluateOptions, evaluateFilter } from "./filter/evaluate.js";
@@ -36,4 +37,4 @@ import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, acces
36
37
  import { restrictModel } from "./access/restrict.js";
37
38
  import "./access/index.js";
38
39
  import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
39
- export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type Constraint, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type Grant, type HeldCapability, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RecordOf, type Ref, type Refs, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SignInPromptModel, type SignInViewModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, resolveAccess, resource, restrictModel, rule, userMenu, view, where };
40
+ export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type Constraint, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type Grant, type HeldCapability, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RecordOf, type Ref, type Refs, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SignInAddress, type SignInPromptModel, type SignInStep, type SignInViewModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
package/dist/index.js CHANGED
@@ -18,8 +18,9 @@ import { bottomBarErrors } from "./bottom-bar-errors.js";
18
18
  import { layout } from "./layout.js";
19
19
  import { keyTypes } from "./keys.js";
20
20
  import { addressWords, appPath, readAppPath } from "./app-address.js";
21
+ import { readSignInAddress, signInParams, signInPath, signInRoute, signInSteps } from "./sign-in-address.js";
21
22
  import { rule } from "./access/rule.js";
22
23
  import { defineRoles } from "./access/roles.js";
23
24
  import { pickView } from "./access/pick-view.js";
24
25
  import { restrictModel } from "./access/restrict.js";
25
- export { accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, resolveAccess, resource, restrictModel, rule, userMenu, view, where };
26
+ export { accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
package/dist/model.d.ts CHANGED
@@ -176,6 +176,11 @@ export type AppAddress = {
176
176
  filterLayout?: FilterLayout;
177
177
  /** Where its map display looks. */
178
178
  map?: MapView;
179
+ /**
180
+ * The saved search the list was opened from, by its key in the text form the record API takes: while the list's
181
+ * filter differs from the search's, the list offers to update the search.
182
+ */
183
+ savedSearch?: string;
179
184
  } | {
180
185
  /** One record. */
181
186
  kind: 'record';
@@ -209,6 +214,8 @@ export type FieldViewModel = {
209
214
  help?: string;
210
215
  /** How the value is shown. */
211
216
  format?: FieldFormat;
217
+ /** A text that runs over several lines: forms edit it in a box that grows with it, and its line breaks show. */
218
+ multiline?: boolean;
212
219
  /** Text before a number wherever it is shown, such as `€`. */
213
220
  prefix?: string;
214
221
  /** Text after a number wherever it is shown, such as a unit: `m²` shows `120 m²`. */
@@ -292,6 +299,16 @@ export type ListDisplayModel = {
292
299
  longitude: string;
293
300
  /** The cards display whose card a chosen marker shows; the list's first cards display when omitted. */
294
301
  cards?: string;
302
+ /** The boolean field that is true when the record's position is approximate. */
303
+ approximate?: string;
304
+ /** The boolean field that is false when the record's position is approximate. */
305
+ exact?: string;
306
+ /** The number field holding the radius, in metres, of the circle around an approximate position. */
307
+ radius?: string;
308
+ /** The radius, in metres, of the circle around an approximate position whose record states no area or radius. */
309
+ defaultRadius?: number;
310
+ /** The field holding the GeoJSON geometry around an approximate position: a polygon, or a line such as a street. */
311
+ area?: string;
295
312
  } | {
296
313
  /** The rows drawn by a project component. */
297
314
  kind: 'custom';
@@ -390,13 +407,14 @@ export type SearchesOfModel = {
390
407
  resource: string;
391
408
  /** The text field holding the search's filter, as the list's address has it. */
392
409
  filter: string;
393
- /** The text field a new search is named in, from its filters; the view's title shows it. */
410
+ /** The text field holding the name the user gives a search, if any; without one, the search's filters are its title. */
394
411
  name?: string;
395
412
  /** Enum and boolean fields changed in place on each search, such as how often to hear of new records. */
396
413
  controls?: string[];
397
414
  /**
398
- * With it, each search shows only the records new to the user: those whose `field` (of `resource`) is after the
399
- * search's `seenAt` (a timestamp field of this resource), which opening all of its results sets to now.
415
+ * With it, each search shows its records newest first by `field` (of `resource`) and marks those new to the user:
416
+ * those whose `field` is after the search's `seenAt` (a timestamp field of this resource), which opening all of its
417
+ * results sets to now.
400
418
  */
401
419
  unseen?: {
402
420
  /** When the user last opened the search's results. */
@@ -404,7 +422,7 @@ export type SearchesOfModel = {
404
422
  /** The searched resource's timestamp that makes a record new, such as when it was published. */
405
423
  field: string;
406
424
  };
407
- /** How many records each search shows; default 12. */
425
+ /** How many records each search shows at most; default every one it finds, loaded as its row scrolls. */
408
426
  limit?: number;
409
427
  };
410
428
  /** A view's list page (`.list(...)`). */
@@ -0,0 +1,33 @@
1
+ //#region src/sign-in-address.d.ts
2
+ /** The path of the sign-in route, below the app's base path. Its steps and the page to come back to are in its query. */
3
+ export declare const signInRoute = "/-/sign-in";
4
+ /** The names of the sign-in route's query parameters: the step it shows, and the page to come back to after it. */
5
+ export declare const signInParams: {
6
+ /** The step the route shows, when it is not the first. */
7
+ readonly step: "step";
8
+ /** The page below the base path to come back to after signing in. */
9
+ readonly next: "next";
10
+ };
11
+ /**
12
+ * A step of signing in or making an account, each with an address of its own: Back from a step returns to the one
13
+ * before. `email-code` and `sign-up-code` enter the code emailed to an address the step before asked for, and
14
+ * `sign-up-details` asks for the name and password once that code was right.
15
+ */
16
+ export declare const signInSteps: readonly ["sign-in", "forgot-password", "email-code", "sign-up", "sign-up-code", "sign-up-details"];
17
+ /** A step of the sign-in route: one of `signInSteps`. */
18
+ export type SignInStep = (typeof signInSteps)[number];
19
+ /** Where the sign-in route is: its step, and the page below the base path to come back to, when it names one. */
20
+ export type SignInAddress = {
21
+ /** The step the route shows. */
22
+ step: SignInStep;
23
+ /** The page below the base path to come back to, when it names one. */
24
+ next?: string;
25
+ };
26
+ /** The path below the base path of a step of the sign-in route, coming back to `next` after it, the start by default. */
27
+ export declare const signInPath: ({ step, next }?: {
28
+ step?: SignInStep;
29
+ next?: string;
30
+ }) => string;
31
+ /** The sign-in route's step and the page to come back to, for the path below the base path; `undefined` elsewhere. */
32
+ export declare const readSignInAddress: (path: string, params: URLSearchParams) => SignInAddress | undefined;
33
+ //#endregion
@@ -0,0 +1,51 @@
1
+ //#region src/sign-in-address.ts
2
+ /** The path of the sign-in route, below the app's base path. Its steps and the page to come back to are in its query. */
3
+ const signInRoute = "/-/sign-in";
4
+ /** The names of the sign-in route's query parameters: the step it shows, and the page to come back to after it. */
5
+ const signInParams = {
6
+ /** The step the route shows, when it is not the first. */
7
+ step: "step",
8
+ /** The page below the base path to come back to after signing in. */
9
+ next: "next"
10
+ };
11
+ /**
12
+ * A step of signing in or making an account, each with an address of its own: Back from a step returns to the one
13
+ * before. `email-code` and `sign-up-code` enter the code emailed to an address the step before asked for, and
14
+ * `sign-up-details` asks for the name and password once that code was right.
15
+ */
16
+ const signInSteps = [
17
+ "sign-in",
18
+ "forgot-password",
19
+ "email-code",
20
+ "sign-up",
21
+ "sign-up-code",
22
+ "sign-up-details"
23
+ ];
24
+ const isStep = (value) => signInSteps.includes(value);
25
+ const localPage = (next) => {
26
+ if (!next?.startsWith("/")) return void 0;
27
+ const url = new URL(next, "http://local");
28
+ if (url.origin !== "http://local" || url.pathname === "/-/sign-in") return void 0;
29
+ return `${url.pathname}${url.search}`;
30
+ };
31
+ /** The path below the base path of a step of the sign-in route, coming back to `next` after it, the start by default. */
32
+ const signInPath = ({ step = "sign-in", next } = {}) => {
33
+ const params = new URLSearchParams();
34
+ if (step !== "sign-in") params.set(signInParams.step, step);
35
+ const page = localPage(next);
36
+ if (page && page !== "/") params.set(signInParams.next, page);
37
+ const query = params.toString();
38
+ return query ? `${signInRoute}?${query}` : signInRoute;
39
+ };
40
+ /** The sign-in route's step and the page to come back to, for the path below the base path; `undefined` elsewhere. */
41
+ const readSignInAddress = (path, params) => {
42
+ if (path !== "/-/sign-in") return void 0;
43
+ const step = params.get(signInParams.step);
44
+ const next = localPage(params.get(signInParams.next));
45
+ return {
46
+ step: isStep(step) ? step : "sign-in",
47
+ ...next && { next }
48
+ };
49
+ };
50
+ //#endregion
51
+ export { readSignInAddress, signInParams, signInPath, signInRoute, signInSteps };
@@ -53,13 +53,22 @@ export declare const displays: {
53
53
  * map style. It asks for the records in its view as it pans and zooms: from far out a count for each part of the map,
54
54
  * which zooms in when chosen, closer in the records. A marker stands for the records that lie close together at the
55
55
  * zoom; choosing it shows their card at the map's bottom, with arrows through them. The card is the one of the cards display named `cards`, the list's first cards display
56
- * when it names none, or the record's title alone without one. Named `map` unless `name` says otherwise.
56
+ * when it names none, or the record's title alone without one. A record's position is approximate when its boolean
57
+ * `approximate` field is true, or its `exact` field false: its marker is drawn hollow and says so, and close enough in
58
+ * an area is drawn around it, the GeoJSON polygon or line its `area` field holds, else a circle of its `radius` field
59
+ * in metres, else one of `defaultRadius`. Named `map` unless `name` says otherwise. See
60
+ * [Approximate positions](website/src/content/docs/reference/ui-config.mdx#approximate-positions).
57
61
  */
58
62
  map: (fields: {
59
63
  latitude: Ref;
60
64
  longitude: Ref;
65
+ approximate?: Ref;
66
+ exact?: Ref;
67
+ radius?: Ref;
68
+ area?: Ref;
61
69
  }, o?: Naming & {
62
70
  cards?: string;
71
+ defaultRadius?: number;
63
72
  }) => ListDisplayModel;
64
73
  /**
65
74
  * The list drawn by a component of the project, registered in `protobase.ui.tsx` under its name. It gets `props` and
@@ -72,6 +81,6 @@ export declare const displays: {
72
81
  props?: ViewProps<P>;
73
82
  }) => ListDisplayModel;
74
83
  };
75
- /** Every field a display names: what it shows, a map's position, and the field references in a custom display's props. */
84
+ /** Every field a display names: what it shows, a map's position and the fields saying how exact it is, and the field references in a custom display's props. */
76
85
  export declare const displayFields: (display: ListDisplayModel) => string[];
77
86
  //#endregion
@@ -48,7 +48,11 @@ const displays = {
48
48
  * map style. It asks for the records in its view as it pans and zooms: from far out a count for each part of the map,
49
49
  * which zooms in when chosen, closer in the records. A marker stands for the records that lie close together at the
50
50
  * zoom; choosing it shows their card at the map's bottom, with arrows through them. The card is the one of the cards display named `cards`, the list's first cards display
51
- * when it names none, or the record's title alone without one. Named `map` unless `name` says otherwise.
51
+ * when it names none, or the record's title alone without one. A record's position is approximate when its boolean
52
+ * `approximate` field is true, or its `exact` field false: its marker is drawn hollow and says so, and close enough in
53
+ * an area is drawn around it, the GeoJSON polygon or line its `area` field holds, else a circle of its `radius` field
54
+ * in metres, else one of `defaultRadius`. Named `map` unless `name` says otherwise. See
55
+ * [Approximate positions](website/src/content/docs/reference/ui-config.mdx#approximate-positions).
52
56
  */
53
57
  map: (fields, o = {}) => ({
54
58
  kind: "map",
@@ -56,7 +60,12 @@ const displays = {
56
60
  ...naming(o),
57
61
  latitude: fields.latitude.name,
58
62
  longitude: fields.longitude.name,
59
- ...o.cards !== void 0 && { cards: o.cards }
63
+ ...o.cards !== void 0 && { cards: o.cards },
64
+ ...fields.approximate && { approximate: fields.approximate.name },
65
+ ...fields.exact && { exact: fields.exact.name },
66
+ ...fields.radius && { radius: fields.radius.name },
67
+ ...o.defaultRadius !== void 0 && { defaultRadius: o.defaultRadius },
68
+ ...fields.area && { area: fields.area.name }
60
69
  }),
61
70
  /**
62
71
  * The list drawn by a component of the project, registered in `protobase.ui.tsx` under its name. It gets `props` and
@@ -81,12 +90,25 @@ const displayErrors = (list) => {
81
90
  ...names.filter((name) => name === tableDisplay).map(() => `"${tableDisplay}" is the list's own display; give the display another name`),
82
91
  ...names.filter((name, index) => names.indexOf(name) !== index).map((name) => `display "${name}" is defined twice`),
83
92
  ...(list.displays ?? []).flatMap((display) => display.kind === "map" && display.cards !== void 0 && !list.displays.some((other) => other.kind === "cards" && other.name === display.cards) ? [`display "${display.name}" names cards "${display.cards}", which is not a cards display of the list`] : []),
93
+ ...(list.displays ?? []).flatMap((display) => display.kind === "map" ? approximateErrors(display).map((error) => `display "${display.name}": ${error}`) : []),
84
94
  ...list.default === void 0 || list.default === "table" || names.includes(list.default) ? [] : [`default "${list.default}" is not one of the displays (${[tableDisplay, ...names].join(", ")})`]
85
95
  ];
86
96
  };
87
- /** Every field a display names: what it shows, a map's position, and the field references in a custom display's props. */
97
+ const approximateErrors = (display) => {
98
+ const { approximate, exact, radius, defaultRadius, area } = display;
99
+ if (approximate !== void 0 && exact !== void 0) return ["name approximate or exact, not both"];
100
+ return [...(radius !== void 0 || defaultRadius !== void 0 || area !== void 0) && approximate === void 0 && exact === void 0 ? ["an area around approximate positions needs the approximate or exact field"] : [], ...defaultRadius === void 0 || Number.isFinite(defaultRadius) && defaultRadius > 0 ? [] : [`defaultRadius ${defaultRadius} is not a number of metres above 0`]];
101
+ };
102
+ /** Every field a display names: what it shows, a map's position and the fields saying how exact it is, and the field references in a custom display's props. */
88
103
  const displayFields = (display) => {
89
- if (display.kind === "map") return [display.latitude, display.longitude];
104
+ if (display.kind === "map") return [
105
+ display.latitude,
106
+ display.longitude,
107
+ display.approximate,
108
+ display.exact,
109
+ display.radius,
110
+ display.area
111
+ ].filter((name) => name !== void 0);
90
112
  if (display.kind === "custom") return [...display.fields, ...fieldRefsIn(display.props)];
91
113
  return [
92
114
  display.image,
@@ -18,6 +18,11 @@ export declare class FieldView<N extends string = string, V = unknown> {
18
18
  help(help: string): FieldView<N, V>;
19
19
  /** How the value shows: `badge` for statuses, `code` in monospace, `percent` with a % sign. */
20
20
  format(format: FieldFormat): FieldView<N, V>;
21
+ /**
22
+ * A text of several lines, such as a description: forms edit it in a box that grows with the text, and the record
23
+ * page and cards keep its line breaks. Only a text field takes it.
24
+ */
25
+ multiline(): FieldView<N, V>;
21
26
  /** Text before a number wherever it is shown, such as a currency: `.prefix('€')`. */
22
27
  prefix(prefix: string): FieldView<N, V>;
23
28
  /** Text after a number wherever it is shown, such as a unit: `.suffix(' m²')`. */
@@ -21,6 +21,13 @@ var FieldView = class FieldView {
21
21
  format(format) {
22
22
  return this.with({ format });
23
23
  }
24
+ /**
25
+ * A text of several lines, such as a description: forms edit it in a box that grows with the text, and the record
26
+ * page and cards keep its line breaks. Only a text field takes it.
27
+ */
28
+ multiline() {
29
+ return this.with({ multiline: true });
30
+ }
24
31
  /** Text before a number wherever it is shown, such as a currency: `.prefix('€')`. */
25
32
  prefix(prefix) {
26
33
  return this.with({ prefix });
package/dist/view.d.ts CHANGED
@@ -122,8 +122,8 @@ export declare class ViewBuilder<R extends {
122
122
  saveSearch(search: SaveSearchModel): ViewBuilder<R>;
123
123
  /**
124
124
  * This resource holds saved searches of `resource`, which a list page saves with `.saveSearch(...)`: its list page
125
- * shows each of the user's searches with its filters, its `controls` to change in place, the newest records it finds
126
- * (with `unseen`, only those new since the user last opened its results) and a link to all of them. Throws in
125
+ * shows each of the user's searches with its filters, its `controls` to change in place, the records it finds (with
126
+ * `unseen`, newest first, those new since the user last opened its results marked) and a link to all of them. Throws in
127
127
  * `toModel` when `filter` or `name` is not a text field, `unseen.seenAt` not a timestamp or a control not an enum or
128
128
  * boolean field.
129
129
  * See [Saved searches](website/src/content/docs/reference/ui-config.mdx#saved-searches).
package/dist/view.js CHANGED
@@ -153,8 +153,8 @@ var ViewBuilder = class ViewBuilder {
153
153
  }
154
154
  /**
155
155
  * This resource holds saved searches of `resource`, which a list page saves with `.saveSearch(...)`: its list page
156
- * shows each of the user's searches with its filters, its `controls` to change in place, the newest records it finds
157
- * (with `unseen`, only those new since the user last opened its results) and a link to all of them. Throws in
156
+ * shows each of the user's searches with its filters, its `controls` to change in place, the records it finds (with
157
+ * `unseen`, newest first, those new since the user last opened its results marked) and a link to all of them. Throws in
158
158
  * `toModel` when `filter` or `name` is not a text field, `unseen.seenAt` not a timestamp or a control not an enum or
159
159
  * boolean field.
160
160
  * See [Saved searches](website/src/content/docs/reference/ui-config.mdx#saved-searches).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.15.5",
3
+ "version": "0.16.1",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {