@protobase/ui 0.38.0 → 0.39.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.
@@ -10,7 +10,7 @@ const isCreatable = (model, field) => field.name !== model.tenant && !field.read
10
10
  /**
11
11
  * The view's sections limited to creatable fields, plus a section for any creatable field the layout leaves out. The
12
12
  * create form is a form, so sections and fields meant only for reading the record stay out of their sections, and a
13
- * creatable field among them lands in that last section.
13
+ * creatable field among them lands in that last section. A write-only field is on the form only where a section names it.
14
14
  */
15
15
  const createSections = (model, view) => {
16
16
  const creatable = Object.values(model.fields).filter((field) => isCreatable(model, field)).map((field) => field.name);
@@ -25,7 +25,7 @@ const createSections = (model, view) => {
25
25
  fields
26
26
  }] : [];
27
27
  });
28
- const rest = creatable.filter((name) => !placed.has(name));
28
+ const rest = creatable.filter((name) => !placed.has(name) && !model.fields[name].writeOnly);
29
29
  if (rest.length === 0) return sections;
30
30
  return [...sections, {
31
31
  title: sections.length === 0 ? text("details") : text("otherFields"),
@@ -52,6 +52,7 @@ const text = defineMessages({
52
52
  show: (label) => `Show ${label}`,
53
53
  hide: (label) => `Hide ${label}`,
54
54
  couldNotShow: (label) => `Could not show the ${label}`,
55
+ setNewValue: "Set a new value",
55
56
  noAnswer: "It did not answer.",
56
57
  signInRefused: (message) => `It did not accept your sign-in: ${message}`,
57
58
  loadingFrom: (label) => `Loading from ${label}`,
@@ -114,6 +115,7 @@ const text = defineMessages({
114
115
  show: (label) => `${label} tonen`,
115
116
  hide: (label) => `${label} verbergen`,
116
117
  couldNotShow: (label) => `Kon ${label} niet tonen`,
118
+ setNewValue: "Nieuwe waarde instellen",
117
119
  noAnswer: "Het antwoordde niet.",
118
120
  signInRefused: (message) => `Het accepteerde je inlog niet: ${message}`,
119
121
  loadingFrom: (label) => `Laden van ${label}`,
@@ -80,7 +80,7 @@ const hidden = (model, name) => name === model.tenant || model.primaryKey.includ
80
80
  */
81
81
  const layoutFor = (model, view, read) => {
82
82
  if (view && view.layout.length > 0) return view.layout;
83
- const shown = Object.values(model.fields).filter((field) => !hidden(model, field.name));
83
+ const shown = Object.values(model.fields).filter((field) => !hidden(model, field.name) && !field.writeOnly);
84
84
  const editable = shown.filter((field) => !field.readOnly);
85
85
  const fields = (editable.length > 0 && !read ? editable : shown).map((field) => field.name);
86
86
  return [
@@ -222,7 +222,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
222
222
  const status = hasStatus && !sidebarFields.includes("status") && statusValue !== null && statusValue !== void 0 && statusValue !== "" ? String(statusValue) : void 0;
223
223
  const updatedAt = auditField(model, "updated");
224
224
  const hint = (name) => view?.fields[name];
225
- const visible = (name) => fieldAccess(stored.permissions, name) !== "hidden" && whenShown(hint(name)?.when, editing);
225
+ const visible = (name) => fieldAccess(stored.permissions, name) !== "hidden" && whenShown(hint(name)?.when, editing) && (shown === "form" || !model.fields[name]?.writeOnly);
226
226
  const known = (name) => !preview || Object.hasOwn(stored.record, name);
227
227
  const fieldView = (name) => {
228
228
  const field = model.fields[name];
@@ -263,7 +263,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
263
263
  },
264
264
  locked: shown !== "form" || access === "read",
265
265
  invalid: Boolean(editor.errors[name]),
266
- reveal: field.sensitive ? () => client.reveal(model.name, recordKey, name) : void 0,
266
+ reveal: field.sensitive && !field.writeOnly ? () => client.reveal(model.name, recordKey, name) : void 0,
267
267
  resource: model.name,
268
268
  onChange: (next) => pick(name, next)
269
269
  }) : /* @__PURE__ */ jsx(Skeleton, { className: "h-9" })
@@ -12,7 +12,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
12
  import { useMutation } from "@tanstack/react-query";
13
13
  //#region src/app/sensitive-field.tsx
14
14
  const mask = "••••••••••";
15
- /** A value hidden like a password. Showing a stored one asks the server for it, once per page visit. */
15
+ /**
16
+ * A value hidden like a password. Showing a stored one asks the server for it, once per page visit; a write-only one has
17
+ * none to show, so its eye only unmasks what was typed.
18
+ */
16
19
  const SensitiveField = ({ field, hints, value, reveal, locked, invalid, placeholder, onChange }) => {
17
20
  const [shown, setShown] = useState(false);
18
21
  const stored = useMutation({
@@ -56,7 +59,7 @@ const SensitiveField = ({ field, hints, value, reveal, locked, invalid, placehol
56
59
  invalid,
57
60
  "aria-label": label,
58
61
  className: cn(hints?.format === "code" && "font-mono text-xs"),
59
- placeholder: typed || !reveal ? placeholder : mask,
62
+ placeholder: typed || !reveal ? placeholder ?? (field.writeOnly ? text("setNewValue") : void 0) : mask,
60
63
  value: text$1,
61
64
  trailing: eye,
62
65
  onChange: (event) => onChange(event.target.value)
@@ -62,8 +62,8 @@ const kindOf = (field, view, index) => {
62
62
  }
63
63
  };
64
64
  const currencyCents = (value) => Math.round(Number.parseFloat(String(value)) * 100);
65
- /** The fields a view's list shows as columns: its list columns, else the resource's first six fields. */
66
- const listColumnNames = (resource, view) => view?.list?.columns ?? Object.keys(resource.fields).slice(0, 6);
65
+ /** The fields a view's list shows as columns: its list columns, else the resource's first six fields the caller reads. */
66
+ const listColumnNames = (resource, view) => view?.list?.columns ?? Object.values(resource.fields).filter((field) => !field.writeOnly).slice(0, 6).map((field) => field.name);
67
67
  /** Grid columns for `names`, by default the view's list: kinds follow the field types and the view's format hints. */
68
68
  const columnsFromModel = (resource, view, highlight = [], names = listColumnNames(resource, view)) => {
69
69
  const title = recordTitleField(resource, view);
@@ -9,8 +9,9 @@ const labelCandidates = [
9
9
  "code",
10
10
  "description"
11
11
  ];
12
+ const namesRecords = (field) => field?.type === "text" && !field.sensitive;
12
13
  /** The field that names a record when another record points at it. */
13
- const labelFieldOf = (model, view) => view?.title ?? labelCandidates.find((name) => model.fields[name]?.type === "text") ?? Object.values(model.fields).find((field) => field.type === "text")?.name ?? model.primaryKey[0];
14
+ const labelFieldOf = (model, view) => view?.title ?? labelCandidates.find((name) => namesRecords(model.fields[name])) ?? Object.values(model.fields).find(namesRecords)?.name ?? model.primaryKey[0];
14
15
  /** What names a record: the view's `title`, else the first text column of its list, else `labelFieldOf`. */
15
16
  const recordTitleField = (model, view) => view?.title ?? view?.list?.columns.find((name) => model.fields[name]?.type === "text") ?? labelFieldOf(model);
16
17
  /** A record's name as shown: its title field's value, an enum's by its label, else `fallback` when the value is missing. */
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.38.0";
2
+ var version = "0.39.1";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };