alinea 0.5.11 → 0.6.0

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.
Files changed (61) hide show
  1. package/dist/chunks/{chunk-UUSWXVEM.js → chunk-3UKCBK2J.js} +1 -1
  2. package/dist/chunks/{chunk-MDIOFKJQ.js → chunk-533F6JLE.js} +21 -4
  3. package/dist/chunks/{chunk-I5C4WAC4.js → chunk-N5R3FLOP.js} +1 -1
  4. package/dist/cli/Serve.js +1 -1
  5. package/dist/cli/bin.js +1 -1
  6. package/dist/cli/generate/GenerateDashboard.js +1 -1
  7. package/dist/cloud/server/CloudAuthServer.js +1 -1
  8. package/dist/core/Document.js +2 -2
  9. package/dist/core/Field.d.ts +6 -1
  10. package/dist/core/Type.js +17 -0
  11. package/dist/core/shape/ListShape.js +4 -4
  12. package/dist/core/shape/RecordShape.js +3 -1
  13. package/dist/core/shape/RichTextShape.d.ts +3 -3
  14. package/dist/core/shape/RichTextShape.js +10 -6
  15. package/dist/dashboard/atoms/EntryEditorAtoms.js +24 -1
  16. package/dist/dashboard/atoms/FormAtoms.d.ts +8 -1
  17. package/dist/dashboard/atoms/FormAtoms.js +26 -10
  18. package/dist/dashboard/atoms/YAtom.js +5 -2
  19. package/dist/dashboard/editor/InputForm.js +1 -1
  20. package/dist/dashboard/editor/UseField.d.ts +2 -0
  21. package/dist/dashboard/editor/UseField.js +37 -2
  22. package/dist/dashboard/view/EntryEdit.js +1 -1
  23. package/dist/dashboard/view/InputLabel.d.ts +9 -25
  24. package/dist/dashboard/view/InputLabel.js +24 -13
  25. package/dist/index.css +52 -11
  26. package/dist/input/check/CheckField.browser.js +2 -1
  27. package/dist/input/check/CheckField.d.ts +0 -2
  28. package/dist/input/code/CodeField.browser.js +2 -1
  29. package/dist/input/code/CodeField.d.ts +0 -2
  30. package/dist/input/date/DateField.browser.js +2 -2
  31. package/dist/input/date/DateField.d.ts +0 -2
  32. package/dist/input/json/JsonField.browser.js +2 -1
  33. package/dist/input/json/JsonField.d.ts +0 -2
  34. package/dist/input/link/LinkField.browser.js +4 -4
  35. package/dist/input/link/LinkField.d.ts +0 -2
  36. package/dist/input/list/ListField.browser.js +14 -6
  37. package/dist/input/list/ListField.d.ts +0 -2
  38. package/dist/input/number/NumberField.browser.js +2 -2
  39. package/dist/input/number/NumberField.d.ts +0 -2
  40. package/dist/input/object/ObjectField.browser.js +3 -2
  41. package/dist/input/path/PathField.browser.js +2 -1
  42. package/dist/input/path/PathField.d.ts +0 -1
  43. package/dist/input/richtext/PickTextLink.js +1 -1
  44. package/dist/input/richtext/RichTextField.browser.js +390 -56
  45. package/dist/input/richtext/RichTextField.d.ts +0 -2
  46. package/dist/input/richtext/RichTextKit.js +2 -2
  47. package/dist/input/richtext/extensions/Link.js +1 -1
  48. package/dist/input/richtext/extensions/Small.js +1 -1
  49. package/dist/input/select/SelectField.browser.js +42 -38
  50. package/dist/input/select/SelectField.d.ts +0 -2
  51. package/dist/input/tabs/Tabs.browser.js +1 -1
  52. package/dist/input/text/TextField.browser.js +2 -1
  53. package/dist/input/text/TextField.d.ts +0 -2
  54. package/dist/picker/entry/EntryPicker.browser.js +1 -1
  55. package/dist/picker/url/UrlPicker.browser.js +1 -1
  56. package/dist/ui/util/TextareaAutosize.d.ts +2 -37
  57. package/dist/ui/util/TextareaAutosize.js +28 -258
  58. package/package.json +1 -1
  59. package/dist/chunks/chunk-OBYSELPT.js +0 -356
  60. package/dist/input/richtext/hook/UseEditor.d.ts +0 -4
  61. package/dist/input/richtext/hook/UseEditor.js +0 -22
@@ -2,7 +2,7 @@
2
2
  var package_default = {
3
3
  bin: "./dist/cli.js",
4
4
  name: "alinea",
5
- version: "0.5.11",
5
+ version: "0.6.0",
6
6
  license: "MIT",
7
7
  type: "module",
8
8
  scripts: {
@@ -12151,11 +12151,11 @@ var PasteRule = class {
12151
12151
  this.handler = config.handler;
12152
12152
  }
12153
12153
  };
12154
- var pasteRuleMatcherHandler = (text, find) => {
12154
+ var pasteRuleMatcherHandler = (text, find, event) => {
12155
12155
  if (isRegExp(find)) {
12156
12156
  return [...text.matchAll(find)];
12157
12157
  }
12158
- const matches2 = find(text);
12158
+ const matches2 = find(text, event);
12159
12159
  if (!matches2) {
12160
12160
  return [];
12161
12161
  }
@@ -12187,7 +12187,7 @@ function run(config) {
12187
12187
  const resolvedFrom = Math.max(from, pos);
12188
12188
  const resolvedTo = Math.min(to, pos + node.content.size);
12189
12189
  const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, void 0, "\uFFFC");
12190
- const matches2 = pasteRuleMatcherHandler(textToMatch, rule.find);
12190
+ const matches2 = pasteRuleMatcherHandler(textToMatch, rule.find, pasteEvent);
12191
12191
  matches2.forEach((match) => {
12192
12192
  if (match.index === void 0) {
12193
12193
  return;
@@ -12913,9 +12913,23 @@ var forEach = (items, fn) => (props) => {
12913
12913
  var insertContent = (value, options) => ({ tr, commands: commands2 }) => {
12914
12914
  return commands2.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options);
12915
12915
  };
12916
+ var removeWhitespaces = (node) => {
12917
+ const children = node.childNodes;
12918
+ for (let i = children.length - 1; i >= 0; i -= 1) {
12919
+ const child = children[i];
12920
+ if (child.nodeType === 3 && child.nodeValue && !/\S/.test(child.nodeValue)) {
12921
+ node.removeChild(child);
12922
+ } else if (child.nodeType === 1) {
12923
+ removeWhitespaces(child);
12924
+ }
12925
+ }
12926
+ return node;
12927
+ };
12916
12928
  function elementFromString(value) {
12917
12929
  const wrappedValue = `<body>${value}</body>`;
12918
- return new window.DOMParser().parseFromString(wrappedValue, "text/html").body;
12930
+ const html = new window.DOMParser().parseFromString(wrappedValue, "text/html").body;
12931
+ removeWhitespaces(html);
12932
+ return removeWhitespaces(html);
12919
12933
  }
12920
12934
  function createNodeFromContent(content, schema, options) {
12921
12935
  options = {
@@ -13355,6 +13369,9 @@ function getMarksBetween(from, to, doc3) {
13355
13369
  });
13356
13370
  } else {
13357
13371
  doc3.nodesBetween(from, to, (node, pos) => {
13372
+ if (!node || node.nodeSize === void 0) {
13373
+ return;
13374
+ }
13358
13375
  marks.push(...node.marks.map((mark) => ({
13359
13376
  from: pos,
13360
13377
  to: pos + node.nodeSize,
@@ -3,7 +3,7 @@ import {
3
3
  Plugin,
4
4
  PluginKey,
5
5
  posToDOMRect
6
- } from "./chunk-MDIOFKJQ.js";
6
+ } from "./chunk-533F6JLE.js";
7
7
 
8
8
  // node_modules/@popperjs/core/lib/enums.js
9
9
  var top = "top";
package/dist/cli/Serve.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-UUSWXVEM.js";
3
+ } from "../chunks/chunk-3UKCBK2J.js";
4
4
  import "../chunks/chunk-U5RRZUYZ.js";
5
5
 
6
6
  // src/cli/Serve.ts
package/dist/cli/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-UUSWXVEM.js";
3
+ } from "../chunks/chunk-3UKCBK2J.js";
4
4
  import "../chunks/chunk-U5RRZUYZ.js";
5
5
 
6
6
  // node_modules/mri/lib/index.mjs
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../../chunks/chunk-UUSWXVEM.js";
3
+ } from "../../chunks/chunk-3UKCBK2J.js";
4
4
  import {
5
5
  __commonJS,
6
6
  __toESM
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../chunks/chunk-IKINPSS5.js";
4
4
  import {
5
5
  package_default
6
- } from "../../chunks/chunk-UUSWXVEM.js";
6
+ } from "../../chunks/chunk-3UKCBK2J.js";
7
7
  import "../../chunks/chunk-U5RRZUYZ.js";
8
8
 
9
9
  // src/cloud/server/CloudAuthServer.ts
@@ -13,8 +13,8 @@ function document(label, definition) {
13
13
  return type(label, {
14
14
  ...tabs(
15
15
  tab("Document", {
16
- title: text("Title", { width: 0.5 }),
17
- path: path("Path", { width: 0.5 }),
16
+ title: text("Title", { required: true, width: 0.5 }),
17
+ path: path("Path", { required: true, width: 0.5 }),
18
18
  ...definition,
19
19
  [Meta]: {
20
20
  icon: IcRoundInsertDriveFile
@@ -4,6 +4,7 @@ import type { ComponentType } from 'react';
4
4
  import { Hint } from './Hint.js';
5
5
  import { Shape } from './Shape.js';
6
6
  export interface FieldOptions<Value> {
7
+ /** A description of the field */
7
8
  label: string;
8
9
  /** Hide this field in the dashboard */
9
10
  hidden?: boolean;
@@ -11,8 +12,12 @@ export interface FieldOptions<Value> {
11
12
  readOnly?: boolean;
12
13
  /** The initial value of the field */
13
14
  initialValue?: Value;
14
- /** The value of this field is shared across all languages */
15
+ /** The value of this field is shared across all languages */
15
16
  shared?: boolean;
17
+ /** Providing a value for this field is required */
18
+ required?: boolean;
19
+ /** Validate the given value */
20
+ validate?(value: Value): boolean | string | undefined;
16
21
  }
17
22
  export type WithoutLabel<Options extends FieldOptions<any>> = Omit<Options, 'label'>;
18
23
  export interface FieldMeta<Value, Mutator, Options extends FieldOptions<Value>> {
package/dist/core/Type.js CHANGED
@@ -113,12 +113,29 @@ var TypeInstance = class {
113
113
  this.sections.push(section({ definition: current }));
114
114
  current = {};
115
115
  };
116
+ const seen = /* @__PURE__ */ new Map();
117
+ function validateField(key, field) {
118
+ const ref = Field.ref(field);
119
+ if (!seen.has(ref))
120
+ return seen.set(ref, key);
121
+ const fieldLabel = Field.label(field);
122
+ throw new Error(
123
+ `Duplicate field "${fieldLabel}" in type "${label}", found under key "${key}" and "${seen.get(
124
+ ref
125
+ )}"
126
+ See: https://alinea.sh/docs/configuration/schema/type#fields-must-be-unique`
127
+ );
128
+ }
116
129
  for (const [key, value] of entries(definition)) {
117
130
  if (Field.isField(value)) {
118
131
  current[key] = value;
132
+ validateField(key, value);
119
133
  } else if (Section.isSection(value)) {
120
134
  addCurrent();
121
135
  this.sections.push(value);
136
+ for (const [key2, field] of entries(Section.fields(value))) {
137
+ validateField(key2, field);
138
+ }
122
139
  }
123
140
  }
124
141
  addCurrent();
@@ -120,19 +120,19 @@ var ListShape = class {
120
120
  parent.set(key, this.toY(this.create()));
121
121
  }
122
122
  watch(parent, key) {
123
- const record = parent.has(key) ? parent.get(key) : parent.set(key, new YMap());
123
+ const map = parent.get(key);
124
124
  return (fun) => {
125
125
  function w(events, transaction) {
126
126
  for (const event of events) {
127
- if (event.target === record)
127
+ if (event.target === map)
128
128
  fun();
129
129
  if (event instanceof YMapEvent && event.keysChanged.has("index"))
130
130
  fun();
131
131
  }
132
132
  }
133
- record.observeDeep(w);
133
+ map.observeDeep(w);
134
134
  return () => {
135
- record.unobserveDeep(w);
135
+ map.unobserveDeep(w);
136
136
  };
137
137
  };
138
138
  }
@@ -48,7 +48,9 @@ var RecordShape = class _RecordShape {
48
48
  return void map.set(key, this.toY(value));
49
49
  const self = value ?? {};
50
50
  for (const key2 of keys(this.properties)) {
51
- this.properties[key2].applyY(self[key2], current, key2);
51
+ this.properties[key2].init(current, key2);
52
+ if (key2 in self)
53
+ this.properties[key2].applyY(self[key2], current, key2);
52
54
  }
53
55
  }
54
56
  init(parent, key) {
@@ -28,14 +28,14 @@ export declare class RichTextShape<Blocks> implements Shape<TextDoc<Blocks>, Ric
28
28
  values: Record<string, RecordShape>;
29
29
  constructor(label: Label, shapes?: Record<string, RecordShape<object>> | undefined, initialValue?: TextDoc<Blocks> | undefined, searchable?: boolean | undefined);
30
30
  create(): TextDoc<Blocks>;
31
- toXml(rows: TextDoc<Blocks>): (Y.XmlElement<{
31
+ toXml(rows: TextDoc<Blocks>): (Y.XmlText | Y.XmlElement<{
32
32
  [key: string]: string;
33
- }> | Y.XmlText)[];
33
+ }>)[];
34
34
  toY(value: TextDoc<Blocks>): Y.Map<unknown>;
35
35
  fromY(value: Y.Map<any>): TextDoc<Blocks>;
36
36
  applyY(value: TextDoc<Blocks>, parent: Y.Map<any>, key: string): void;
37
37
  init(parent: Y.Map<any>, key: string): void;
38
- watch(parent: Y.Map<any>, key: string): () => () => void;
38
+ watch(parent: Y.Map<any>, key: string): (fun: () => void) => () => void;
39
39
  mutator(parent: Y.Map<any>, key: string): {
40
40
  map: any;
41
41
  fragment: any;
@@ -239,15 +239,19 @@ var RichTextShape = class {
239
239
  parent.set(key, this.toY(this.create()));
240
240
  }
241
241
  watch(parent, key) {
242
- return () => () => {
242
+ const map = parent.get(key);
243
+ return (fun) => {
244
+ const listener = (events, tx) => {
245
+ if (tx.origin === "self")
246
+ return;
247
+ fun();
248
+ };
249
+ map.observeDeep(listener);
250
+ return () => map.unobserveDeep(listener);
243
251
  };
244
252
  }
245
253
  mutator(parent, key) {
246
- let map = parent.get(key);
247
- if (!map) {
248
- parent.set(key, this.toY([]));
249
- map = parent.get(key);
250
- }
254
+ const map = parent.get(key);
251
255
  return {
252
256
  map,
253
257
  fragment: map.get("$text"),
@@ -11,6 +11,7 @@ import "../../chunks/chunk-U5RRZUYZ.js";
11
11
  import { Media } from "alinea/backend";
12
12
  import {
13
13
  EntryPhase,
14
+ Field,
14
15
  ROOT_KEY,
15
16
  Root,
16
17
  Type,
@@ -311,7 +312,27 @@ function createEntryEditor(entryData) {
311
312
  }
312
313
  return res;
313
314
  }
315
+ const errorsAtom = atom((get) => {
316
+ return get(get(form).errors);
317
+ });
318
+ const confirmErrorsAtom = atom(null, (get) => {
319
+ const errors = get(errorsAtom);
320
+ if (errors.size > 0) {
321
+ let errorMessage = "";
322
+ for (const [path, { field, error }] of errors.entries()) {
323
+ const label = Field.label(field);
324
+ const line = typeof error === "string" ? `${label}: ${error}` : label;
325
+ errorMessage += `
326
+ \u2014 ${line}`;
327
+ }
328
+ const message = `These fields contains errors, are you sure you want to publish?${errorMessage}`;
329
+ return confirm(message);
330
+ }
331
+ return true;
332
+ });
314
333
  const publishEdits = atom(null, async (get, set) => {
334
+ if (!set(confirmErrorsAtom))
335
+ return;
315
336
  const currentFile = entryFile(activeVersion);
316
337
  const update = base64.stringify(edits.getLocalUpdate());
317
338
  const entry = await getDraftEntry({ phase: EntryPhase.Published });
@@ -363,6 +384,8 @@ function createEntryEditor(entryData) {
363
384
  });
364
385
  });
365
386
  const publishDraft = atom(null, async (get, set) => {
387
+ if (!set(confirmErrorsAtom))
388
+ return;
366
389
  const mutations = [
367
390
  {
368
391
  type: MutationType.Publish,
@@ -533,7 +556,7 @@ function createEntryEditor(entryData) {
533
556
  const form = atom((get) => {
534
557
  const doc = get(currentDoc);
535
558
  const readOnly = doc !== edits.doc ? true : void 0;
536
- return new FormAtoms(type, doc.getMap(ROOT_KEY), { readOnly });
559
+ return new FormAtoms(type, doc.getMap(ROOT_KEY), "", { readOnly });
537
560
  });
538
561
  const yUpdate = debounceAtom(edits.yUpdate, 250);
539
562
  const discardEdits = edits.resetChanges;
@@ -12,12 +12,19 @@ export interface FieldInfo<Value = any, Mutator = any, Options extends FieldOpti
12
12
  export declare class FormAtoms<T = any> {
13
13
  type: Type<T>;
14
14
  container: Y.Map<any>;
15
+ path: string;
15
16
  options: {
16
17
  parent?: FormAtoms;
17
18
  readOnly?: boolean;
18
19
  };
19
20
  private fields;
20
- constructor(type: Type<T>, container: Y.Map<any>, options?: {
21
+ private errorMap;
22
+ errors: import("jotai").WritableAtom<Map<string, {
23
+ field: Field;
24
+ error: boolean | string;
25
+ }>, [path: string, field: Field<any, any, FieldOptions<any>>, error: string | boolean | undefined], void>;
26
+ hasErrors: Atom<boolean>;
27
+ constructor(type: Type<T>, container: Y.Map<any>, path?: string, options?: {
21
28
  parent?: FormAtoms;
22
29
  readOnly?: boolean;
23
30
  });
@@ -5,8 +5,7 @@ import {
5
5
  atom
6
6
  } from "../../chunks/chunk-OBOPLPUQ.js";
7
7
  import {
8
- Doc,
9
- YMap
8
+ Doc
10
9
  } from "../../chunks/chunk-OYP4EJOA.js";
11
10
  import "../../chunks/chunk-O6EXLFU2.js";
12
11
  import "../../chunks/chunk-U5RRZUYZ.js";
@@ -25,9 +24,10 @@ import { entries } from "alinea/core/util/Objects";
25
24
  import { createContext, useContext, useMemo } from "react";
26
25
  import { jsx } from "react/jsx-runtime";
27
26
  var FormAtoms = class {
28
- constructor(type, container, options = {}) {
27
+ constructor(type, container, path = "", options = {}) {
29
28
  this.type = type;
30
29
  this.container = container;
30
+ this.path = path;
31
31
  this.options = options;
32
32
  const readOnly = options.readOnly;
33
33
  const forcedOptions = typeof readOnly === "boolean" ? { readOnly } : {};
@@ -65,6 +65,26 @@ var FormAtoms = class {
65
65
  }, "self");
66
66
  }
67
67
  fields = /* @__PURE__ */ new Map();
68
+ errorMap = atom(
69
+ /* @__PURE__ */ new Map()
70
+ );
71
+ errors = atom(
72
+ (get) => get(this.errorMap),
73
+ (get, set, path, field, error) => {
74
+ const current = get(this.errorMap);
75
+ if (!error && !current.has(path))
76
+ return;
77
+ const errors = new Map(current);
78
+ if (error)
79
+ errors.set(path, { field, error });
80
+ else
81
+ errors.delete(path);
82
+ set(this.errorMap, errors);
83
+ if (this.options.parent)
84
+ set(this.options.parent.errors, path, field, error);
85
+ }
86
+ );
87
+ hasErrors = atom((get) => get(this.errorMap).size > 0);
68
88
  data() {
69
89
  return Type.shape(this.type).fromY(this.container);
70
90
  }
@@ -87,7 +107,7 @@ var FormAtoms = class {
87
107
  g(revision);
88
108
  const current = shape.fromY(this.container.get(key));
89
109
  const next = tracker ? tracker(this.getter(g)) : current;
90
- if (next !== current)
110
+ if (tracker && next !== current)
91
111
  shape.applyY(next, this.container, key);
92
112
  return next;
93
113
  });
@@ -105,7 +125,6 @@ var FormAtoms = class {
105
125
  const res = this.fields.get(Field.ref(field));
106
126
  const label = Field.label(field);
107
127
  if (!res) {
108
- console.log(this.options);
109
128
  if (this.options.parent)
110
129
  return this.options.parent.fieldInfo(field);
111
130
  throw new Error(`Field not found: ${label}`);
@@ -146,12 +165,9 @@ function FormRow({
146
165
  const rowForm = useMemo(() => {
147
166
  const key = form.keyOf(field);
148
167
  const inner = form.container.get(key);
149
- if (rowId) {
150
- if (!inner.has(rowId))
151
- inner.set(rowId, new YMap());
152
- }
153
168
  const row = rowId ? inner.get(rowId) : inner;
154
- return new FormAtoms(type, row, {
169
+ const path = form.path + `.${key}` + (rowId ? `[${rowId}]` : "");
170
+ return new FormAtoms(type, row, path, {
155
171
  readOnly,
156
172
  parent: form
157
173
  });
@@ -7,8 +7,11 @@ import "../../chunks/chunk-U5RRZUYZ.js";
7
7
  function yAtom(yType, get) {
8
8
  const revision = atom(0);
9
9
  revision.onMount = (setAtom) => {
10
- const onChange = () => setAtom((x) => x + 1);
11
- onChange();
10
+ const onChange = (events, tx) => {
11
+ if (tx.origin === "self")
12
+ return;
13
+ setAtom((x) => x + 1);
14
+ };
12
15
  yType.observeDeep(onChange);
13
16
  return () => yType.unobserveDeep(onChange);
14
17
  };
@@ -37,9 +37,9 @@ function MissingView({ field }) {
37
37
  }
38
38
  function InputField({ field }) {
39
39
  const View = field[Field.Data].view;
40
+ const options = useFieldOptions(field);
40
41
  if (!View)
41
42
  return /* @__PURE__ */ jsx(MissingView, { field });
42
- const options = useFieldOptions(field);
43
43
  if (options.hidden)
44
44
  return null;
45
45
  return /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(View, { field }) });
@@ -5,8 +5,10 @@ export declare function useField<Value, Mutator, Options extends FieldOptions<Va
5
5
  options: Awaited<Options>;
6
6
  value: Awaited<Value>;
7
7
  mutator: Mutator;
8
+ error: string | boolean | undefined;
8
9
  };
9
10
  export declare function useFieldKey<Value, Mutator, Options extends FieldOptions<Value>>(field: Field<Value, Mutator, Options>): string;
10
11
  export declare function useFieldOptions<Value, Mutator, Options extends FieldOptions<Value>>(field: Field<Value, Mutator, Options>): Awaited<Options> | Awaited<Options>;
12
+ export declare function useFieldError<Value, Mutator, Options extends FieldOptions<Value>>(field: Field<Value, Mutator, Options>): string | boolean | undefined;
11
13
  export declare function useFieldValue<Value, Mutator, Options extends FieldOptions<Value>>(field: Field<Value, Mutator, Options>): Awaited<Value>;
12
14
  export declare function useFieldMutator<Value, Mutator, Options extends FieldOptions<Value>>(field: Field<Value, Mutator, Options>): Mutator;
@@ -1,10 +1,12 @@
1
1
  import {
2
- useAtomValue
2
+ useAtomValue,
3
+ useSetAtom
3
4
  } from "../../chunks/chunk-WF77DMLN.js";
4
5
  import "../../chunks/chunk-OBOPLPUQ.js";
5
6
  import "../../chunks/chunk-U5RRZUYZ.js";
6
7
 
7
8
  // src/dashboard/editor/UseField.tsx
9
+ import { useCallback, useEffect, useMemo } from "react";
8
10
  import { useFormContext } from "../atoms/FormAtoms.js";
9
11
  function useField(field) {
10
12
  const atoms = useFormContext();
@@ -13,12 +15,14 @@ function useField(field) {
13
15
  const value = useFieldValue(actual);
14
16
  const mutator = useFieldMutator(actual);
15
17
  const options = useFieldOptions(actual);
18
+ const error = useFieldError(actual);
16
19
  return {
17
20
  fieldKey,
18
21
  label: options.label,
19
22
  options,
20
23
  value,
21
- mutator
24
+ mutator,
25
+ error
22
26
  };
23
27
  }
24
28
  function useFieldKey(field) {
@@ -31,6 +35,36 @@ function useFieldOptions(field) {
31
35
  const atom = atoms.fieldInfo(field);
32
36
  return useAtomValue(atom.options);
33
37
  }
38
+ function useFieldError(field) {
39
+ const atoms = useFormContext();
40
+ const setError = useSetAtom(atoms.errors);
41
+ const value = useFieldValue(field);
42
+ const options = useFieldOptions(field);
43
+ const key = useFieldKey(field);
44
+ const fieldPath = atoms.path + "." + key;
45
+ const hasError = useCallback(
46
+ (value2) => {
47
+ if (options.validate) {
48
+ const validates = options.validate(value2);
49
+ if (typeof validates === "boolean")
50
+ return !validates;
51
+ return validates;
52
+ }
53
+ const isRequired = options.required;
54
+ const isEmpty = value2 === void 0 || value2 === null || value2 === "" || Array.isArray(value2) && value2.length === 0;
55
+ if (isRequired && isEmpty)
56
+ return true;
57
+ },
58
+ [options]
59
+ );
60
+ const error = useMemo(() => {
61
+ return hasError(value);
62
+ }, [hasError, value]);
63
+ useEffect(() => {
64
+ setError(fieldPath, field, error);
65
+ }, [setError, fieldPath, field, error]);
66
+ return error;
67
+ }
34
68
  function useFieldValue(field) {
35
69
  const atoms = useFormContext();
36
70
  const atom = atoms.fieldInfo(field);
@@ -43,6 +77,7 @@ function useFieldMutator(field) {
43
77
  }
44
78
  export {
45
79
  useField,
80
+ useFieldError,
46
81
  useFieldKey,
47
82
  useFieldMutator,
48
83
  useFieldOptions,
@@ -205,7 +205,7 @@ function EntryEdit({ editor }) {
205
205
  }
206
206
  ) }),
207
207
  /* @__PURE__ */ jsx(EntryEditorProvider, { editor, children: /* @__PURE__ */ jsx(SuspenseBoundary, { name: "input form", children: mode === EditMode.Diff ? /* @__PURE__ */ jsx(ShowChanges, { editor }) : hasRootTabs && visibleTypes ? /* @__PURE__ */ jsx(Tabs.Panels, { children: visibleTypes.map((type, i) => {
208
- return /* @__PURE__ */ jsx(FormProvider, { form, children: /* @__PURE__ */ jsx(Tabs.Panel, { tabIndex: i, children: /* @__PURE__ */ jsx(InputForm, { type }) }) }, i);
208
+ return /* @__PURE__ */ jsx(FormProvider, { form, children: /* @__PURE__ */ jsx(Tabs.Panel, { unmount: false, tabIndex: i, children: /* @__PURE__ */ jsx(InputForm, { type }) }) }, i);
209
209
  }) }) : /* @__PURE__ */ jsx(VStack, { gap: 18, children: /* @__PURE__ */ jsx(InputForm, { form }) }) }) })
210
210
  ] })
211
211
  ] }),
@@ -1,21 +1,20 @@
1
- import { Label } from 'alinea/core/Label';
2
1
  import { ComponentType, PropsWithChildren } from 'react';
3
2
  export type LabelHeaderProps = {
4
- label: Label;
3
+ label: string;
5
4
  help?: string;
6
- optional?: boolean;
7
5
  size?: 'small' | 'medium' | 'large';
8
6
  focused?: boolean;
9
7
  icon?: ComponentType;
10
8
  shared?: boolean;
11
9
  readOnly?: boolean;
10
+ required?: boolean;
11
+ error?: boolean | string;
12
12
  };
13
13
  export declare const LabelHeader: import("react").NamedExoticComponent<LabelHeaderProps>;
14
- export type LabelProps = PropsWithChildren<{
15
- label?: Label;
14
+ export interface LabelProps extends PropsWithChildren {
15
+ label?: string;
16
16
  asLabel?: boolean;
17
17
  help?: string;
18
- optional?: boolean;
19
18
  width?: number;
20
19
  inline?: boolean;
21
20
  collection?: boolean;
@@ -26,23 +25,8 @@ export type LabelProps = PropsWithChildren<{
26
25
  shared?: boolean;
27
26
  readOnly?: boolean;
28
27
  className?: string;
29
- }>;
28
+ error?: boolean | string;
29
+ required?: boolean;
30
+ }
30
31
  /** Label for an input */
31
- export declare const InputLabel: import("react").ForwardRefExoticComponent<{
32
- label?: string | undefined;
33
- asLabel?: boolean | undefined;
34
- help?: string | undefined;
35
- optional?: boolean | undefined;
36
- width?: number | undefined;
37
- inline?: boolean | undefined;
38
- collection?: boolean | undefined;
39
- focused?: boolean | undefined;
40
- size?: "small" | "medium" | "large" | undefined;
41
- icon?: ComponentType | undefined;
42
- empty?: boolean | undefined;
43
- shared?: boolean | undefined;
44
- readOnly?: boolean | undefined;
45
- className?: string | undefined;
46
- } & {
47
- children?: import("react").ReactNode;
48
- } & import("react").RefAttributes<HTMLElement>>;
32
+ export declare const InputLabel: import("react").ForwardRefExoticComponent<LabelProps & import("react").RefAttributes<HTMLElement>>;