@petrarca/sonnet-schema-designer 0.6.0 → 0.8.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.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @petrarca/sonnet-schema-designer
2
+
3
+ A visual editor for JSON Schema, part of the Petrarca Sonnet component library.
4
+ One component, `SchemaDesigner`, edits a schema in place. Presentation hints
5
+ travel beside it in a UI Schema map, or embedded on the schema when you ask for
6
+ that shape.
7
+
8
+ ## What it provides
9
+
10
+ **Three panes.** A property tree on the left for structure -- select, add,
11
+ delete, reorder, rename, with a collapsible region for `$defs`. A preview in
12
+ the centre with tabs over the live form, the form layout designer, a table and
13
+ the JSON source. An inspector on the right with Data, Enum, UI, Rule and Table
14
+ editors for the selected node. Both side panes resize and collapse.
15
+
16
+ **Schema authoring.** Types and validation, nesting, `oneOf` variants,
17
+ conditionals, definitions and references. Local `#/$defs/...` and
18
+ `#/definitions/...` references resolve on their own; anything else goes through
19
+ a synchronous `refResolver` and expands read-only in the tree.
20
+
21
+ **Presentation authoring.** Widgets and their options, table hints, visibility
22
+ rules and form layout, in the same `x-ui-*` vocabulary
23
+ `JsonSchemaFormRenderer` reads. `embedUiSchema` merges the top-level form hints
24
+ onto the schema when a single self-contained artifact is wanted.
25
+
26
+ **Capability gating.** A profile restricts what a user may author -- a
27
+ data-only profile, a fixed schema with editable areas, or a locked view --
28
+ validated by `resolveCapabilities`, `validateCapabilities` and
29
+ `capabilitiesSchema`.
30
+
31
+ **The pieces behind the shell.** `useDesignerState` holds the whole model and
32
+ every mutation; `PropertyTree`, `Inspector`, `PreviewPane`,
33
+ `FormLayoutDesigner` and `FormSettingsSheet` each take that state. A real seam,
34
+ though the shell is the supported way to use them.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pnpm add @petrarca/sonnet-schema-designer
40
+ ```
41
+
42
+ `@petrarca/sonnet-core`, `@petrarca/sonnet-ui` and `@petrarca/sonnet-forms` are
43
+ regular dependencies and arrive with it, as do `lucide-react` and Zod.
44
+
45
+ | Peer | Range | Needed for |
46
+ |---|---|---|
47
+ | `react`, `react-dom` | `>=19` | Everything |
48
+ | `tailwindcss` | `>=3` | The styles the components emit |
49
+
50
+ It renders through `sonnet-ui`'s stylesheet and Tailwind preset, so set those
51
+ up first -- see
52
+ [sonnet-ui: getting started](../sonnet-ui/docs/usage/getting-started.md).
53
+
54
+ ## Usage
55
+
56
+ ```tsx
57
+ import { SchemaDesigner } from "@petrarca/sonnet-schema-designer";
58
+ import type { JsonSchema, UISchema } from "@petrarca/sonnet-core/schema";
59
+
60
+ const [schema, setSchema] = useState<JsonSchema>(initial);
61
+ const [uiSchema, setUiSchema] = useState<Record<string, UISchema>>({});
62
+
63
+ <SchemaDesigner
64
+ schema={schema}
65
+ uiSchema={uiSchema}
66
+ onChange={(nextSchema, nextUiSchema) => {
67
+ setSchema(nextSchema);
68
+ setUiSchema(nextUiSchema);
69
+ }}
70
+ />;
71
+ ```
72
+
73
+ All nine props are optional -- `schema`, `uiSchema`, `initialSchema`,
74
+ `initialUiSchema`, `onChange`, `readOnly`, `capabilities`, `widgetOptions` and
75
+ `refResolver`. The mode is decided by whether `schema` is `undefined`: omit it
76
+ and seed with `initialSchema` for uncontrolled use, where the designer keeps
77
+ the model internally and still reports every edit through `onChange`. With
78
+ neither, it starts from an empty object schema.
79
+
80
+ The root element is `w-full h-full`, so the parent must have a resolved height.
81
+
82
+ ## Documentation
83
+
84
+ - [Usage guides](docs/usage/index.md) -- getting started,
85
+ [authoring schemas](docs/usage/authoring-schemas.md),
86
+ [UI hints](docs/usage/ui-hints.md) and
87
+ [capabilities](docs/usage/capabilities.md).
88
+ - [Design docs](docs/design/index.md) -- the designer's structure, logic
89
+ authoring, and references and definitions.
90
+ - [The UISchema vocabulary](../sonnet-forms/docs/usage/ui-schema.md) -- what the
91
+ `x-ui-*` keys mean to the form renderer.
92
+ - [Workspace documentation](../../docs/README.md) -- how the packages layer and
93
+ the architecture decision records.
94
+
95
+ ## License
96
+
97
+ Apache 2.0. See [LICENSE.md](../../LICENSE.md).
package/dist/index.d.ts CHANGED
@@ -443,7 +443,8 @@ declare function embedUiSchema(schema: JsonSchema, uiSchema: Record<string, UISc
443
443
  * fully-locked shorthand). Every field is optional and defaults to the most
444
444
  * permissive value, so adding a capability never tightens an existing profile.
445
445
  *
446
- * Design: docs/design/schema-designer.md, section "Capability gating".
446
+ * Design: docs/design/schema-designer.md in this package; usage in
447
+ * docs/usage/capabilities.md.
447
448
  */
448
449
 
449
450
  /**
@@ -451,11 +452,17 @@ declare function embedUiSchema(schema: JsonSchema, uiSchema: Record<string, UISc
451
452
  * schema; "capabilities" is a meta-editor for the profile itself (a dev /
452
453
  * power-user affordance), not a preview -- it is opt-in and never in a default.
453
454
  */
454
- type PreviewKind = "form" | "table" | "json" | "capabilities";
455
+ declare const previewKindSchema: z.ZodEnum<{
456
+ form: "form";
457
+ table: "table";
458
+ json: "json";
459
+ capabilities: "capabilities";
460
+ }>;
461
+ type PreviewKind = z.infer<typeof previewKindSchema>;
455
462
  /**
456
463
  * The capabilities schema. Small by design -- it covers the real consumer needs
457
- * (sestina's audit-event and terminology schema editors) without a per-area
458
- * matrix we do not use yet.
464
+ * (locked-down, single-schema editors embedded in an application) without a
465
+ * per-area matrix we do not use yet.
459
466
  */
460
467
  declare const capabilitiesSchema: z.ZodObject<{
461
468
  mode: z.ZodDefault<z.ZodEnum<{
@@ -475,13 +482,15 @@ declare const capabilitiesSchema: z.ZodObject<{
475
482
  * fields optional on input (defaults are applied by resolveCapabilities).
476
483
  */
477
484
  type SchemaDesignerCapabilities = z.input<typeof capabilitiesSchema>;
485
+ /**
486
+ * The schema's own fields once resolved. Derived from the schema rather than
487
+ * restated, so adding or retyping a capability cannot silently drift from it.
488
+ * `Required` because resolveCapabilities defaults every optional field --
489
+ * `previews` by mode, `editablePaths` to empty -- so none is absent downstream.
490
+ */
491
+ type ResolvedSchemaFields = Required<z.output<typeof capabilitiesSchema>>;
478
492
  /** The fully-resolved capabilities, plus the derived flags used for gating. */
479
- interface ResolvedCapabilities {
480
- mode: "data" | "full";
481
- /** Effective preview list (defaulted by mode when not provided). */
482
- previews: PreviewKind[];
483
- /** JSON Pointer paths whose children are editable; empty = no scoping. */
484
- editablePaths: string[];
493
+ interface ResolvedCapabilities extends ResolvedSchemaFields {
485
494
  /** No structural edits at all (readOnly shorthand). */
486
495
  locked: boolean;
487
496
  /** editablePaths is non-empty -- only designated subtrees are editable. */
@@ -829,4 +838,4 @@ interface FormSettingsSheetProps {
829
838
  }
830
839
  declare function FormSettingsSheet({ open, onOpenChange, state, }: FormSettingsSheetProps): React.ReactElement;
831
840
 
832
- export { type CapabilitiesValidation, type ChildSlot, type ConditionCombinator, DataTab, type DesignerModel, type DesignerState, type EnumChoice, EnumEditor, FormLayoutDesigner, FormSettingsSheet, Inspector, type LayoutScope, type NewPropertyKind, type PathSegment, type PathSlot, PreviewPane, type PropertyKind, type PropertyNode, type PropertyPath, PropertyTree, type PropertyTypeChoice, type ResolvedCapabilities, SchemaDesigner, type SchemaDesignerCapabilities, type SchemaDesignerProps, type SlotKind, type TableHints, TableHintsTab, TablePreview, UITab, type UiSchemaMap, type UseDesignerStateArgs, type WidgetChoice, addBranch, addConditional, addDefinition, addElse, addLayoutGroup, addProperty, branch, buildPropertyTree, capabilitiesSchema, changePropertyType, childContainer, childSlots, compatibleWidgets, condition, def, definitionUsageCount, definitionsOf, deleteProperty, embedUiSchema, getProperty, getScopedLayout, getUiEntryAtPath, isContainer, isReservedKey, layoutScopes, moveBranch, moveLayoutGroup, moveProperty, newBranch, newCondition, newProperty, orderedKeys, parsePathKey, pathKey, placeFieldInGroup, prop, propertyKind, readEnumChoices, readTableHints, removeBranch, removeConditional, removeDefinition, removeElse, removeLayoutGroup, renameDefinition, renameLayoutGroup, renameProperty, resolveCapabilities, samePath, sameSegment, sampleRows, schemaAsRootProp, setCondition, setConditionalBehavior, setEnum, setGlobalOptions, setLayoutGroupRule, setPropertyLayout, setReference, setRequired, setRootLayout, setRootLayoutRows, setScopedGroupRows, setScopedLayoutGroups, setScopedLayoutMode, setScopedLayoutOptions, setScopedLayoutRows, setScopedLayoutVariant, setTableHints, setUiEntry, slot, updateProperty, useDesignerState, validateCapabilities };
841
+ export { type CapabilitiesValidation, type ChildSlot, type ConditionCombinator, DataTab, type DesignerModel, type DesignerState, type EnumChoice, EnumEditor, FormLayoutDesigner, FormSettingsSheet, Inspector, type LayoutScope, type NewPropertyKind, type PathSegment, type PathSlot, type PreviewKind, PreviewPane, type PropertyKind, type PropertyNode, type PropertyPath, PropertyTree, type PropertyTypeChoice, type ResolvedCapabilities, SchemaDesigner, type SchemaDesignerCapabilities, type SchemaDesignerProps, type SlotKind, type TableHints, TableHintsTab, TablePreview, UITab, type UiSchemaMap, type UseDesignerStateArgs, type WidgetChoice, addBranch, addConditional, addDefinition, addElse, addLayoutGroup, addProperty, branch, buildPropertyTree, capabilitiesSchema, changePropertyType, childContainer, childSlots, compatibleWidgets, condition, def, definitionUsageCount, definitionsOf, deleteProperty, embedUiSchema, getProperty, getScopedLayout, getUiEntryAtPath, isContainer, isReservedKey, layoutScopes, moveBranch, moveLayoutGroup, moveProperty, newBranch, newCondition, newProperty, orderedKeys, parsePathKey, pathKey, placeFieldInGroup, prop, propertyKind, readEnumChoices, readTableHints, removeBranch, removeConditional, removeDefinition, removeElse, removeLayoutGroup, renameDefinition, renameLayoutGroup, renameProperty, resolveCapabilities, samePath, sameSegment, sampleRows, schemaAsRootProp, setCondition, setConditionalBehavior, setEnum, setGlobalOptions, setLayoutGroupRule, setPropertyLayout, setReference, setRequired, setRootLayout, setRootLayoutRows, setScopedGroupRows, setScopedLayoutGroups, setScopedLayoutMode, setScopedLayoutOptions, setScopedLayoutRows, setScopedLayoutVariant, setTableHints, setUiEntry, slot, updateProperty, useDesignerState, validateCapabilities };
package/dist/index.js CHANGED
@@ -1261,11 +1261,12 @@ import { useCallback, useMemo, useRef, useState } from "react";
1261
1261
  // src/capabilities.ts
1262
1262
  import { z } from "zod";
1263
1263
  import { warnLog } from "@petrarca/sonnet-core";
1264
+ var previewKindSchema = z.enum(["form", "table", "json", "capabilities"]);
1264
1265
  var capabilitiesSchema = z.object({
1265
1266
  mode: z.enum(["data", "full"]).default("full").meta({
1266
1267
  description: "What the designer authors. 'full' (default): JSON Schema + uiSchema (form presentation hints). 'data': JSON Schema only -- the UI Schema tab, Combined tab, and the inspector's per-field UI tab are hidden (pure data schemas: audit events, terminology)."
1267
1268
  }),
1268
- previews: z.array(z.enum(["form", "table", "json", "capabilities"])).optional().meta({
1269
+ previews: z.array(previewKindSchema).optional().meta({
1269
1270
  description: "Which tabs the center pane offers, in order. 'form'/'table'/'json' preview the schema; 'capabilities' is a JSON editor for this profile (opt-in, never in a default). When omitted, defaults to ['form','table','json'] in 'full' mode and ['json'] in 'data' mode. An empty list hides the center pane entirely."
1270
1271
  }),
1271
1272
  editablePaths: z.array(z.string()).optional().meta({
@@ -4730,11 +4731,36 @@ function ConditionEditor({
4730
4731
 
4731
4732
  // src/inspector/RuleEditor.tsx
4732
4733
  import { useMemo as useMemo12 } from "react";
4733
- import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
4734
- function siblingPointers(state, node) {
4735
- const parentProps = node.path.length <= 1 ? state.schema.properties : findParentProperties(state, node);
4736
- const self = node.key;
4737
- return Object.entries(parentProps ?? {}).filter(([k]) => k !== self).map(([k, prop2]) => ({ pointer: `/${k}`, type: prop2.type }));
4734
+
4735
+ // src/inspector/ruleScopes.ts
4736
+ function dataPointerPrefix(node) {
4737
+ let prefix = "";
4738
+ for (let i = 0; i < node.path.length - 1; i++) {
4739
+ const seg = node.path[i];
4740
+ if (seg.kind !== "prop") return void 0;
4741
+ prefix += `/${seg.key}`;
4742
+ }
4743
+ return prefix;
4744
+ }
4745
+ function scopePointers(state, node) {
4746
+ const options = /* @__PURE__ */ new Map();
4747
+ const prefix = dataPointerPrefix(node);
4748
+ const selfPointer = prefix === void 0 ? void 0 : `${prefix}/${node.key}`;
4749
+ const add = (pointer, prop2) => {
4750
+ if (pointer === selfPointer) return;
4751
+ if (!options.has(pointer))
4752
+ options.set(pointer, { pointer, type: prop2.type });
4753
+ };
4754
+ for (const [k, prop2] of Object.entries(state.schema.properties ?? {})) {
4755
+ add(`/${k}`, prop2);
4756
+ }
4757
+ if (prefix) {
4758
+ const siblings = findParentProperties(state, node);
4759
+ for (const [k, prop2] of Object.entries(siblings ?? {})) {
4760
+ add(`${prefix}/${k}`, prop2);
4761
+ }
4762
+ }
4763
+ return [...options.values()];
4738
4764
  }
4739
4765
  function findParentProperties(state, node) {
4740
4766
  let props = state.schema.properties;
@@ -4747,11 +4773,14 @@ function findParentProperties(state, node) {
4747
4773
  }
4748
4774
  return props;
4749
4775
  }
4776
+
4777
+ // src/inspector/RuleEditor.tsx
4778
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
4750
4779
  function RuleEditor({
4751
4780
  node,
4752
4781
  state
4753
4782
  }) {
4754
- const scopes = useMemo12(() => siblingPointers(state, node), [state, node]);
4783
+ const scopes = useMemo12(() => scopePointers(state, node), [state, node]);
4755
4784
  return /* @__PURE__ */ jsxs13("div", { className: "space-y-3", children: [
4756
4785
  /* @__PURE__ */ jsx15("p", { className: "text-xs text-muted-foreground", children: "Toggle this field's visibility (or enabled state) based on another field's value. The field keeps its position in the form." }),
4757
4786
  /* @__PURE__ */ jsx15(