@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-22

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.
Files changed (37) hide show
  1. package/README.md +230 -35
  2. package/dist/ts-res-ui-components.d.ts +283 -19
  3. package/lib/components/orchestrator/ResourceOrchestrator.js +3 -1
  4. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
  5. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  6. package/lib/components/views/ResolutionView/index.js +1 -1
  7. package/lib/hooks/useResolutionState.js +836 -235
  8. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  9. package/lib/namespaces/ResolutionTools.js +2 -0
  10. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  11. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  12. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  13. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  14. package/lib/types/index.d.ts +124 -19
  15. package/lib/types/index.js +2 -1
  16. package/lib/utils/resolutionEditing.js +2 -1
  17. package/lib/utils/resourceSelectors.d.ts +146 -0
  18. package/lib/utils/resourceSelectors.js +233 -0
  19. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +3 -1
  20. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +29 -10
  21. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  22. package/lib-commonjs/components/views/ResolutionView/index.js +1 -1
  23. package/lib-commonjs/hooks/useResolutionState.js +835 -234
  24. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  25. package/lib-commonjs/types/index.js +10 -0
  26. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  27. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  28. package/package.json +7 -7
  29. package/src/components/orchestrator/ResourceOrchestrator.tsx +3 -1
  30. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +30 -10
  31. package/src/components/pickers/ResourcePicker/index.tsx +7 -2
  32. package/src/components/views/ResolutionView/index.tsx +1 -1
  33. package/src/hooks/useResolutionState.ts +1054 -257
  34. package/src/namespaces/ResolutionTools.ts +13 -1
  35. package/src/types/index.ts +131 -20
  36. package/src/utils/resolutionEditing.ts +2 -2
  37. package/src/utils/resourceSelectors.ts +278 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @fgv/ts-res-ui-components
2
2
 
3
- React components for building user interfaces that work with the [ts-res](https://github.com/fgv/ts-res) multidimensional resource management library.
3
+ React components for building user interfaces that work with the [ts-res](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) multidimensional resource management library.
4
4
 
5
5
  ## Overview
6
6
 
@@ -45,16 +45,69 @@ This library requires the following peer dependencies:
45
45
 
46
46
  ```json
47
47
  {
48
- "@fgv/ts-res": "^5.0.0",
49
- "@fgv/ts-utils": "^5.0.0",
50
- "@fgv/ts-json-base": "^5.0.0",
51
- "react": "^18.0.0",
52
- "react-dom": "^18.0.0"
48
+ "@fgv/ts-res": ">=5.0.0",
49
+ "@fgv/ts-utils": ">=5.0.0",
50
+ "@fgv/ts-json-base": ">=5.0.0",
51
+ "@fgv/ts-json": ">=5.0.0",
52
+ "react": ">=18.0.0",
53
+ "react-dom": ">=18.0.0"
53
54
  }
54
55
  ```
55
56
 
56
57
  ## Quick Start
57
58
 
59
+ ### Canonical New Resource Flow
60
+
61
+ The recommended approach for programmatic resource creation:
62
+
63
+ ```tsx
64
+ import { ResolutionTools } from '@fgv/ts-res-ui-components';
65
+
66
+ // Single atomic operation (recommended)
67
+ async function createResourceAtomic(actions) {
68
+ const result = await actions.createPendingResource({
69
+ id: 'platform.languages.az-AZ',
70
+ resourceTypeName: 'json',
71
+ json: { text: 'Welcome', locale: 'az-AZ' }
72
+ });
73
+
74
+ if (result.isSuccess()) {
75
+ console.log('Resource created successfully');
76
+ await actions.applyPendingResources();
77
+ } else {
78
+ console.error('Creation failed:', result.message);
79
+ }
80
+ }
81
+
82
+ // Step-by-step workflow (if needed)
83
+ function createResourceStepByStep(actions) {
84
+ // 1) Start new resource
85
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
86
+ if (!startResult.success) return;
87
+
88
+ // 2) Update resource ID
89
+ const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
90
+ if (!idResult.success) return;
91
+
92
+ // 3) Select resource type (if step 1 didn't set it)
93
+ const typeResult = actions.selectResourceType('json');
94
+ if (!typeResult.success) return;
95
+
96
+ // 4) Update JSON content (optional, recommended)
97
+ const jsonResult = actions.updateNewResourceJson({
98
+ text: 'Welcome',
99
+ locale: 'az-AZ'
100
+ });
101
+ if (!jsonResult.success) return;
102
+
103
+ // 5) Save as pending
104
+ const saveResult = actions.saveNewResourceAsPending();
105
+ if (!saveResult.success) return;
106
+
107
+ // Note: Do NOT call saveResourceEdit for brand-new resources
108
+ }
109
+ ```
110
+
58
111
  ### Minimal Editing App (Unified Apply)
59
112
 
60
113
  ```tsx
@@ -79,9 +132,12 @@ export default function App() {
79
132
  hasEdit: actions.hasResourceEdit,
80
133
  clearEdits: actions.clearResourceEdits,
81
134
  discardEdits: actions.discardResourceEdits,
135
+ // Enhanced resource creation actions with atomic API
136
+ createPendingResource: actions.createPendingResource,
82
137
  startNewResource: actions.startNewResource,
83
138
  updateNewResourceId: actions.updateNewResourceId,
84
139
  selectResourceType: actions.selectResourceType,
140
+ updateNewResourceJson: actions.updateNewResourceJson,
85
141
  saveNewResourceAsPending: actions.saveNewResourceAsPending,
86
142
  cancelNewResource: actions.cancelNewResource,
87
143
  removePendingResource: actions.removePendingResource,
@@ -515,47 +571,128 @@ This is particularly useful when:
515
571
 
516
572
  #### Resource Creation
517
573
 
518
- ResolutionView now supports creating new resources directly in the UI with a pending/apply workflow similar to context management:
574
+ ResolutionView supports both atomic and sequential resource creation workflows:
575
+
576
+ ##### Atomic Resource Creation (Recommended)
577
+
578
+ Use the new `createPendingResource` API for simple, robust resource creation:
579
+
580
+ ```tsx
581
+ // Single atomic call to create a pending resource
582
+ const result = await actions.createPendingResource({
583
+ id: 'platform.languages.az-AZ', // Full resource ID (required)
584
+ resourceTypeName: 'json', // Resource type (required)
585
+ json: { text: 'Welcome', locale: 'az-AZ' } // JSON content (optional - uses base template if omitted)
586
+ });
587
+
588
+ if (result.isSuccess()) {
589
+ console.log('Resource created and added to pending resources');
590
+ // Apply all pending changes when ready
591
+ await actions.applyPendingResources();
592
+ } else {
593
+ console.error('Failed to create resource:', result.message);
594
+ }
595
+
596
+ // Example: Creating a resource with base template (no JSON content)
597
+ const baseTemplateResult = await actions.createPendingResource({
598
+ id: 'platform.languages.fr-FR',
599
+ resourceTypeName: 'json'
600
+ // json omitted - resource type will provide base template (typically {})
601
+ });
602
+
603
+ if (baseTemplateResult.isSuccess()) {
604
+ console.log('Resource created using base template from resource type');
605
+ // You can then edit the resource using the UI or saveEdit()
606
+ }
607
+ ```
608
+
609
+ **Benefits of the atomic API:**
610
+ - **Single Operation**: No multi-step sequencing required
611
+ - **Better Error Handling**: Returns `Result<void>` with detailed error messages
612
+ - **Validation**: Comprehensive validation of ID, type, and JSON content
613
+ - **Safe**: Prevents temporary IDs and ensures uniqueness
614
+ - **Context Stamping**: Automatically applies current context as conditions
615
+
616
+ ##### Sequential Resource Creation (Legacy)
617
+
618
+ The traditional step-by-step workflow is still available:
619
+
620
+ ```tsx
621
+ // Step 1: Start a new resource
622
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
623
+ if (startResult.isFailure()) {
624
+ console.error('Failed to start resource:', startResult.message);
625
+ return;
626
+ }
627
+
628
+ // Step 2: Set the final resource ID
629
+ const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
630
+ if (idResult.isFailure()) {
631
+ console.error('Invalid resource ID:', idResult.message);
632
+ return;
633
+ }
634
+
635
+ // Step 3: Update JSON content (optional)
636
+ const jsonResult = actions.updateNewResourceJson({
637
+ text: 'Welcome',
638
+ locale: 'az-AZ'
639
+ });
640
+ if (jsonResult.isFailure()) {
641
+ console.error('Invalid JSON:', jsonResult.message);
642
+ return;
643
+ }
644
+
645
+ // Step 4: Save as pending
646
+ const saveResult = actions.saveNewResourceAsPending();
647
+ if (saveResult.isFailure()) {
648
+ console.error('Failed to save:', saveResult.message);
649
+ return;
650
+ }
651
+
652
+ // Step 5: Apply all pending changes
653
+ await actions.applyPendingResources();
654
+ ```
655
+
656
+ **Enhanced Action Return Values:**
657
+
658
+ All resource creation actions now return `Result<T>` objects following the standard Result pattern:
659
+ - Use `.isSuccess()` and `.isFailure()` to check status
660
+ - Access success values with `.value` property (contains `draft`/`pendingResources` and `diagnostics`)
661
+ - Access error messages with `.message` property
662
+ - Multiline error messages include diagnostic information
663
+
664
+ ```tsx
665
+ // Example: Working with Result values
666
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
667
+ if (startResult.isSuccess()) {
668
+ console.log('Started draft:', startResult.value.draft);
669
+ console.log('Diagnostics:', startResult.value.diagnostics);
670
+ } else {
671
+ console.error('Error:', startResult.message); // May include diagnostics
672
+ }
673
+ ```
674
+
675
+ ##### UI Integration Example
519
676
 
520
677
  ```tsx
521
- // Basic resource creation - user selects resource type
522
678
  <ResolutionView
523
679
  resources={state.processedResources}
524
680
  resolutionState={resolutionState}
525
681
  resolutionActions={resolutionActions}
526
682
  allowResourceCreation={true}
683
+ defaultResourceType="json" // Optional: Host-controlled resource type
684
+ showPendingResourcesInList={true} // Show pending resources with visual distinction
527
685
  onPendingResourcesApplied={(added, deleted) => {
686
+ console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
528
687
  // Rebuild resource manager with new resources
529
688
  const updatedResources = rebuildWithResources(added, deleted);
530
689
  setState({ processedResources: updatedResources });
531
690
  }}
532
691
  />
533
-
534
- // Host-controlled resource type
535
- <ResolutionView
536
- resources={state.processedResources}
537
- resolutionState={resolutionState}
538
- resolutionActions={resolutionActions}
539
- allowResourceCreation={true}
540
- defaultResourceType="json" // Type selector hidden, always creates JSON resources
541
- onPendingResourcesApplied={(added, deleted) => {
542
- console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
543
- }}
544
- />
545
-
546
- // With custom resource types
547
- <ResolutionView
548
- resources={state.processedResources}
549
- resolutionState={resolutionState}
550
- resolutionActions={resolutionActions}
551
- allowResourceCreation={true}
552
- resourceTypeFactory={[customType1, customType2]} // Provide custom resource types
553
- showPendingResourcesInList={true} // Show pending resources with visual distinction
554
- />
555
692
  ```
556
693
 
557
694
  **Unified Change Workflow (Edits, Additions, Deletions):**
558
- 1. Click "Add Resource" in the picker header to create new resources (if enabled)
695
+ 1. Use `createPendingResource()` or the traditional UI workflow to create resources
559
696
  2. Edit existing resources from the results pane using the JSON or custom editors
560
697
  3. Mark resources for deletion where supported
561
698
  4. All changes appear in a single "Pending Changes" bar with counts (edits/additions/deletions)
@@ -563,12 +700,70 @@ ResolutionView now supports creating new resources directly in the UI with a pen
563
700
  6. Or click "Discard Changes" to remove all pending changes
564
701
 
565
702
  **Key features:**
703
+ - **Atomic Creation**: Single API call creates complete resources
704
+ - **Enhanced Error Handling**: Detailed validation and error messages
566
705
  - **Template-based creation**: Resource types provide default templates for new resources
567
706
  - **Pending workflow**: Resources aren't added immediately, allowing review before applying
568
707
  - **Host control**: Can specify a default resource type to hide the type selector
569
708
  - **Batch operations**: Create multiple resources before applying
570
709
  - **Visual feedback**: Pending resources shown with distinct styling
571
- - **Validation**: Resource IDs are validated for uniqueness
710
+ - **ID Validation**: Comprehensive resource ID validation and uniqueness checking
711
+
712
+ ##### Resource ID Requirements
713
+
714
+ **IMPORTANT**: All pending resources use **full resource IDs** as keys, never leaf IDs:
715
+
716
+ ```tsx
717
+ // ✅ Correct: Full resource IDs
718
+ createPendingResource({
719
+ id: 'platform.languages.az-AZ', // Full path
720
+ resourceTypeName: 'json',
721
+ json: { text: 'Azərbaycan dili' }
722
+ });
723
+
724
+ // ❌ Wrong: Leaf IDs
725
+ createPendingResource({
726
+ id: 'az-AZ', // Just the leaf - will be rejected
727
+ resourceTypeName: 'json',
728
+ json: { text: 'Azərbaycan dili' }
729
+ });
730
+ ```
731
+
732
+ ##### Helper Functions for Pending Resources
733
+
734
+ ```tsx
735
+ import { ResolutionTools } from '@fgv/ts-res-ui-components';
736
+
737
+ // Get pending resources by type
738
+ const jsonResources = ResolutionTools.getPendingAdditionsByType(
739
+ resolutionState.pendingResources,
740
+ 'json'
741
+ );
742
+
743
+ // Check if a resource is pending
744
+ const isPending = ResolutionTools.isPendingAddition(
745
+ 'platform.languages.az-AZ',
746
+ resolutionState.pendingResources
747
+ );
748
+
749
+ // Work with resource IDs
750
+ const leafIdResult = ResolutionTools.deriveLeafId('platform.languages.az-AZ');
751
+ console.log(leafIdResult.value); // 'az-AZ'
752
+
753
+ const fullIdResult = ResolutionTools.deriveFullId('platform.languages', 'az-AZ');
754
+ console.log(fullIdResult.value); // 'platform.languages.az-AZ'
755
+
756
+ // Get statistics
757
+ const stats = ResolutionTools.getPendingResourceStats(resolutionState.pendingResources);
758
+ console.log(`${stats.totalCount} pending resources`);
759
+ console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
760
+
761
+ // Validate pending resource keys (diagnostic)
762
+ const validation = ResolutionTools.validatePendingResourceKeys(resolutionState.pendingResources);
763
+ if (validation.isFailure()) {
764
+ console.error('Key validation failed:', validation.message);
765
+ }
766
+ ```
572
767
 
573
768
  #### Custom Resource Editors
574
769
 
@@ -1596,6 +1791,6 @@ The API documentation includes detailed examples, usage patterns, and type infor
1596
1791
  For questions and support, please:
1597
1792
 
1598
1793
  1. Check the [API documentation](./docs/index.md) for detailed component usage
1599
- 2. Review the [ts-res documentation](https://docs.ts-res.dev) for core concepts
1600
- 3. Search [existing issues](https://github.com/fgv/ts-res/issues)
1601
- 4. Create a [new issue](https://github.com/fgv/ts-res/issues/new)
1794
+ 2. Review the [ts-res documentation](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) for core concepts
1795
+ 3. Search [existing issues](https://github.com/ErikFortune/fgv/issues)
1796
+ 4. Create a [new issue](https://github.com/ErikFortune/fgv/issues/new)