@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 +97 -0
- package/dist/index.d.ts +20 -11
- package/dist/index.js +36 -7
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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
|
|
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
|
-
|
|
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
|
-
* (
|
|
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(
|
|
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
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
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(() =>
|
|
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(
|