@protobase/schema 0.19.12 → 0.19.14

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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FileOptions, FileProcessor, FileProcessorInput, ImageFormat, ImageVariant, ImageVariantOptions, imageFormats } from "./files/file-options.js";
2
- import { ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CreateModel, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, ExportModel, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, GalleryModel, HelpWhen, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, RecordMode, RelatedModel, ResourceModel, SaveFeedback, SaveSearchModel, SearchMatch, SearchResultModel, SearchesOfModel, SectionPresentation, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
2
+ import { ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CreateModel, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, ExportModel, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, GalleryModel, HelpWhen, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, RecordMode, RelatedColumnModel, RelatedModel, ResourceModel, SaveFeedback, SaveSearchModel, SearchMatch, SearchResultModel, SearchesOfModel, SectionPresentation, ShowWhen, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
3
3
  import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./access/types.js";
4
4
  import { AnyField, Constraint, Field, FieldMeta, FieldTypeDef } from "./field.js";
5
5
  import { RelationField } from "./relation-field.js";
@@ -9,6 +9,7 @@ import { FieldRefs, FieldsInput, Ref, Refs } from "./refs.js";
9
9
  import { CreatedOptions, DeletedOptions, EventActor, EventContext, EventHelpers, EventMatch, EventSpec, UndeletedOptions, UpdatedOptions } from "./events/event-helpers.js";
10
10
  import { Access, InferRecord, Issue, KeyOf, KeyRefs, RecordKey, RecordOf } from "./resource-types.js";
11
11
  import { ResourceBuilder, SearchRef, SearchRefs, Validator, resource } from "./resource.js";
12
+ import { ListColumn } from "./view-columns.js";
12
13
  import { ComponentRef, ViewProps } from "./view-displays.js";
13
14
  import { FieldView } from "./view-field.js";
14
15
  import { FieldViews, ViewBuilder, view } from "./view.js";
@@ -37,4 +38,4 @@ import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, acces
37
38
  import { restrictModel } from "./access/restrict.js";
38
39
  import "./access/index.js";
39
40
  import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
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 CreateModel, 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 ExportModel, 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 GalleryModel, type Grant, type HeldCapability, type HelpWhen, 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 RecordMode, 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 SectionPresentation, 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 };
41
+ 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 CreateModel, 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 ExportModel, 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 GalleryModel, type Grant, type HeldCapability, type HelpWhen, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListColumn, 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 RecordMode, type RecordOf, type Ref, type Refs, type RelatedColumnModel, 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 SectionPresentation, type ShowWhen, 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/layout.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CardPhotosModel, HelpWhen, LayoutItemModel, RelatedModel, SectionPresentation } from "./model.js";
1
+ import { CardPhotosModel, HelpWhen, LayoutItemModel, RelatedModel, SectionPresentation, ShowWhen } from "./model.js";
2
2
  import { Ref } from "./refs.js";
3
3
  import { ComponentRef, ViewProps } from "./view-displays.js";
4
4
  //#region src/layout.d.ts
@@ -14,6 +14,10 @@ import { ComponentRef, ViewProps } from "./view-displays.js";
14
14
  * l.related('Parts used', { resource: 'repair_parts', field: 'repair_id' }),
15
15
  * ])
16
16
  * ```
17
+ *
18
+ * Every item shows on both the form and the read view unless its `when` says `editing` (the form and the create form)
19
+ * or `reading` (the read view and the locked form). See
20
+ * [Form and read view](website/src/content/docs/reference/ui-config.mdx#form-and-read-view).
17
21
  */
18
22
  export declare const layout: {
19
23
  /**
@@ -27,6 +31,7 @@ export declare const layout: {
27
31
  help?: string;
28
32
  helpWhen?: HelpWhen;
29
33
  presentation?: SectionPresentation;
34
+ when?: ShowWhen;
30
35
  }) => LayoutItemModel;
31
36
  /**
32
37
  * The record's photos as tiles in the main column, one large and the next four smaller, the last saying how many more
@@ -39,9 +44,12 @@ export declare const layout: {
39
44
  gallery: (title: string, o: {
40
45
  image?: Ref;
41
46
  photos?: CardPhotosModel;
47
+ when?: ShowWhen;
42
48
  }) => LayoutItemModel;
43
49
  /** A read-only summary card of fields beside the main column. */
44
- sidebar: (fields: Ref[]) => LayoutItemModel;
50
+ sidebar: (fields: Ref[], o?: {
51
+ when?: ShowWhen;
52
+ }) => LayoutItemModel;
45
53
  /** A table of the records of another resource that point at this one, directly or through a join table. */
46
54
  related: (title: string, o: Omit<RelatedModel, "kind" | "title">) => LayoutItemModel;
47
55
  /**
@@ -54,6 +62,7 @@ export declare const layout: {
54
62
  longitude: Ref;
55
63
  zoom?: number;
56
64
  sidebar?: boolean;
65
+ when?: ShowWhen;
57
66
  }) => LayoutItemModel;
58
67
  /**
59
68
  * A component of the project, registered in `protobase.ui.tsx` under its name, drawn where it is listed: in the main
@@ -64,6 +73,7 @@ export declare const layout: {
64
73
  title?: string;
65
74
  props?: ViewProps<P>;
66
75
  sidebar?: boolean;
76
+ when?: ShowWhen;
67
77
  }) => LayoutItemModel;
68
78
  };
69
79
  //#endregion
package/dist/layout.js CHANGED
@@ -13,6 +13,10 @@ import { customComponent } from "./view-displays.js";
13
13
  * l.related('Parts used', { resource: 'repair_parts', field: 'repair_id' }),
14
14
  * ])
15
15
  * ```
16
+ *
17
+ * Every item shows on both the form and the read view unless its `when` says `editing` (the form and the create form)
18
+ * or `reading` (the read view and the locked form). See
19
+ * [Form and read view](website/src/content/docs/reference/ui-config.mdx#form-and-read-view).
16
20
  */
17
21
  const layout = {
18
22
  /**
@@ -28,7 +32,8 @@ const layout = {
28
32
  fields: fields.map((ref) => ref.name),
29
33
  ...o.help !== void 0 && { help: o.help },
30
34
  ...o.helpWhen !== void 0 && { helpWhen: o.helpWhen },
31
- ...o.presentation !== void 0 && { presentation: o.presentation }
35
+ ...o.presentation !== void 0 && { presentation: o.presentation },
36
+ ...o.when !== void 0 && { when: o.when }
32
37
  }),
33
38
  /**
34
39
  * The record's photos as tiles in the main column, one large and the next four smaller, the last saying how many more
@@ -44,13 +49,15 @@ const layout = {
44
49
  kind: "gallery",
45
50
  title,
46
51
  ...o.image && { image: o.image.name },
47
- ...o.photos && { photos: { ...o.photos } }
52
+ ...o.photos && { photos: { ...o.photos } },
53
+ ...o.when !== void 0 && { when: o.when }
48
54
  };
49
55
  },
50
56
  /** A read-only summary card of fields beside the main column. */
51
- sidebar: (fields) => ({
57
+ sidebar: (fields, o = {}) => ({
52
58
  kind: "sidebar",
53
- fields: fields.map((ref) => ref.name)
59
+ fields: fields.map((ref) => ref.name),
60
+ ...o.when !== void 0 && { when: o.when }
54
61
  }),
55
62
  /** A table of the records of another resource that point at this one, directly or through a join table. */
56
63
  related: (title, o) => ({
@@ -69,7 +76,8 @@ const layout = {
69
76
  latitude: o.latitude.name,
70
77
  longitude: o.longitude.name,
71
78
  ...o.zoom !== void 0 && { zoom: o.zoom },
72
- ...o.sidebar && { sidebar: true }
79
+ ...o.sidebar && { sidebar: true },
80
+ ...o.when !== void 0 && { when: o.when }
73
81
  }),
74
82
  /**
75
83
  * A component of the project, registered in `protobase.ui.tsx` under its name, drawn where it is listed: in the main
@@ -81,7 +89,8 @@ const layout = {
81
89
  component: customComponent(component, "l.custom"),
82
90
  ...o.title !== void 0 && { title: o.title },
83
91
  ...o.props && { props: markFieldRefs(o.props) },
84
- ...o.sidebar && { sidebar: true }
92
+ ...o.sidebar && { sidebar: true },
93
+ ...o.when !== void 0 && { when: o.when }
85
94
  })
86
95
  };
87
96
  //#endregion
package/dist/model.d.ts CHANGED
@@ -219,6 +219,8 @@ export type FieldViewModel = {
219
219
  help?: string;
220
220
  /** When `help` shows: always, or only while the record is edited; the view's `helpWhen` when omitted. */
221
221
  helpWhen?: HelpWhen;
222
+ /** When the field shows on the record page: always, the default, only on its form or only on its read view. */
223
+ when?: ShowWhen;
222
224
  /** How the value is shown. */
223
225
  format?: FieldFormat;
224
226
  /** A text that runs over several lines: forms edit it in a box that grows with it, and its line breaks show. */
@@ -254,6 +256,12 @@ export type FieldViewModel = {
254
256
  * [The record page](website/src/content/docs/reference/ui-config.mdx#the-record-page).
255
257
  */
256
258
  export type HelpWhen = 'always' | 'editing';
259
+ /**
260
+ * When a record page's field or layout item shows: `always`, the default; only while the record is `editing`, on its
261
+ * form and the create form; or only while `reading` it, on the read view and the locked form. See
262
+ * [Form and read view](website/src/content/docs/reference/ui-config.mdx#form-and-read-view).
263
+ */
264
+ export type ShowWhen = 'always' | 'editing' | 'reading';
257
265
  /** How a field's badges are drawn: `arrow` points them right, as on a rating scale. */
258
266
  export type BadgeShape = 'arrow';
259
267
  /** A field type by the name a plugin registers it under, and the plain data it is given. */
@@ -455,6 +463,31 @@ export type ListModel = {
455
463
  displays?: ListDisplayModel[];
456
464
  /** The display the list opens in when the address names none; the table when omitted. */
457
465
  default?: string;
466
+ /** The columns of related records among `columns`, each named there by its `name`. */
467
+ relatedColumns?: RelatedColumnModel[];
468
+ };
469
+ /**
470
+ * A list column of the records of another resource that point at each row, directly or through a join resource, as a
471
+ * dot per record in its status's colour, each opening the record (`c.related(...)`). See
472
+ * [Related records in a list](website/src/content/docs/reference/ui-config.mdx#related-records-in-a-list).
473
+ */
474
+ export type RelatedColumnModel = {
475
+ /** The column's name in the list's `columns`, which no field of the resource has. */
476
+ name: string;
477
+ /** The column's header; the name in words when omitted. */
478
+ label?: string;
479
+ /** The resource whose records it shows. */
480
+ resource: string;
481
+ /** The filterable field of `resource` matched against the row's key, or with `through` against the join records' keys. */
482
+ field: string;
483
+ /** The join records between the row and the shown ones, as a related section's. */
484
+ through?: RelatedModel['through'];
485
+ /** An AIP-160 filter on the shown records. */
486
+ filter?: string;
487
+ /** An AIP-132 order of the dots. */
488
+ sort?: string;
489
+ /** The enum field of `resource` whose value colours each dot, by its view's `valueColors`. */
490
+ status: string;
458
491
  };
459
492
  /** A button of a date range filter: today, yesterday, the last 7, 30 or 90 days, or this month, quarter or year. */
460
493
  export type DatePreset = 'today' | 'yesterday' | '7d' | '30d' | '90d' | 'month' | 'quarter' | 'year';
@@ -535,6 +568,8 @@ export type RelatedModel = {
535
568
  display?: string;
536
569
  /** The text when nothing matches. */
537
570
  empty?: string;
571
+ /** Whether it shows on the form, the read view or both. */
572
+ when?: ShowWhen;
538
573
  };
539
574
  /**
540
575
  * A project component drawn on the record page where it is listed, in the main column or with `sidebar` in the
@@ -551,6 +586,8 @@ export type CustomSectionModel = {
551
586
  props?: Record<string, JsonValue>;
552
587
  /** Drawn as a sidebar card instead of in the main column. */
553
588
  sidebar?: true;
589
+ /** Whether it shows on the form, the read view or both. */
590
+ when?: ShowWhen;
554
591
  };
555
592
  /**
556
593
  * A map of the record's position, its `latitude` and `longitude` fields in decimal degrees (WGS84), centred on a marker
@@ -570,6 +607,8 @@ export type MapSectionModel = {
570
607
  zoom?: number;
571
608
  /** Drawn as a sidebar card instead of in the main column. */
572
609
  sidebar?: true;
610
+ /** Whether it shows on the form, the read view or both. */
611
+ when?: ShowWhen;
573
612
  };
574
613
  /** How a section shows its fields on the read view: `facts` as a row of key facts; label/value rows when omitted. */
575
614
  export type SectionPresentation = 'facts';
@@ -590,11 +629,15 @@ export type LayoutItemModel = {
590
629
  * label/value rows; the form edits them as any section's.
591
630
  */
592
631
  presentation?: SectionPresentation;
632
+ /** Whether it shows on the form, the read view or both. */
633
+ when?: ShowWhen;
593
634
  } | {
594
635
  /** A read-only summary card beside the main column. */
595
636
  kind: 'sidebar';
596
637
  /** The fields it shows, in order. */
597
638
  fields: string[];
639
+ /** Whether it shows on the form, the read view or both. */
640
+ when?: ShowWhen;
598
641
  } | RelatedModel | CustomSectionModel | MapSectionModel | GalleryModel;
599
642
  /**
600
643
  * A record's photos as tiles in the main column under `title`, one large and the next few smaller, opening a lightbox
@@ -611,6 +654,8 @@ export type GalleryModel = {
611
654
  image?: string;
612
655
  /** The records holding the further photos. */
613
656
  photos?: CardPhotosModel;
657
+ /** Whether it shows on the form, the read view or both. */
658
+ when?: ShowWhen;
614
659
  };
615
660
  /** How far back a list's chart reaches. */
616
661
  export type ChartRange = '7d' | '30d' | '90d' | '1y' | 'all';
@@ -0,0 +1,18 @@
1
+ import { RelatedColumnModel } from "./model.js";
2
+ //#region src/view-columns.d.ts
3
+ /** A column of a view's list that is no field of the resource, as `c` makes one. */
4
+ export type ListColumn = RelatedColumnModel & {
5
+ /** Marks a related records column. */
6
+ readonly kind: 'related';
7
+ };
8
+ export declare const columns: {
9
+ /**
10
+ * A column named `name` of the records of `resource` that point at each row, through its relation `field` or, with
11
+ * `through`, through a join resource, as a related section's. Each record is a dot in the colour its `status` value
12
+ * has in its view's `valueColors`, in `sort` order, and a link to the record, showing its card on hover or focus. The
13
+ * name must not be a field of the resource. See
14
+ * [Related records in a list](website/src/content/docs/reference/ui-config.mdx#related-records-in-a-list).
15
+ */
16
+ related: (name: string, o: Omit<RelatedColumnModel, "name">) => ListColumn;
17
+ };
18
+ //#endregion
@@ -0,0 +1,17 @@
1
+ //#region src/view-columns.ts
2
+ const isListColumn = (value) => typeof value === "object" && value !== null && value.kind === "related";
3
+ const columns = {
4
+ /**
5
+ * A column named `name` of the records of `resource` that point at each row, through its relation `field` or, with
6
+ * `through`, through a join resource, as a related section's. Each record is a dot in the colour its `status` value
7
+ * has in its view's `valueColors`, in `sort` order, and a link to the record, showing its card on hover or focus. The
8
+ * name must not be a field of the resource. See
9
+ * [Related records in a list](website/src/content/docs/reference/ui-config.mdx#related-records-in-a-list).
10
+ */
11
+ related: (name, o) => ({
12
+ kind: "related",
13
+ name,
14
+ ...structuredClone(o)
15
+ }) };
16
+ //#endregion
17
+ export { columns, isListColumn };
@@ -1,4 +1,4 @@
1
- import { BadgeShape, FieldFormat, FieldViewModel, HelpWhen, JsonValue } from "./model.js";
1
+ import { BadgeShape, FieldFormat, FieldViewModel, HelpWhen, JsonValue, ShowWhen } from "./model.js";
2
2
  //#region src/view-field.d.ts
3
3
  /** How a view shows one field, as `.fields((r) => ...)` hands it out: each hint returns a new field view. */
4
4
  export declare class FieldView<N extends string = string, V = unknown> {
@@ -21,6 +21,11 @@ export declare class FieldView<N extends string = string, V = unknown> {
21
21
  help(help: string, o?: {
22
22
  when?: HelpWhen;
23
23
  }): FieldView<N, V>;
24
+ /**
25
+ * When the field shows on the record page: `editing` only on its form and the create form, `reading` only on its read
26
+ * view and locked form; `always` when omitted. Its lists, cards and filters show it either way.
27
+ */
28
+ when(when: ShowWhen): FieldView<N, V>;
24
29
  /** How the value shows: `badge` for statuses, `code` in monospace, `percent` with a % sign. */
25
30
  format(format: FieldFormat): FieldView<N, V>;
26
31
  /**
@@ -23,6 +23,13 @@ var FieldView = class FieldView {
23
23
  ...o.when !== void 0 && { helpWhen: o.when }
24
24
  });
25
25
  }
26
+ /**
27
+ * When the field shows on the record page: `editing` only on its form and the create form, `reading` only on its read
28
+ * view and locked form; `always` when omitted. Its lists, cards and filters show it either way.
29
+ */
30
+ when(when) {
31
+ return this.with({ when });
32
+ }
26
33
  /** How the value shows: `badge` for statuses, `code` in monospace, `percent` with a % sign. */
27
34
  format(format) {
28
35
  return this.with({ format });
package/dist/view.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ActionModel, ChartGranularity, ChartRange, CreateModel, ExportModel, FilterWidgetModel, HelpWhen, LayoutItemModel, ListDisplayModel, NavModel, NavRecentModel, RecordMode, ResourceModel, SaveFeedback, SaveSearchModel, SignInPromptModel, ViewModel } from "./model.js";
2
2
  import { FieldRefs, Ref } from "./refs.js";
3
3
  import { actions } from "./view-actions.js";
4
+ import { ListColumn, columns } from "./view-columns.js";
4
5
  import { displays } from "./view-displays.js";
5
6
  import { FieldView } from "./view-field.js";
6
7
  import { widgets } from "./view-widgets.js";
@@ -65,11 +66,12 @@ export declare class ViewBuilder<R extends {
65
66
  /** Hints per field, such as `{ total: r.total.prefix('€').decimals(2) }`; calls add up. */
66
67
  fields(build: (r: FieldViews<R>) => Record<string, FieldView>): ViewBuilder<R>;
67
68
  /**
68
- * The list: its table's `columns`, `sort` and `search`, and other `displays` to switch to (`d.cards`, `d.custom`), one
69
- * of which may be the `default` the list opens in. Throws when display names collide or `default` names none.
69
+ * The list: its table's `columns`, fields or columns of related records (`c.related`), `sort` and `search`, and other
70
+ * `displays` to switch to (`d.cards`, `d.custom`), one of which may be the `default` the list opens in. Throws when
71
+ * display or column names collide or `default` names none.
70
72
  */
71
- list(build: (r: FieldRefs<FieldsOf<R>>, d: typeof displays) => {
72
- columns: Ref[];
73
+ list(build: (r: FieldRefs<FieldsOf<R>>, d: typeof displays, c: typeof columns) => {
74
+ columns: Array<Ref | ListColumn>;
73
75
  sort?: Array<[Ref, 'asc' | 'desc']>;
74
76
  search?: Ref[];
75
77
  displays?: ListDisplayModel[];
@@ -172,7 +174,7 @@ export declare class ViewBuilder<R extends {
172
174
  };
173
175
  limit?: number;
174
176
  }): ViewBuilder<R>;
175
- /** With the resource's model, also verifies the title, search result and list display fields, a gallery's image, `listOf`, `searchesOf` and the sidebar's `recent` group. */
177
+ /** With the resource's model, also verifies the title, search result and list display fields, related column names, a gallery's image, `listOf`, `searchesOf` and the sidebar's `recent` group. */
176
178
  toModel(resource?: ResourceModel): ViewModel;
177
179
  private refs;
178
180
  private next;
package/dist/view.js CHANGED
@@ -2,6 +2,7 @@ import { createRefs, ref } from "./refs.js";
2
2
  import { isMenuHref } from "./menu-href.js";
3
3
  import { navRecentErrors } from "./nav-recent.js";
4
4
  import { actions } from "./view-actions.js";
5
+ import { columns, isListColumn } from "./view-columns.js";
5
6
  import { displayErrors, displayFields, displays } from "./view-displays.js";
6
7
  import { FieldView } from "./view-field.js";
7
8
  import { widgets } from "./view-widgets.js";
@@ -72,13 +73,19 @@ var ViewBuilder = class ViewBuilder {
72
73
  } });
73
74
  }
74
75
  /**
75
- * The list: its table's `columns`, `sort` and `search`, and other `displays` to switch to (`d.cards`, `d.custom`), one
76
- * of which may be the `default` the list opens in. Throws when display names collide or `default` names none.
76
+ * The list: its table's `columns`, fields or columns of related records (`c.related`), `sort` and `search`, and other
77
+ * `displays` to switch to (`d.cards`, `d.custom`), one of which may be the `default` the list opens in. Throws when
78
+ * display or column names collide or `default` names none.
77
79
  */
78
80
  list(build) {
79
- const { columns, sort, search, displays: shown = [], default: opens } = build(this.refs(), displays);
81
+ const { columns: columns$1, sort, search, displays: shown = [], default: opens } = build(this.refs(), displays, columns);
82
+ const related = columns$1.filter(isListColumn).map(({ kind: _, ...column }) => column);
83
+ const names = columns$1.map((column) => column.name);
84
+ const repeated = names.find((name, index) => names.indexOf(name) !== index);
85
+ if (repeated !== void 0) throw new Error(`View "${this.state.resource}": the list has two columns named "${repeated}"`);
80
86
  const list = {
81
- columns: columns.map((ref) => ref.name),
87
+ columns: names,
88
+ ...related.length > 0 && { relatedColumns: related },
82
89
  ...sort && { sort: sort.map(([ref, dir]) => [ref.name, dir]) },
83
90
  ...search && { search: search.map((ref) => ref.name) },
84
91
  ...shown.length > 0 && { displays: shown },
@@ -218,7 +225,7 @@ var ViewBuilder = class ViewBuilder {
218
225
  };
219
226
  return this.next({ searchesOf: searches });
220
227
  }
221
- /** With the resource's model, also verifies the title, search result and list display fields, a gallery's image, `listOf`, `searchesOf` and the sidebar's `recent` group. */
228
+ /** With the resource's model, also verifies the title, search result and list display fields, related column names, a gallery's image, `listOf`, `searchesOf` and the sidebar's `recent` group. */
222
229
  toModel(resource) {
223
230
  const { title, nav, searchResult, list, listOf } = this.state;
224
231
  if (resource && title !== void 0 && !Object.hasOwn(resource.fields, title)) throw new Error(`View "${this.state.resource}": title field "${title}" does not exist on the resource`);
@@ -231,6 +238,8 @@ var ViewBuilder = class ViewBuilder {
231
238
  const flag = display.kind === "cards" ? display.flags?.find((name) => resource.fields[name].type !== "boolean") : void 0;
232
239
  if (flag !== void 0) throw new Error(`View "${this.state.resource}": display "${display.name}" shows "${flag}" as a flag, which is not a boolean field`);
233
240
  }
241
+ const clash = resource ? list?.relatedColumns?.find((column) => Object.hasOwn(resource.fields, column.name)) : void 0;
242
+ if (clash) throw new Error(`View "${this.state.resource}": related column "${clash.name}" has the name of a field of the resource`);
234
243
  for (const item of resource ? this.state.layout : []) {
235
244
  if (item.kind !== "gallery" || item.image === void 0) continue;
236
245
  if (!Object.hasOwn(resource.fields, item.image)) throw new Error(`View "${this.state.resource}": gallery "${item.title}" names field "${item.image}", which the resource does not have`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.19.12",
3
+ "version": "0.19.14",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {