@duskmoon-dev/components 0.4.0 → 0.4.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.
package/README.md CHANGED
@@ -41,7 +41,7 @@ import { Button, DmTable, theme } from "@duskmoon-dev/components";
41
41
 
42
42
  ## Public Surface
43
43
 
44
- - 78 standard components such as `Button`, `Chat`, `OtpInput`, `Swap`, `Fab`,
44
+ - 79 standard components such as `Button`, `Chat`, `OtpInput`, `Swap`, `Fab`,
45
45
  `Megamenu`, `ConsolePage`, `Table`, `Modal`, `Select`, and `Typography`.
46
46
  - 21 DuskMoon workflow components such as `DmLayout`, `DmSearch`, `DmTable`,
47
47
  `DmProTable`, and `DmToolbar`.
@@ -90,10 +90,14 @@ export function Conversation() {
90
90
  <Chat.Bubble>The build passed.</Chat.Bubble>
91
91
  <Chat.Status>
92
92
  <Chat.StatusItem>Completed</Chat.StatusItem>
93
- <Chat.StatusItem><Chat.StatusValue>42</Chat.StatusValue> token/s</Chat.StatusItem>
93
+ <Chat.StatusItem>
94
+ <Chat.StatusValue>42</Chat.StatusValue> token/s
95
+ </Chat.StatusItem>
94
96
  </Chat.Status>
95
97
  <Chat.Actions hover>
96
- <button type="button" onClick={copyReply}>Copy reply</button>
98
+ <button type="button" onClick={copyReply}>
99
+ Copy reply
100
+ </button>
97
101
  </Chat.Actions>
98
102
  </Chat>
99
103
  </Chat.ScrollBody>
@@ -124,3 +128,80 @@ bun run typecheck
124
128
  # Check parity manifest coverage
125
129
  bun run parity:components
126
130
  ```
131
+
132
+ ## JSON Schema forms
133
+
134
+ `@duskmoon-dev/components/json-schema-form` exports `JsonSchemaForm`,
135
+ `compileForm`, `JsonSchemaWidget`, and the `RenderableSchema`, `CompiledForm`,
136
+ `FormValue`, `JsonValue`, `FormErrors`, `FormValidationResult`,
137
+ `JsonSchemaFormProps`, `JsonSchemaWidgetProps`, and widget contract types.
138
+ Ajv and ajv-formats are installed as package runtime dependencies. The package
139
+ stylesheet includes the renderer layout. Import these APIs from the explicit
140
+ subpath; schema compilation is kept separate from the root component bundle.
141
+
142
+ ```tsx
143
+ import { useState } from "react";
144
+ import "@duskmoon-dev/components/styles.css";
145
+ import {
146
+ JsonSchemaForm,
147
+ type FormErrors,
148
+ type FormValue,
149
+ type RenderableSchema,
150
+ } from "@duskmoon-dev/components/json-schema-form";
151
+
152
+ const schema: RenderableSchema = {
153
+ type: "object",
154
+ required: ["name"],
155
+ properties: {
156
+ name: { type: "string", title: "Name", minLength: 1 },
157
+ age: { type: "integer", title: "Age", minimum: 0 },
158
+ },
159
+ };
160
+
161
+ export function ProfileIsland({ send }: { send: (value: FormValue) => void }) {
162
+ const [value, setValue] = useState<FormValue>({ name: "Ada" });
163
+ const [errors, setErrors] = useState<FormErrors>({});
164
+ return (
165
+ <JsonSchemaForm
166
+ schema={schema}
167
+ value={value}
168
+ onChange={setValue}
169
+ errors={errors}
170
+ onErrorsChange={setErrors}
171
+ onSubmit={send}
172
+ />
173
+ );
174
+ }
175
+ ```
176
+
177
+ Mount the React root inside a LiveView element with a stable `id` and
178
+ `phx-update="ignore"`. Parse the backend's schema JSON and call `compileForm`
179
+ when accepting untrusted input; catch compilation errors before rendering.
180
+ Pass the returned object as `compiled` instead of `schema` to compile once.
181
+ Send the validated object from `onSubmit` through the island's LiveView hook;
182
+ backend validation is still required. Use a new React `key` when replacing a
183
+ schema and starting a fresh form.
184
+
185
+ `value` and `onChange` control values. Omit `value` for internal state, optionally
186
+ seeded by `defaultValue`. Reset restores `defaultValue` or schema defaults,
187
+ notifies `onChange`, clears validation errors, and calls `onReset`. Backend
188
+ `errors` merge with local errors and use escaped JSON Pointer keys such as
189
+ `/address/email` (the empty key denotes a form error). Clear backend errors in
190
+ `onErrorsChange`, which receives an empty object on edits/reset and validation
191
+ errors on submission. `onSubmit` runs only for valid values and receives a
192
+ copied JSON object with numbers and booleans preserved.
193
+
194
+ The supported schema is a strict JSON Schema draft 2020-12 subset: object roots,
195
+ nested objects, homogeneous arrays, primitive fields/enums/defaults, required
196
+ fields, `additionalProperties: false`, string constraints/formats, numeric
197
+ constraints, and array/object size constraints. References, combinators,
198
+ nullable types, and arbitrary keywords are rejected. Validation does not coerce,
199
+ remove, or default submitted values.
200
+
201
+ Use `x-widget` for explicit component selection, `x-options` for typed option
202
+ labels/hierarchies, and `x-widget-options` for orientation, length, count,
203
+ allowHalf, min, max, and step where supported. `widgetNames`, `WidgetName`,
204
+ `WidgetOption`, `WidgetConfig`, and `OrientationWidgetConfig` expose this
205
+ contract; `compileForm` checks widget/type/config compatibility. Upload widgets
206
+ submit JSON file metadata (`name`, `size`, `type`, `lastModified`), with file
207
+ transfer owned by the application.
@@ -2142,11 +2142,17 @@ var OptGroup = forwardRef6(({ children, ...props }, ref) => /* @__PURE__ */ jsx8
2142
2142
  OptGroup.displayName = "Select.OptGroup";
2143
2143
  var SelectRoot = forwardRef6(({
2144
2144
  allowClear,
2145
+ "aria-describedby": ariaDescribedBy,
2146
+ "aria-errormessage": ariaErrorMessage,
2147
+ "aria-invalid": ariaInvalid,
2148
+ "aria-label": ariaLabel,
2149
+ "aria-labelledby": ariaLabelledBy,
2145
2150
  children,
2146
2151
  className,
2147
2152
  defaultValue,
2148
2153
  disabled,
2149
2154
  filterOption,
2155
+ id,
2150
2156
  loading,
2151
2157
  mode,
2152
2158
  onChange,
@@ -2269,8 +2275,14 @@ var SelectRoot = forwardRef6(({
2269
2275
  children: [
2270
2276
  /* @__PURE__ */ jsxs7("button", {
2271
2277
  type: "button",
2278
+ id,
2272
2279
  className: getSelectClasses({ size, status, disabled }),
2273
2280
  disabled: disabled || loading,
2281
+ "aria-describedby": ariaDescribedBy,
2282
+ "aria-errormessage": ariaErrorMessage,
2283
+ "aria-invalid": ariaInvalid,
2284
+ "aria-label": ariaLabel,
2285
+ "aria-labelledby": ariaLabelledBy,
2274
2286
  "aria-haspopup": "listbox",
2275
2287
  "aria-expanded": open,
2276
2288
  onClick: () => setOpen((current) => !current),
@@ -1640,11 +1640,17 @@ var OptGroup = forwardRef4(({ children, ...props }, ref) => /* @__PURE__ */ jsx5
1640
1640
  OptGroup.displayName = "Select.OptGroup";
1641
1641
  var SelectRoot = forwardRef4(({
1642
1642
  allowClear,
1643
+ "aria-describedby": ariaDescribedBy,
1644
+ "aria-errormessage": ariaErrorMessage,
1645
+ "aria-invalid": ariaInvalid,
1646
+ "aria-label": ariaLabel,
1647
+ "aria-labelledby": ariaLabelledBy,
1643
1648
  children,
1644
1649
  className,
1645
1650
  defaultValue,
1646
1651
  disabled,
1647
1652
  filterOption,
1653
+ id,
1648
1654
  loading,
1649
1655
  mode,
1650
1656
  onChange,
@@ -1767,8 +1773,14 @@ var SelectRoot = forwardRef4(({
1767
1773
  children: [
1768
1774
  /* @__PURE__ */ jsxs5("button", {
1769
1775
  type: "button",
1776
+ id,
1770
1777
  className: getSelectClasses({ size, status, disabled }),
1771
1778
  disabled: disabled || loading,
1779
+ "aria-describedby": ariaDescribedBy,
1780
+ "aria-errormessage": ariaErrorMessage,
1781
+ "aria-invalid": ariaInvalid,
1782
+ "aria-label": ariaLabel,
1783
+ "aria-labelledby": ariaLabelledBy,
1772
1784
  "aria-haspopup": "listbox",
1773
1785
  "aria-expanded": open,
1774
1786
  onClick: () => setOpen((current) => !current),
@@ -1632,11 +1632,17 @@ var OptGroup = forwardRef4(({ children, ...props }, ref) => /* @__PURE__ */ jsx5
1632
1632
  OptGroup.displayName = "Select.OptGroup";
1633
1633
  var SelectRoot = forwardRef4(({
1634
1634
  allowClear,
1635
+ "aria-describedby": ariaDescribedBy,
1636
+ "aria-errormessage": ariaErrorMessage,
1637
+ "aria-invalid": ariaInvalid,
1638
+ "aria-label": ariaLabel,
1639
+ "aria-labelledby": ariaLabelledBy,
1635
1640
  children,
1636
1641
  className,
1637
1642
  defaultValue,
1638
1643
  disabled,
1639
1644
  filterOption,
1645
+ id,
1640
1646
  loading,
1641
1647
  mode,
1642
1648
  onChange,
@@ -1759,8 +1765,14 @@ var SelectRoot = forwardRef4(({
1759
1765
  children: [
1760
1766
  /* @__PURE__ */ jsxs5("button", {
1761
1767
  type: "button",
1768
+ id,
1762
1769
  className: getSelectClasses({ size, status, disabled }),
1763
1770
  disabled: disabled || loading,
1771
+ "aria-describedby": ariaDescribedBy,
1772
+ "aria-errormessage": ariaErrorMessage,
1773
+ "aria-invalid": ariaInvalid,
1774
+ "aria-label": ariaLabel,
1775
+ "aria-labelledby": ariaLabelledBy,
1764
1776
  "aria-haspopup": "listbox",
1765
1777
  "aria-expanded": open,
1766
1778
  onClick: () => setOpen((current) => !current),
@@ -2132,11 +2132,17 @@ var OptGroup = forwardRef6(({ children, ...props }, ref) => /* @__PURE__ */ jsx8
2132
2132
  OptGroup.displayName = "Select.OptGroup";
2133
2133
  var SelectRoot = forwardRef6(({
2134
2134
  allowClear,
2135
+ "aria-describedby": ariaDescribedBy,
2136
+ "aria-errormessage": ariaErrorMessage,
2137
+ "aria-invalid": ariaInvalid,
2138
+ "aria-label": ariaLabel,
2139
+ "aria-labelledby": ariaLabelledBy,
2135
2140
  children,
2136
2141
  className,
2137
2142
  defaultValue,
2138
2143
  disabled,
2139
2144
  filterOption,
2145
+ id,
2140
2146
  loading,
2141
2147
  mode,
2142
2148
  onChange,
@@ -2259,8 +2265,14 @@ var SelectRoot = forwardRef6(({
2259
2265
  children: [
2260
2266
  /* @__PURE__ */ jsxs7("button", {
2261
2267
  type: "button",
2268
+ id,
2262
2269
  className: getSelectClasses({ size, status, disabled }),
2263
2270
  disabled: disabled || loading,
2271
+ "aria-describedby": ariaDescribedBy,
2272
+ "aria-errormessage": ariaErrorMessage,
2273
+ "aria-invalid": ariaInvalid,
2274
+ "aria-label": ariaLabel,
2275
+ "aria-labelledby": ariaLabelledBy,
2264
2276
  "aria-haspopup": "listbox",
2265
2277
  "aria-expanded": open,
2266
2278
  onClick: () => setOpen((current) => !current),
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import type { CompiledForm, FormErrors, RenderableSchema, FormValue } from "./json-schema-form";
3
+ export type JsonSchemaFormProps = {
4
+ value?: FormValue;
5
+ defaultValue?: FormValue;
6
+ onChange?: (value: FormValue) => void;
7
+ /** Backend errors keyed by JSON Pointer; clear these in onErrorsChange. */
8
+ errors?: FormErrors;
9
+ onErrorsChange?: (errors: FormErrors) => void;
10
+ onSubmit: (value: FormValue) => void;
11
+ onReset?: () => void;
12
+ } & ({
13
+ compiled: CompiledForm;
14
+ schema?: never;
15
+ } | {
16
+ schema: RenderableSchema;
17
+ compiled?: never;
18
+ });
19
+ export declare function JsonSchemaForm({ compiled: suppliedCompiled, schema, value: controlledValue, defaultValue, onChange, errors: suppliedErrors, onErrorsChange, onSubmit, onReset, }: JsonSchemaFormProps): React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import type { JsonValue, RenderableSchema } from "./json-schema-form";
3
+ export type JsonSchemaWidgetProps = {
4
+ schema: RenderableSchema;
5
+ value: JsonValue | undefined;
6
+ onChange: (value: JsonValue | undefined) => void;
7
+ id: string;
8
+ label: string;
9
+ path: string;
10
+ describedBy?: string;
11
+ invalid: boolean;
12
+ };
13
+ export declare const specializedWidgets: Set<string>;
14
+ export declare function JsonSchemaWidget({ schema, value, onChange, id, label, path, describedBy, invalid, }: JsonSchemaWidgetProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ export { JsonSchemaForm } from "./JsonSchemaForm";
2
+ export type { JsonSchemaFormProps } from "./JsonSchemaForm";
3
+ export { JsonSchemaWidget, specializedWidgets } from "./JsonSchemaWidget";
4
+ export type { JsonSchemaWidgetProps } from "./JsonSchemaWidget";
5
+ export { compileForm } from "./json-schema-form";
6
+ export type { CompiledForm, FormErrors, FormValidationResult, FormValue, JsonValue, RenderableSchema, } from "./json-schema-form";
7
+ export { flattenOptions, inspectWidgets, naturalWidgetValue, optionsFor, widgetConfig, widgetNames, widgetValueError, } from "./json-form-widgets";
8
+ export type { OrientationWidgetConfig, WidgetConfig, WidgetName, WidgetOption, } from "./json-form-widgets";