@takuhon/ui 0.23.0 → 0.25.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/dist/admin/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ValidationError, LocaleTag,
|
|
1
|
+
import { ValidationError, LocaleTag, Takuhon } from '@takuhon/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Field-error plumbing shared by the admin form components.
|
|
@@ -295,6 +295,19 @@ declare const EN: {
|
|
|
295
295
|
readonly 'section.projects': "Projects";
|
|
296
296
|
readonly 'section.skills': "Skills";
|
|
297
297
|
readonly 'section.settings': "Settings";
|
|
298
|
+
readonly 'section.education': "Education";
|
|
299
|
+
readonly 'section.certifications': "Certifications";
|
|
300
|
+
readonly 'section.publications': "Publications";
|
|
301
|
+
readonly 'section.honors': "Honors & awards";
|
|
302
|
+
readonly 'section.volunteering': "Volunteering";
|
|
303
|
+
readonly 'section.memberships': "Memberships";
|
|
304
|
+
readonly 'section.languages': "Languages";
|
|
305
|
+
readonly 'section.courses': "Courses";
|
|
306
|
+
readonly 'section.patents': "Patents";
|
|
307
|
+
readonly 'section.testScores': "Test scores";
|
|
308
|
+
readonly 'section.recommendations': "Recommendations";
|
|
309
|
+
readonly 'section.contact': "Contact";
|
|
310
|
+
readonly 'section.meta': "Metadata";
|
|
298
311
|
readonly 'field.displayName': "Display name";
|
|
299
312
|
readonly 'field.tagline': "Tagline";
|
|
300
313
|
readonly 'field.bio': "Bio";
|
|
@@ -382,71 +395,241 @@ type AdminLabelKey = keyof typeof EN;
|
|
|
382
395
|
*/
|
|
383
396
|
declare function getAdminLabel(key: AdminLabelKey, locale?: LocaleTag): string;
|
|
384
397
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Schema-driven field classification for the admin editor.
|
|
400
|
+
*
|
|
401
|
+
* Maps a node of the canonical takuhon JSON Schema (2020-12) to a
|
|
402
|
+
* {@link FieldKind} — the widget the admin form should render for it. This is
|
|
403
|
+
* the pure-logic foundation of the schema-driven form engine (design:
|
|
404
|
+
* `admin-form-schema-driven-design.md`). It resolves local `$ref`s against
|
|
405
|
+
* `$defs` and unwraps nullable `anyOf` unions, so the renderer never hand-wires
|
|
406
|
+
* fields per section. No React here; the classifier is fully unit-testable.
|
|
407
|
+
*
|
|
408
|
+
* Presentation concerns the schema cannot express (widget overrides, ordering,
|
|
409
|
+
* help text, cross-section reference selectors, image/Gravatar widgets) live in
|
|
410
|
+
* a separate field-spec registry, not in the data schema (design decision A1).
|
|
411
|
+
*/
|
|
412
|
+
/**
|
|
413
|
+
* Minimal structural view of a JSON Schema 2020-12 node — only the keywords the
|
|
414
|
+
* classifier reads. The canonical schema (`@takuhon/core`'s `schema`) is cast to
|
|
415
|
+
* this for traversal.
|
|
416
|
+
*/
|
|
417
|
+
interface SchemaNode {
|
|
418
|
+
$ref?: string;
|
|
419
|
+
$defs?: Record<string, SchemaNode>;
|
|
420
|
+
type?: string | readonly string[];
|
|
421
|
+
format?: string;
|
|
422
|
+
enum?: readonly unknown[];
|
|
423
|
+
properties?: Record<string, SchemaNode>;
|
|
424
|
+
required?: readonly string[];
|
|
425
|
+
items?: SchemaNode;
|
|
426
|
+
anyOf?: readonly SchemaNode[];
|
|
427
|
+
maxLength?: number;
|
|
428
|
+
minimum?: number;
|
|
429
|
+
description?: string;
|
|
430
|
+
default?: unknown;
|
|
396
431
|
}
|
|
432
|
+
/** The widget the admin form should render for a classified schema node. */
|
|
433
|
+
type FieldKind = {
|
|
434
|
+
widget: 'text';
|
|
435
|
+
maxLength?: number;
|
|
436
|
+
} | {
|
|
437
|
+
widget: 'url';
|
|
438
|
+
} | {
|
|
439
|
+
widget: 'email';
|
|
440
|
+
} | {
|
|
441
|
+
widget: 'month';
|
|
442
|
+
} | {
|
|
443
|
+
widget: 'date';
|
|
444
|
+
} | {
|
|
445
|
+
widget: 'datetime';
|
|
446
|
+
} | {
|
|
447
|
+
widget: 'integer';
|
|
448
|
+
minimum?: number;
|
|
449
|
+
} | {
|
|
450
|
+
widget: 'checkbox';
|
|
451
|
+
default?: boolean;
|
|
452
|
+
} | {
|
|
453
|
+
widget: 'select';
|
|
454
|
+
options: readonly string[];
|
|
455
|
+
} | {
|
|
456
|
+
widget: 'localeTag';
|
|
457
|
+
} | {
|
|
458
|
+
widget: 'localizedTitle';
|
|
459
|
+
} | {
|
|
460
|
+
widget: 'localizedBody';
|
|
461
|
+
} | {
|
|
462
|
+
widget: 'slug';
|
|
463
|
+
} | {
|
|
464
|
+
widget: 'array';
|
|
465
|
+
item: FieldKind;
|
|
466
|
+
} | {
|
|
467
|
+
widget: 'object';
|
|
468
|
+
fields: readonly FieldEntry[];
|
|
469
|
+
} | {
|
|
470
|
+
widget: 'unsupported';
|
|
471
|
+
};
|
|
472
|
+
/** A named property of an object schema: its optionality and classified kind. */
|
|
473
|
+
interface FieldEntry {
|
|
474
|
+
name: string;
|
|
475
|
+
/** Listed in the object's `required` array. */
|
|
476
|
+
required: boolean;
|
|
477
|
+
/** The schema permits an explicit `null` (e.g. `anyOf: [X, { type: 'null' }]`). */
|
|
478
|
+
nullable: boolean;
|
|
479
|
+
kind: FieldKind;
|
|
480
|
+
}
|
|
481
|
+
/** Classify a (possibly `$ref`/nullable) schema node into the widget to render. */
|
|
482
|
+
declare function classifyNode(root: SchemaNode, node: SchemaNode): FieldKind;
|
|
483
|
+
/** Classify a top-level section (e.g. `'education'`, `'contact'`) by name. */
|
|
484
|
+
declare function sectionFieldKind(root: SchemaNode, section: string): FieldKind;
|
|
485
|
+
|
|
397
486
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
487
|
+
* Field-spec registry — the UI-hint layer the data schema deliberately does not
|
|
488
|
+
* carry (design decision A1). Keyed by a section-relative dot path *without*
|
|
489
|
+
* array indices (e.g. `education.institution`, `meta.createdAt`), it overrides
|
|
490
|
+
* or augments the schema-derived classification with presentation concerns:
|
|
491
|
+
* labels, help text, hiding auto-managed fields, forcing a widget.
|
|
492
|
+
*
|
|
493
|
+
* Keeping these out of `takuhon.schema.json` leaves the data contract pure
|
|
494
|
+
* (consumers / validators see no UI noise) and means this track changes no
|
|
495
|
+
* `schemaVersion`.
|
|
401
496
|
*/
|
|
402
|
-
declare function ProfileForm({ value, onChange, locales, errors, formatLocale, uploadAsset, }: ProfileFormProps): React.JSX.Element;
|
|
403
497
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
498
|
+
/**
|
|
499
|
+
* Everything a custom field renderer needs. The engine builds this as it walks,
|
|
500
|
+
* so a bespoke widget (avatar, comma-separated list, visibility matrix) plugs in
|
|
501
|
+
* without re-deriving its pointer/path or re-threading locales and errors. This
|
|
502
|
+
* is the escape hatch for the handful of fields a generic widget cannot capture
|
|
503
|
+
* (design §8 "generic default + targeted craft").
|
|
504
|
+
*/
|
|
505
|
+
interface CustomFieldContext {
|
|
506
|
+
/** Current value at this path. */
|
|
507
|
+
value: unknown;
|
|
508
|
+
/** Write the next value for this field back into the document. */
|
|
509
|
+
onChange: (next: unknown) => void;
|
|
510
|
+
/** RFC 6901 pointer (with array indices) for error lookup. */
|
|
511
|
+
pointer: string;
|
|
512
|
+
/** Registry path (no array indices). */
|
|
513
|
+
path: string;
|
|
514
|
+
/** Resolved label (registry override or humanized field name). */
|
|
515
|
+
label: string;
|
|
516
|
+
/** Listed in the parent object's `required`. */
|
|
517
|
+
required: boolean;
|
|
407
518
|
locales: readonly LocaleTag[];
|
|
408
|
-
errors
|
|
519
|
+
errors: FieldErrorIndex;
|
|
409
520
|
formatLocale?: (locale: LocaleTag) => string;
|
|
521
|
+
/** Avatar upload, threaded from the host; absent means URL-only. */
|
|
522
|
+
uploadAsset?: UploadAsset;
|
|
523
|
+
/**
|
|
524
|
+
* The whole document (root value), so a field can read sibling sections —
|
|
525
|
+
* e.g. a reference selector listing `careers[]` / `education[]` ids. Typed
|
|
526
|
+
* `unknown` to keep the engine schema-generic; renderers narrow it.
|
|
527
|
+
*/
|
|
528
|
+
document?: unknown;
|
|
410
529
|
}
|
|
411
|
-
/**
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
530
|
+
/** Presentation overrides for a single field, looked up by its schema path. */
|
|
531
|
+
interface FieldHint {
|
|
532
|
+
/** Replace the humanized field name. */
|
|
533
|
+
label?: string;
|
|
534
|
+
/** Help text shown under the control. */
|
|
535
|
+
hint?: string;
|
|
536
|
+
/** Omit from the form (the value is still preserved in the document). */
|
|
537
|
+
hidden?: boolean;
|
|
538
|
+
/** Render this widget instead of the schema-derived one. */
|
|
539
|
+
widget?: FieldKind['widget'];
|
|
540
|
+
/** Force multiline for a localized/text field. */
|
|
541
|
+
multiline?: boolean;
|
|
542
|
+
/**
|
|
543
|
+
* Render a bespoke control for this field instead of the schema-derived
|
|
544
|
+
* widget. Used for the few fields no generic widget captures (avatar,
|
|
545
|
+
* comma-separated lists, the public-visibility matrix).
|
|
546
|
+
*/
|
|
547
|
+
render?: (ctx: CustomFieldContext) => React.ReactNode;
|
|
420
548
|
}
|
|
421
|
-
/**
|
|
422
|
-
|
|
549
|
+
/** Schema-path → hint. Paths use dots and never include array indices. */
|
|
550
|
+
type FieldRegistry = Readonly<Record<string, FieldHint>>;
|
|
551
|
+
declare const EMPTY_REGISTRY: FieldRegistry;
|
|
552
|
+
/**
|
|
553
|
+
* Humanize a property name for a default label: `fieldOfStudy` → `Field of
|
|
554
|
+
* study`, `credentialId` → `Credential ID`, `url` → `URL`. Known acronyms stay
|
|
555
|
+
* uppercased. The registry overrides this where a better label is needed.
|
|
556
|
+
*/
|
|
557
|
+
declare function humanize(name: string): string;
|
|
558
|
+
/** The hint at `path`, or an empty hint when none is registered. */
|
|
559
|
+
declare function hintAt(registry: FieldRegistry, path: string): FieldHint;
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Schema-driven form engine.
|
|
563
|
+
*
|
|
564
|
+
* Renders an editable form for any takuhon section straight from its
|
|
565
|
+
* {@link FieldKind} (see `field-classification.ts`), using the existing admin
|
|
566
|
+
* primitives. The RFC 6901 `pointer` (with array indices, for error lookup) and
|
|
567
|
+
* the registry `path` (without indices, for UI hints) are derived automatically
|
|
568
|
+
* as the engine walks — so no section hand-wires field plumbing or pointer
|
|
569
|
+
* strings. Presentation overrides come from the {@link FieldRegistry}
|
|
570
|
+
* (decision A1); the data schema stays UI-free.
|
|
571
|
+
*
|
|
572
|
+
* Values are treated as `unknown` and narrowed per kind — the document has been
|
|
573
|
+
* (or will be) validated against the schema, so each branch's cast is sound.
|
|
574
|
+
*/
|
|
423
575
|
|
|
424
|
-
interface
|
|
425
|
-
|
|
426
|
-
|
|
576
|
+
interface SchemaFormProps {
|
|
577
|
+
/** Classified shape of the section (from `sectionFieldKind`). */
|
|
578
|
+
kind: FieldKind;
|
|
579
|
+
/** Current value for the section. */
|
|
580
|
+
value: unknown;
|
|
581
|
+
/** Receives the next value on any edit. */
|
|
582
|
+
onChange: (next: unknown) => void;
|
|
583
|
+
/** RFC 6901 pointer to this value, e.g. `/education`. */
|
|
584
|
+
pointer: string;
|
|
585
|
+
/** Registry path (no array indices). Defaults to the pointer sans leading slash. */
|
|
586
|
+
path?: string;
|
|
587
|
+
/** Heading for the section / object. */
|
|
588
|
+
label: string;
|
|
427
589
|
locales: readonly LocaleTag[];
|
|
428
590
|
errors?: FieldErrorIndex;
|
|
591
|
+
registry?: FieldRegistry;
|
|
429
592
|
formatLocale?: (locale: LocaleTag) => string;
|
|
593
|
+
/** Avatar upload, threaded to the avatar field's custom renderer. */
|
|
594
|
+
uploadAsset?: UploadAsset;
|
|
595
|
+
/** Whole document, so reference fields can list sibling-section ids. */
|
|
596
|
+
document?: unknown;
|
|
430
597
|
}
|
|
431
|
-
/**
|
|
432
|
-
|
|
598
|
+
/**
|
|
599
|
+
* Render a section's form from its schema-derived {@link FieldKind}. Array
|
|
600
|
+
* sections become a {@link Repeater}; object sections a labelled fieldset;
|
|
601
|
+
* scalars map to the matching primitive.
|
|
602
|
+
*/
|
|
603
|
+
declare function SchemaForm(props: SchemaFormProps): React.JSX.Element;
|
|
433
604
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
605
|
+
/**
|
|
606
|
+
* The admin editor's single source of truth for which sections to render and how.
|
|
607
|
+
*
|
|
608
|
+
* {@link ADMIN_SECTIONS} lists every top-level section in display order; the
|
|
609
|
+
* {@link SchemaForm} engine renders each one from the schema. {@link SECTION_REGISTRY}
|
|
610
|
+
* is the UI-hint layer (decision A1): bespoke renderers for the few fields a
|
|
611
|
+
* generic widget cannot capture (avatar, comma-separated lists, the visibility
|
|
612
|
+
* matrix), plus curated labels and help text. The data schema stays UI-free.
|
|
613
|
+
*/
|
|
441
614
|
|
|
442
|
-
interface
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
errors?: FieldErrorIndex;
|
|
446
|
-
formatLocale?: (locale: LocaleTag) => string;
|
|
615
|
+
interface AdminSection {
|
|
616
|
+
key: keyof Takuhon;
|
|
617
|
+
label: AdminLabelKey;
|
|
447
618
|
}
|
|
448
|
-
/**
|
|
449
|
-
|
|
619
|
+
/**
|
|
620
|
+
* All editable sections, in display order. The first six were once bespoke
|
|
621
|
+
* forms; they now share the schema-driven engine with the rest, so every section
|
|
622
|
+
* is a form (spec §14.2 Phase 5, no more raw-JSON-only sections).
|
|
623
|
+
*/
|
|
624
|
+
declare const ADMIN_SECTIONS: readonly AdminSection[];
|
|
625
|
+
/**
|
|
626
|
+
* UI hints keyed by section-relative dot path (no array indices). Covers the
|
|
627
|
+
* bespoke widgets (avatar, comma-separated lists, visibility matrix, and
|
|
628
|
+
* cross-section reference selectors), curated labels for fields whose humanized
|
|
629
|
+
* name reads poorly, help text, and the auto-managed meta fields that stay
|
|
630
|
+
* hidden.
|
|
631
|
+
*/
|
|
632
|
+
declare const SECTION_REGISTRY: FieldRegistry;
|
|
450
633
|
|
|
451
634
|
interface RawJsonEditorProps {
|
|
452
635
|
value: Takuhon;
|
|
@@ -506,4 +689,4 @@ interface AdminEditorProps {
|
|
|
506
689
|
*/
|
|
507
690
|
declare function AdminEditor({ initialDocument, onSave, onReload, onExport, onImport, formatLocale, uploadAsset, }: AdminEditorProps): React.JSX.Element;
|
|
508
691
|
|
|
509
|
-
export { AdminEditor, type AdminEditorProps, type AdminLabelKey, type AdminSaveOutcome, type
|
|
692
|
+
export { ADMIN_SECTIONS, AdminEditor, type AdminEditorProps, type AdminLabelKey, type AdminSaveOutcome, type AdminSection, type AssetUploadResult, CheckboxField, type CheckboxFieldProps, type CustomFieldContext, EMPTY_REGISTRY, Field, type FieldControlProps, type FieldErrorIndex, type FieldErrorLike, type FieldHint, type FieldKind, type FieldProps, type FieldRegistry, GravatarField, type GravatarFieldProps, ImageField, type ImageFieldProps, LocaleTabs, type LocaleTabsProps, NO_FIELD_ERRORS, RawJsonEditor, type RawJsonEditorProps, Repeater, type RepeaterProps, SECTION_REGISTRY, SchemaForm, type SchemaFormProps, type SchemaNode, SelectField, type SelectFieldProps, type SelectOption, TextAreaField, type TextAreaFieldProps, TextField, type TextFieldProps, type UploadAsset, canonicalPointer, classifyNode, collectErrorsUnder, errorsAt, getAdminLabel, hasErrorsUnder, hintAt, humanize, indexErrors, indexValidationErrors, sectionFieldKind };
|