@nextlyhq/plugin-form-builder 0.0.2-alpha.62 → 0.0.2-alpha.65

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nextlyhq/plugin-form-builder
2
2
 
3
- Drag-and-drop form builder plugin for Nextly. Build forms visually, collect submissions, and filter them by form. _Coming soon in beta._
3
+ Drag-and-drop form builder plugin for Nextly. Build forms visually, collect submissions, and filter them by form.
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/@nextlyhq/plugin-form-builder"><img alt="npm" src="https://img.shields.io/npm/v/@nextlyhq/plugin-form-builder?style=flat-square&label=npm&color=cb3837" /></a>
@@ -8,11 +8,10 @@ Drag-and-drop form builder plugin for Nextly. Build forms visually, collect subm
8
8
  <a href="https://nextlyhq.com/docs"><img alt="Status" src="https://img.shields.io/badge/status-alpha-orange?style=flat-square" /></a>
9
9
  </p>
10
10
 
11
- > [!WARNING]
12
- > **Coming soon in beta. Plugins are not ready for use yet.** Public plugin support, including stable APIs, the official plugin gallery, and documentation guarantees, lands at the Nextly **beta** release. This package is published for early exploration only. Surfaces, names, and behaviour will change without notice. Do not rely on plugins for production or serious development work. Wait for the beta release announcement before integrating plugins into your project.
13
-
14
11
  > [!IMPORTANT]
15
- > Nextly is in alpha. APIs may change before 1.0. Pin exact versions in production.
12
+ > Nextly is in alpha. APIs may change before 1.0 — pin exact versions in production.
13
+ > See the [plugin stability ladder](https://nextlyhq.com/docs/plugins/stability) for
14
+ > which plugin surfaces are stable and which are experimental.
16
15
 
17
16
  ## What it is
18
17
 
@@ -1,4 +1,4 @@
1
- import { ComponentPath } from '@nextlyhq/plugin-sdk/admin';
1
+ import { DocumentLockGates, ComponentPath } from '@nextlyhq/plugin-sdk/admin';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import { q as FormField, s as FormFieldTypeId, A as AnyFormField, b as FormSettings, v as FormNotification } from '../types-hLfLe81K.js';
4
4
  import { FormFieldCatalogType, FieldTypeCatalogEntry } from 'nextly/field-catalog';
@@ -25,8 +25,18 @@ interface FormBuilderViewProps {
25
25
  onSave?: (data: unknown) => void;
26
26
  onSuccess?: (entry?: Record<string, unknown>) => void;
27
27
  onCancel?: () => void;
28
+ /**
29
+ * What a colleague's claim on this form withholds, handed over by the admin
30
+ * page that renders this view.
31
+ *
32
+ * The strip above this builder says in words that unsaved changes cannot be
33
+ * saved while someone else holds the document. Committing anyway would make
34
+ * that sentence false and would overwrite the holder's row, so the save reads
35
+ * it too.
36
+ */
37
+ documentLock?: DocumentLockGates;
28
38
  }
29
- declare function FormBuilderView({ id, entryId, collection, collectionSlug, isCreating, initialData, onSave, onSuccess, onCancel, }: FormBuilderViewProps): react_jsx_runtime.JSX.Element;
39
+ declare function FormBuilderView({ id, entryId, collection, collectionSlug, isCreating, initialData, onSave, onSuccess, onCancel, documentLock, }: FormBuilderViewProps): react_jsx_runtime.JSX.Element;
30
40
 
31
41
  interface SubmissionRow {
32
42
  id: string;
@@ -216,6 +226,17 @@ interface FormBuilderProviderProps {
216
226
  };
217
227
  /** Child components */
218
228
  children: ReactNode;
229
+ /**
230
+ * The document belongs to someone else right now, so nothing here may change
231
+ * it.
232
+ *
233
+ * Held here rather than at each control because the builder has dozens of
234
+ * them across four tabs, and a rule spelled at every one is a rule one new
235
+ * control forgets. Reading is untouched: the editor still browses fields,
236
+ * switches tabs and opens settings, which is exactly what the strip above
237
+ * offers them.
238
+ */
239
+ readOnly?: boolean;
219
240
  }
220
241
  /** Default form settings (the canonical defaults from the settings reader) */
221
242
  declare const DEFAULT_SETTINGS: FormSettings;
@@ -251,7 +272,7 @@ declare function createFieldFromType(type: FormFieldTypeId, existingNames?: read
251
272
  * </FormBuilderProvider>
252
273
  * ```
253
274
  */
254
- declare function FormBuilderProvider({ initialData, children, }: FormBuilderProviderProps): react_jsx_runtime.JSX.Element;
275
+ declare function FormBuilderProvider({ initialData, children, readOnly, }: FormBuilderProviderProps): react_jsx_runtime.JSX.Element;
255
276
  /**
256
277
  * useFormBuilder - Access form builder context
257
278
  *
@@ -9,7 +9,7 @@ import {
9
9
  normalizeFormSettings,
10
10
  parseFieldRef,
11
11
  parseRedirectReference
12
- } from "../chunk-H5X3FKOZ.js";
12
+ } from "../chunk-W6NLZFHH.js";
13
13
 
14
14
  // src/admin/index.ts
15
15
  import {
@@ -938,6 +938,7 @@ import {
938
938
  useState as useState3
939
939
  } from "react";
940
940
  import { jsx as jsx3 } from "react/jsx-runtime";
941
+ var READS_ONLY = /* @__PURE__ */ new Set(["selectField", "setActiveTab", "markAsSaved"]);
941
942
  var DEFAULT_SETTINGS = DEFAULT_FORM_SETTINGS;
942
943
  function createNotification() {
943
944
  const id = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;
@@ -1021,7 +1022,8 @@ function createFieldFromType(type, existingNames = []) {
1021
1022
  }
1022
1023
  function FormBuilderProvider({
1023
1024
  initialData,
1024
- children
1025
+ children,
1026
+ readOnly = false
1025
1027
  }) {
1026
1028
  const [fields, setFieldsState] = useState3(
1027
1029
  initialData?.fields || []
@@ -1225,7 +1227,15 @@ function FormBuilderProvider({
1225
1227
  markAsSaved
1226
1228
  ]
1227
1229
  );
1228
- return /* @__PURE__ */ jsx3(FormBuilderContext.Provider, { value, children });
1230
+ const guarded = useMemo2(() => {
1231
+ if (!readOnly) return value;
1232
+ return Object.fromEntries(
1233
+ Object.entries(value).map(
1234
+ ([name, member]) => typeof member === "function" && !READS_ONLY.has(name) ? [name, () => void 0] : [name, member]
1235
+ )
1236
+ );
1237
+ }, [value, readOnly]);
1238
+ return /* @__PURE__ */ jsx3(FormBuilderContext.Provider, { value: guarded, children });
1229
1239
  }
1230
1240
  function useFormBuilder() {
1231
1241
  const context = useContext(FormBuilderContext);
@@ -4434,7 +4444,8 @@ function FormBuilderViewInner({
4434
4444
  entryId,
4435
4445
  onSave,
4436
4446
  onSuccess,
4437
- onCancel
4447
+ onCancel,
4448
+ documentLock
4438
4449
  }) {
4439
4450
  const {
4440
4451
  fields,
@@ -4521,6 +4532,7 @@ function FormBuilderViewInner({
4521
4532
  [fields, addField, selectField]
4522
4533
  );
4523
4534
  const handleSave = useCallback7(async () => {
4535
+ if (documentLock?.actionsDisabled === true) return;
4524
4536
  if (fields.length === 0) {
4525
4537
  toast.error("Please add at least one field to the form");
4526
4538
  return;
@@ -4593,7 +4605,11 @@ function FormBuilderViewInner({
4593
4605
  collectionSlug,
4594
4606
  entryId,
4595
4607
  queryClient,
4596
- markAsSaved
4608
+ markAsSaved,
4609
+ // Read inside, so it belongs here. Without it the callback closes over the
4610
+ // claim as it stood when the form mounted, and a colleague taking the
4611
+ // document over mid-session would leave this still committing.
4612
+ documentLock?.actionsDisabled
4597
4613
  ]);
4598
4614
  const handleCancel = useCallback7(() => {
4599
4615
  if (onCancel) onCancel();
@@ -4778,7 +4794,7 @@ function FormBuilderViewInner({
4778
4794
  onClick: () => {
4779
4795
  void handleSave();
4780
4796
  },
4781
- disabled: isSaving,
4797
+ disabled: isSaving || documentLock?.actionsDisabled === true,
4782
4798
  className: "flex items-center gap-1.5",
4783
4799
  children: isSaving ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
4784
4800
  /* @__PURE__ */ jsxs11(
@@ -4831,7 +4847,8 @@ function FormBuilderView({
4831
4847
  initialData,
4832
4848
  onSave,
4833
4849
  onSuccess,
4834
- onCancel
4850
+ onCancel,
4851
+ documentLock
4835
4852
  }) {
4836
4853
  const resolvedEntryId = entryId || id || initialData?.id;
4837
4854
  const resolvedCollectionSlug = collectionSlug || collection || "forms";
@@ -4840,6 +4857,7 @@ function FormBuilderView({
4840
4857
  return /* @__PURE__ */ jsx12(
4841
4858
  FormBuilderProvider,
4842
4859
  {
4860
+ readOnly: documentLock?.readOnly === true,
4843
4861
  initialData: {
4844
4862
  id: resolvedEntryId,
4845
4863
  name: initialData?.name,
@@ -4859,7 +4877,8 @@ function FormBuilderView({
4859
4877
  entryId: resolvedEntryId,
4860
4878
  onSave,
4861
4879
  onSuccess,
4862
- onCancel
4880
+ onCancel,
4881
+ documentLock
4863
4882
  }
4864
4883
  )
4865
4884
  },