@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.
@@ -0,0 +1,26 @@
1
+ import type { JsonValue, RenderableSchema } from "./json-schema-form";
2
+ export declare const widgetNames: readonly ["input", "input-number", "checkbox", "radio", "radio-group", "checkbox-group", "color-picker", "date-picker", "dm-date-picker", "mentions", "otp-input", "rate", "segmented", "select", "slider", "switch", "time-picker", "transfer", "tree-select", "cascader", "upload"];
3
+ export type WidgetName = (typeof widgetNames)[number];
4
+ export type WidgetOption = {
5
+ value: string | number | boolean;
6
+ label: string;
7
+ children?: WidgetOption[];
8
+ disabled?: boolean;
9
+ };
10
+ export type OrientationWidgetConfig = {
11
+ orientation?: "horizontal" | "vertical";
12
+ };
13
+ export type WidgetConfig = OrientationWidgetConfig & {
14
+ length?: number;
15
+ count?: number;
16
+ allowHalf?: boolean;
17
+ min?: number;
18
+ max?: number;
19
+ step?: number;
20
+ };
21
+ export declare const flattenOptions: (options: WidgetOption[]) => WidgetOption[];
22
+ export declare function widgetConfig(schema: RenderableSchema): WidgetConfig;
23
+ export declare function naturalWidgetValue(schema: RenderableSchema): JsonValue | undefined;
24
+ export declare function optionsFor(schema: RenderableSchema): WidgetOption[];
25
+ export declare function inspectWidgets(input: Record<string, unknown>, location: string): void;
26
+ export declare function widgetValueError(schema: RenderableSchema, value: JsonValue): string | undefined;
@@ -0,0 +1,50 @@
1
+ import { type WidgetName, type WidgetOption, type WidgetConfig } from "./json-form-widgets";
2
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type FormValue = {
6
+ [key: string]: JsonValue;
7
+ };
8
+ export type RenderableSchema = {
9
+ type: "object" | "array" | "string" | "number" | "integer" | "boolean";
10
+ $schema?: "https://json-schema.org/draft/2020-12/schema";
11
+ $id?: string;
12
+ $comment?: string;
13
+ title?: string;
14
+ description?: string;
15
+ default?: JsonValue;
16
+ enum?: JsonValue[];
17
+ properties?: Record<string, RenderableSchema>;
18
+ required?: string[];
19
+ items?: RenderableSchema;
20
+ additionalProperties?: false;
21
+ minLength?: number;
22
+ maxLength?: number;
23
+ pattern?: string;
24
+ format?: string;
25
+ minItems?: number;
26
+ maxItems?: number;
27
+ uniqueItems?: boolean;
28
+ minProperties?: number;
29
+ maxProperties?: number;
30
+ exclusiveMinimum?: number;
31
+ exclusiveMaximum?: number;
32
+ minimum?: number;
33
+ maximum?: number;
34
+ multipleOf?: number;
35
+ "x-widget"?: WidgetName;
36
+ "x-options"?: WidgetOption[];
37
+ "x-widget-options"?: WidgetConfig;
38
+ };
39
+ /** Errors keyed by escaped JSON Pointer, with an empty key for form errors. */
40
+ export type FormErrors = Record<string, string>;
41
+ export type FormValidationResult = {
42
+ valid: boolean;
43
+ errors: FormErrors;
44
+ };
45
+ export type CompiledForm = {
46
+ schema: RenderableSchema;
47
+ initialValue: () => FormValue;
48
+ validate: (value: FormValue) => FormValidationResult;
49
+ };
50
+ export declare function compileForm(input: unknown): CompiledForm;
@@ -172,11 +172,17 @@ var OptGroup = forwardRef(({ children, ...props }, ref) => /* @__PURE__ */ jsx("
172
172
  OptGroup.displayName = "Select.OptGroup";
173
173
  var SelectRoot = forwardRef(({
174
174
  allowClear,
175
+ "aria-describedby": ariaDescribedBy,
176
+ "aria-errormessage": ariaErrorMessage,
177
+ "aria-invalid": ariaInvalid,
178
+ "aria-label": ariaLabel,
179
+ "aria-labelledby": ariaLabelledBy,
175
180
  children,
176
181
  className,
177
182
  defaultValue,
178
183
  disabled,
179
184
  filterOption,
185
+ id,
180
186
  loading,
181
187
  mode,
182
188
  onChange,
@@ -299,8 +305,14 @@ var SelectRoot = forwardRef(({
299
305
  children: [
300
306
  /* @__PURE__ */ jsxs("button", {
301
307
  type: "button",
308
+ id,
302
309
  className: getSelectClasses({ size, status, disabled }),
303
310
  disabled: disabled || loading,
311
+ "aria-describedby": ariaDescribedBy,
312
+ "aria-errormessage": ariaErrorMessage,
313
+ "aria-invalid": ariaInvalid,
314
+ "aria-label": ariaLabel,
315
+ "aria-labelledby": ariaLabelledBy,
304
316
  "aria-haspopup": "listbox",
305
317
  "aria-expanded": open,
306
318
  onClick: () => setOpen((current) => !current),
@@ -1 +1 @@
1
- export declare const version = "0.4.0";
1
+ export declare const version = "0.4.1";
package/dist/index.js CHANGED
@@ -107,7 +107,7 @@ function duskmoonMerge(classes) {
107
107
  import { useCallback as useCallback2, useState as useState2 } from "react";
108
108
 
109
109
  // src/generated/version.ts
110
- var version = "0.4.0";
110
+ var version = "0.4.1";
111
111
 
112
112
  // src/infrastructure.ts
113
113
  var theme = {
@@ -8535,11 +8535,17 @@ var OptGroup = forwardRef37(({ children, ...props }, ref) => /* @__PURE__ */ jsx
8535
8535
  OptGroup.displayName = "Select.OptGroup";
8536
8536
  var SelectRoot = forwardRef37(({
8537
8537
  allowClear,
8538
+ "aria-describedby": ariaDescribedBy,
8539
+ "aria-errormessage": ariaErrorMessage,
8540
+ "aria-invalid": ariaInvalid,
8541
+ "aria-label": ariaLabel,
8542
+ "aria-labelledby": ariaLabelledBy,
8538
8543
  children,
8539
8544
  className,
8540
8545
  defaultValue,
8541
8546
  disabled,
8542
8547
  filterOption,
8548
+ id,
8543
8549
  loading,
8544
8550
  mode,
8545
8551
  onChange,
@@ -8662,8 +8668,14 @@ var SelectRoot = forwardRef37(({
8662
8668
  children: [
8663
8669
  /* @__PURE__ */ jsxs33("button", {
8664
8670
  type: "button",
8671
+ id,
8665
8672
  className: getSelectClasses({ size, status, disabled }),
8666
8673
  disabled: disabled || loading,
8674
+ "aria-describedby": ariaDescribedBy,
8675
+ "aria-errormessage": ariaErrorMessage,
8676
+ "aria-invalid": ariaInvalid,
8677
+ "aria-label": ariaLabel,
8678
+ "aria-labelledby": ariaLabelledBy,
8667
8679
  "aria-haspopup": "listbox",
8668
8680
  "aria-expanded": open,
8669
8681
  onClick: () => setOpen((current) => !current),
@@ -4,7 +4,7 @@
4
4
  import { useCallback, useState } from "react";
5
5
 
6
6
  // src/generated/version.ts
7
- var version = "0.4.0";
7
+ var version = "0.4.1";
8
8
 
9
9
  // src/infrastructure.ts
10
10
  var theme = {
package/dist/styles.css CHANGED
@@ -25543,7 +25543,8 @@ html {
25543
25543
  overflow: hidden;
25544
25544
  }
25545
25545
 
25546
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-header {
25546
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25547
+ .modal-header {
25547
25548
  display: flex;
25548
25549
  align-items: flex-start;
25549
25550
  gap: 16px;
@@ -25551,7 +25552,8 @@ html {
25551
25552
  border-block-end: 1px solid var(--color-outline-variant);
25552
25553
  }
25553
25554
 
25554
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-title {
25555
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25556
+ .modal-title {
25555
25557
  margin: 0;
25556
25558
  color: var(--color-on-surface);
25557
25559
  font-size: 1rem;
@@ -25559,7 +25561,8 @@ html {
25559
25561
  line-height: 1.4;
25560
25562
  }
25561
25563
 
25562
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-body {
25564
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25565
+ .modal-body {
25563
25566
  flex: 1 1 auto;
25564
25567
  min-height: 0;
25565
25568
  padding: 20px;
@@ -25568,7 +25571,8 @@ html {
25568
25571
  line-height: 1.5;
25569
25572
  }
25570
25573
 
25571
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-footer {
25574
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25575
+ .modal-footer {
25572
25576
  display: flex;
25573
25577
  justify-content: flex-end;
25574
25578
  gap: 8px;
@@ -25580,7 +25584,9 @@ html {
25580
25584
  margin-top: 0;
25581
25585
  }
25582
25586
 
25583
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-footer > button {
25587
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25588
+ .modal-footer
25589
+ > button {
25584
25590
  min-height: 2.25rem;
25585
25591
  padding: 0 14px;
25586
25592
  color: var(--color-on-surface);
@@ -25592,18 +25598,23 @@ html {
25592
25598
  cursor: pointer;
25593
25599
  }
25594
25600
 
25595
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-footer > button:last-child {
25601
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25602
+ .modal-footer
25603
+ > button:last-child {
25596
25604
  color: var(--color-primary-content);
25597
25605
  background: var(--color-primary);
25598
25606
  border-color: var(--color-primary);
25599
25607
  }
25600
25608
 
25601
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-footer > button:disabled {
25609
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25610
+ .modal-footer
25611
+ > button:disabled {
25602
25612
  cursor: not-allowed;
25603
25613
  opacity: 0.55;
25604
25614
  }
25605
25615
 
25606
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-close {
25616
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25617
+ .modal-close {
25607
25618
  position: absolute;
25608
25619
  top: 12px;
25609
25620
  right: 12px;
@@ -25622,8 +25633,10 @@ html {
25622
25633
  cursor: pointer;
25623
25634
  }
25624
25635
 
25625
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-close:hover,
25626
- :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box) .modal-close:focus-visible {
25636
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25637
+ .modal-close:hover,
25638
+ :is(.modal-backdrop:not(dialog) > .modal:not(dialog), .react-modal-box)
25639
+ .modal-close:focus-visible {
25627
25640
  color: var(--color-on-surface);
25628
25641
  background: var(--color-surface-container);
25629
25642
  outline: none;
@@ -29163,3 +29176,200 @@ html {
29163
29176
  );
29164
29177
  }
29165
29178
  }
29179
+
29180
+ /* JSON Schema renderer layout. */
29181
+ .schema-form {
29182
+ max-width: none;
29183
+ }
29184
+
29185
+ .schema-form .schema-object,
29186
+ .schema-form .schema-array {
29187
+ display: grid;
29188
+ gap: 1rem;
29189
+ min-width: 0;
29190
+ margin: 0;
29191
+ padding: 1rem;
29192
+ border: 1px solid var(--color-outline, currentColor);
29193
+ border-radius: 8px;
29194
+ }
29195
+
29196
+ .schema-form > .schema-object {
29197
+ padding: 0;
29198
+ border: 0;
29199
+ }
29200
+
29201
+ .schema-form .schema-object legend,
29202
+ .schema-form .schema-array legend {
29203
+ padding: 0 0.25rem;
29204
+ color: var(--color-on-surface);
29205
+ font-weight: 700;
29206
+ }
29207
+
29208
+ .schema-form .schema-choice-group {
29209
+ display: grid;
29210
+ gap: 0.5rem;
29211
+ min-width: 0;
29212
+ margin: 0;
29213
+ padding: 0;
29214
+ border: 0;
29215
+ }
29216
+
29217
+ .schema-form .schema-choice-group legend {
29218
+ margin-bottom: 0.5rem;
29219
+ padding: 0;
29220
+ color: var(--color-on-surface);
29221
+ font-weight: 500;
29222
+ }
29223
+
29224
+ .schema-form .schema-choice-options {
29225
+ display: grid;
29226
+ justify-items: start;
29227
+ gap: 0.75rem;
29228
+ min-width: 0;
29229
+ }
29230
+
29231
+ .schema-form .schema-choice-horizontal .schema-choice-options {
29232
+ display: flex;
29233
+ flex-wrap: wrap;
29234
+ align-items: center;
29235
+ gap: 0.75rem 1rem;
29236
+ }
29237
+
29238
+ .schema-form .schema-choice-options > label {
29239
+ max-width: 100%;
29240
+ overflow-wrap: anywhere;
29241
+ }
29242
+
29243
+ .schema-form .schema-choice-group .schema-field-error {
29244
+ margin: 0;
29245
+ font-size: 0.75rem;
29246
+ }
29247
+
29248
+ .schema-form .schema-required::before {
29249
+ margin-right: 0.25rem;
29250
+ color: var(--color-error);
29251
+ content: "*";
29252
+ }
29253
+
29254
+ .schema-form .schema-description,
29255
+ .schema-form .form-item-extra {
29256
+ margin: 0;
29257
+ color: var(--color-on-surface-variant);
29258
+ font-size: 0.75rem;
29259
+ line-height: 1.4;
29260
+ }
29261
+
29262
+ .schema-form .form-item-extra p,
29263
+ .schema-form .form-item-help p {
29264
+ margin: 0;
29265
+ }
29266
+
29267
+ .schema-form .schema-array-item {
29268
+ display: grid;
29269
+ grid-template-columns: minmax(0, 1fr) auto;
29270
+ align-items: start;
29271
+ gap: 0.5rem;
29272
+ min-width: 0;
29273
+ }
29274
+
29275
+ .schema-form .schema-array-item > .schema-array-remove {
29276
+ grid-column: 2;
29277
+ grid-row: 1;
29278
+ align-self: center;
29279
+ }
29280
+
29281
+ .schema-form .schema-array-remove svg {
29282
+ display: block;
29283
+ }
29284
+
29285
+ /* Share the label and control rows so wrapped labels keep actions aligned. */
29286
+ .schema-form .schema-array-item:has(> .form-item) {
29287
+ grid-template-rows: auto;
29288
+ row-gap: 0.375rem;
29289
+ }
29290
+
29291
+ .schema-form
29292
+ .schema-array-item:has(
29293
+ > .form-item > :is(.form-item-help, .form-item-extra)
29294
+ ) {
29295
+ grid-template-rows: auto auto;
29296
+ }
29297
+
29298
+ .schema-form
29299
+ .schema-array-item:has(> .form-item > .form-item-help):has(
29300
+ > .form-item > .form-item-extra
29301
+ ) {
29302
+ grid-template-rows: auto auto auto;
29303
+ }
29304
+
29305
+ .schema-form .schema-array-item:has(> .form-item > .form-item-label) {
29306
+ grid-template-rows: auto auto;
29307
+ }
29308
+
29309
+ .schema-form
29310
+ .schema-array-item:has(> .form-item > .form-item-label):has(
29311
+ > .form-item > :is(.form-item-help, .form-item-extra)
29312
+ ) {
29313
+ grid-template-rows: auto auto auto;
29314
+ }
29315
+
29316
+ .schema-form
29317
+ .schema-array-item:has(> .form-item > .form-item-label):has(
29318
+ > .form-item > .form-item-help
29319
+ ):has(> .form-item > .form-item-extra) {
29320
+ grid-template-rows: auto auto auto auto;
29321
+ }
29322
+
29323
+ .schema-form .schema-array-item > .form-item {
29324
+ grid-column: 1;
29325
+ grid-row: 1 / -1;
29326
+ grid-template-rows: subgrid;
29327
+ }
29328
+
29329
+ .schema-form .schema-array-item > .form-item > .form-item-control {
29330
+ align-self: center;
29331
+ }
29332
+
29333
+ .schema-form
29334
+ .schema-array-item:has(> .form-item > .form-item-label)
29335
+ > .schema-array-remove {
29336
+ grid-row: 2;
29337
+ }
29338
+
29339
+ .schema-form .schema-array > button {
29340
+ justify-self: start;
29341
+ }
29342
+
29343
+ .schema-form .schema-form-actions {
29344
+ display: flex;
29345
+ flex-wrap: wrap;
29346
+ gap: 0.75rem;
29347
+ }
29348
+
29349
+ .schema-form [role="alert"] {
29350
+ color: var(--color-error, #b42318);
29351
+ }
29352
+
29353
+ /* Schema fields own their labels, including composite workflow controls. */
29354
+ .schema-form .schema-specialized-field {
29355
+ min-width: 0;
29356
+ margin: 0;
29357
+ padding: 0;
29358
+ border: 0;
29359
+ }
29360
+ .schema-form .schema-specialized-field > legend {
29361
+ margin-bottom: 0.5rem;
29362
+ font-weight: 500;
29363
+ }
29364
+ .schema-form .schema-widget {
29365
+ min-width: 0;
29366
+ }
29367
+ .schema-form .schema-widget-value {
29368
+ margin: 0.5rem 0 0;
29369
+ overflow-wrap: anywhere;
29370
+ font-size: 0.75rem;
29371
+ color: var(--color-on-surface-variant);
29372
+ }
29373
+ .schema-form .schema-widget > :is(.cascader-container, .tree-select) {
29374
+ width: 100%;
29375
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duskmoon-dev/components",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -419,7 +419,11 @@
419
419
  "import": "./dist/components/swap/index.js",
420
420
  "types": "./dist/components/swap/index.d.ts"
421
421
  },
422
- "./styles.css": "./dist/styles.css"
422
+ "./styles.css": "./dist/styles.css",
423
+ "./json-schema-form": {
424
+ "import": "./dist/components/json-schema-form/index.js",
425
+ "types": "./dist/components/json-schema-form/index.d.ts"
426
+ }
423
427
  },
424
428
  "files": [
425
429
  "dist",
@@ -476,7 +480,9 @@
476
480
  "clsx": "^2.1.1",
477
481
  "react-markdown": "^10.1.0",
478
482
  "remark-breaks": "^4.0.0",
479
- "remark-gfm": "^4.0.1"
483
+ "remark-gfm": "^4.0.1",
484
+ "ajv": "^8.20.0",
485
+ "ajv-formats": "^3.0.1"
480
486
  },
481
487
  "devDependencies": {
482
488
  "@duskmoon-dev/core": "1.20.3",