@fgv/ts-res-ui-components 5.0.0-26 → 5.0.0-28

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.
@@ -2,6 +2,7 @@ import { Bundle } from '@fgv/ts-res';
2
2
  import { Config } from '@fgv/ts-res';
3
3
  import { FileTree } from '@fgv/ts-utils';
4
4
  import { Import } from '@fgv/ts-res';
5
+ import { JsonCompatible } from '@fgv/ts-json-base';
5
6
  import { JsonValue } from '@fgv/ts-json-base';
6
7
  import { Logging } from '@fgv/ts-utils';
7
8
  import { MessageLogLevel } from '@fgv/ts-utils';
@@ -1832,13 +1833,13 @@ declare interface IConfigurationViewProps extends IViewBaseProps {
1832
1833
  *
1833
1834
  * @public
1834
1835
  */
1835
- declare interface ICreatePendingResourceParams {
1836
+ declare interface ICreatePendingResourceParams<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
1836
1837
  /** Full resource ID (e.g., 'platform.languages.az-AZ') - must be unique */
1837
1838
  id: string;
1838
1839
  /** Name of the resource type to use for validation and template creation */
1839
1840
  resourceTypeName: string;
1840
1841
  /** JSON content for the resource candidate. If undefined, the resource type's base template will be used. */
1841
- json?: JsonValue;
1842
+ json?: TV;
1842
1843
  }
1843
1844
 
1844
1845
  /**
@@ -1977,11 +1978,11 @@ declare interface IEditableJsonViewProps {
1977
1978
  *
1978
1979
  * @public
1979
1980
  */
1980
- declare interface IEditedResourceInfo {
1981
+ declare interface IEditedResourceInfo<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
1981
1982
  /** Unique identifier of the resource being edited */
1982
1983
  resourceId: string;
1983
- originalValue: JsonValue;
1984
- editedValue: JsonValue;
1984
+ originalValue: TV;
1985
+ editedValue: TV;
1985
1986
  timestamp: Date;
1986
1987
  }
1987
1988
 
@@ -2987,16 +2988,20 @@ export declare interface IOrchestratorActions {
2987
2988
  updateFilterState: (state: Partial<IFilterState>) => void;
2988
2989
  applyFilter: () => Promise<IFilterResult | null>;
2989
2990
  resetFilter: () => void;
2990
- updateResolutionContext: (qualifierName: string, value: string | undefined) => void;
2991
- applyResolutionContext: () => void;
2992
- selectResourceForResolution: (resourceId: string) => void;
2991
+ updateResolutionContext: (qualifierName: string, value: string | undefined) => Result<void>;
2992
+ applyResolutionContext: (hostManagedValues?: Record<string, string | undefined>) => Result<void>;
2993
+ selectResourceForResolution: (resourceId: string) => Result<void>;
2993
2994
  setResolutionViewMode: (mode: 'composed' | 'best' | 'all' | 'raw') => void;
2994
- resetResolutionCache: () => void;
2995
- saveResourceEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => void;
2995
+ resetResolutionCache: () => Result<void>;
2996
+ saveResourceEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => Result<void>;
2996
2997
  getEditedValue: (resourceId: string) => JsonValue | undefined;
2997
2998
  hasResourceEdit: (resourceId: string) => boolean;
2998
- clearResourceEdits: () => void;
2999
- discardResourceEdits: () => void;
2999
+ clearResourceEdits: () => Result<{
3000
+ clearedCount: number;
3001
+ }>;
3002
+ discardResourceEdits: () => Result<{
3003
+ discardedCount: number;
3004
+ }>;
3000
3005
  createPendingResource: (params: ICreatePendingResourceParams) => Result<void>;
3001
3006
  startNewResource: (params?: IStartNewResourceParams) => Result<{
3002
3007
  draft: IResolutionState['newResourceDraft'];
@@ -3019,7 +3024,7 @@ export declare interface IOrchestratorActions {
3019
3024
  diagnostics: string[];
3020
3025
  }>;
3021
3026
  cancelNewResource: () => void;
3022
- removePendingResource: (resourceId: string) => void;
3027
+ removePendingResource: (resourceId: string) => Result<void>;
3023
3028
  markResourceForDeletion: (resourceId: string) => void;
3024
3029
  applyPendingResources: () => Promise<Result<{
3025
3030
  appliedCount: number;
@@ -3111,6 +3116,7 @@ export declare interface IOrchestratorState {
3111
3116
  isProcessing: boolean;
3112
3117
  error: string | null;
3113
3118
  messages: IMessage[];
3119
+ resourceEditorFactory?: IResourceEditorFactory;
3114
3120
  }
3115
3121
 
3116
3122
  /**
@@ -3506,7 +3512,7 @@ declare interface IResolutionOptions {
3506
3512
  *
3507
3513
  * @public
3508
3514
  */
3509
- declare interface IResolutionResult {
3515
+ declare interface IResolutionResult<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
3510
3516
  /** Whether the resolution was successful */
3511
3517
  success: boolean;
3512
3518
  /** ID of the resource that was resolved */
@@ -3520,7 +3526,7 @@ declare interface IResolutionResult {
3520
3526
  /** Detailed information about each candidate's matching process */
3521
3527
  candidateDetails?: ICandidateInfo[];
3522
3528
  /** The final composed/resolved value */
3523
- composedValue?: JsonValue;
3529
+ composedValue?: TV;
3524
3530
  /** Error message if resolution failed */
3525
3531
  error?: string;
3526
3532
  }
@@ -3732,7 +3738,7 @@ declare interface IResourceDetailData {
3732
3738
  *
3733
3739
  * @public
3734
3740
  */
3735
- declare interface IResourceEditorFactory {
3741
+ declare interface IResourceEditorFactory<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
3736
3742
  /**
3737
3743
  * Attempts to create a resource editor for the given resource.
3738
3744
  *
@@ -3741,7 +3747,7 @@ declare interface IResourceEditorFactory {
3741
3747
  * @param value - The current value of the resource
3742
3748
  * @returns ResourceEditorResult indicating success/failure and the editor component or error message
3743
3749
  */
3744
- createEditor(resourceId: string, resourceType: string, value: JsonValue): ResourceEditorResult;
3750
+ createEditor(resourceId: string, resourceType: string, value: TV): ResourceEditorResult<T, TV>;
3745
3751
  }
3746
3752
 
3747
3753
  /**
@@ -3750,17 +3756,17 @@ declare interface IResourceEditorFactory {
3750
3756
  *
3751
3757
  * @public
3752
3758
  */
3753
- declare interface IResourceEditorProps {
3759
+ declare interface IResourceEditorProps<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
3754
3760
  /** The original JSON value to edit */
3755
- value: JsonValue;
3761
+ value: TV;
3756
3762
  /** The resource ID for tracking edits */
3757
3763
  resourceId: string;
3758
3764
  /** Whether this resource has been edited */
3759
3765
  isEdited?: boolean;
3760
3766
  /** The current edited value if any */
3761
- editedValue?: JsonValue;
3767
+ editedValue?: TV;
3762
3768
  /** Callback when the user saves an edit */
3763
- onSave?: (resourceId: string, editedValue: JsonValue, originalValue: JsonValue) => void;
3769
+ onSave?: (resourceId: string, editedValue: TV, originalValue: TV) => void;
3764
3770
  /** Callback when the user cancels an edit */
3765
3771
  onCancel?: (resourceId: string) => void;
3766
3772
  /** Whether editing is currently disabled */
@@ -3844,6 +3850,12 @@ declare interface IResourceOrchestratorProps {
3844
3850
  qualifierTypeFactory?: Config.IConfigInitFactory<QualifierTypes.Config.IAnyQualifierTypeConfig, QualifierTypes.QualifierType>;
3845
3851
  /** Optional resource type factory for creating custom resource types */
3846
3852
  resourceTypeFactory?: Config.IConfigInitFactory<ResourceTypes.Config.IResourceTypeConfig, ResourceTypes.ResourceType>;
3853
+ /**
3854
+ * Optional factory for creating type-specific resource editors.
3855
+ * When provided, ResolutionView will use custom editors for supported resource types
3856
+ * instead of the default JSON editor.
3857
+ */
3858
+ resourceEditorFactory?: IResourceEditorFactory;
3847
3859
  /** Callback fired when orchestrator state changes */
3848
3860
  onStateChange?: (state: Partial<IOrchestratorState>) => void;
3849
3861
  /** Optional observability context for logging and user feedback */
@@ -4129,6 +4141,10 @@ declare interface ISourceResourceDetailProps {
4129
4141
  declare interface ISourceViewProps extends IViewBaseProps {
4130
4142
  /** The processed resource system to display */
4131
4143
  resources?: IExtendedProcessedResources | null;
4144
+ /** Optional filter state for filtered views */
4145
+ filterState?: IFilterState;
4146
+ /** Result of filtering if applied */
4147
+ filterResult?: IFilterResult | null;
4132
4148
  /** Currently selected resource ID for detailed view */
4133
4149
  selectedResourceId?: string | null;
4134
4150
  /** Callback when a resource is selected */
@@ -4177,7 +4193,7 @@ declare function isPendingAddition(resourceId: string, pendingResources: Map<str
4177
4193
  *
4178
4194
  * @public
4179
4195
  */
4180
- declare interface IStartNewResourceParams {
4196
+ declare interface IStartNewResourceParams<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
4181
4197
  /** Resource type to use (optional - will use first available if not provided) */
4182
4198
  defaultTypeName?: string;
4183
4199
  /** Pre-seed with specific ID (optional) */
@@ -4185,7 +4201,7 @@ declare interface IStartNewResourceParams {
4185
4201
  /** Pre-seed with specific resource type name (optional) */
4186
4202
  resourceTypeName?: string;
4187
4203
  /** Pre-seed with specific JSON content (optional) */
4188
- json?: JsonValue;
4204
+ json?: TV;
4189
4205
  }
4190
4206
 
4191
4207
  /**
@@ -5401,11 +5417,11 @@ declare function resolveResourceDetailed(resolver: Runtime.ResourceResolver, res
5401
5417
  *
5402
5418
  * @public
5403
5419
  */
5404
- declare type ResourceEditorResult = {
5420
+ declare type ResourceEditorResult<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> = {
5405
5421
  /** Indicates whether the factory was able to create an editor for the resource */
5406
5422
  success: true;
5407
5423
  /** The React component to render for editing this resource */
5408
- editor: React.ComponentType<IResourceEditorProps>;
5424
+ editor: React.ComponentType<IResourceEditorProps<T, TV>>;
5409
5425
  } | {
5410
5426
  /** Indicates the factory could not create an editor for this resource */
5411
5427
  success: false;
@@ -6490,6 +6506,35 @@ declare function useResolutionState(processedResources: IProcessedResources | nu
6490
6506
  */
6491
6507
  declare function useResourceData(params?: IUseResourceDataParams): IUseResourceDataReturn;
6492
6508
 
6509
+ /**
6510
+ * Smart observability hook that automatically provides the best available observability context.
6511
+ *
6512
+ * This hook detects the current observability context type and:
6513
+ * - Uses ViewState-connected context when available (best experience)
6514
+ * - Auto-creates ViewState connection when only console context is available
6515
+ * - Preserves custom contexts provided by consumers
6516
+ * - Provides helpful warnings for suboptimal configurations
6517
+ *
6518
+ * @returns The most appropriate observability context for the current component tree
6519
+ *
6520
+ * @example
6521
+ * ```tsx
6522
+ * // In a component that needs user feedback in the UI
6523
+ * const MyComponent = () => {
6524
+ * const o11y = useSmartObservability();
6525
+ *
6526
+ * const handleAction = () => {
6527
+ * o11y.user.success('Action completed successfully!');
6528
+ * };
6529
+ *
6530
+ * return <button onClick={handleAction}>Do Something</button>;
6531
+ * };
6532
+ * ```
6533
+ *
6534
+ * @public
6535
+ */
6536
+ export declare function useSmartObservability(): IObservabilityContext;
6537
+
6493
6538
  /**
6494
6539
  * Hook for managing view state including messages and resource selection.
6495
6540
  *
@@ -118,22 +118,23 @@ export const QualifierTypeEditForm = ({ qualifierType, onSave, onCancel, existin
118
118
  let allowedTerritories = [];
119
119
  let hierarchy = {};
120
120
  if (qualifierTypeInstance.isSuccess()) {
121
- const configResult = qualifierTypeInstance.value.getConfigurationJson();
121
+ const configResult = qualifierTypeInstance.value.getConfiguration();
122
122
  if (configResult.isSuccess()) {
123
123
  const config = configResult.value;
124
- allowContextList = config.allowContextList === true;
125
- caseSensitive = config.caseSensitive !== false; // Default to true
126
- enumeratedValues = Array.isArray(config.enumeratedValues)
127
- ? config.enumeratedValues
128
- : [];
129
- acceptLowercase = config.acceptLowercase === true;
130
- allowedTerritories = Array.isArray(config.allowedTerritories)
131
- ? config.allowedTerritories
132
- : [];
133
- hierarchy =
134
- typeof config.hierarchy === 'object' && config.hierarchy !== null
135
- ? config.hierarchy
136
- : {};
124
+ allowContextList = config.configuration?.allowContextList === true;
125
+ // Type-specific property access based on systemType
126
+ if (config.systemType === 'literal') {
127
+ const literalConfig = config.configuration;
128
+ caseSensitive = literalConfig?.caseSensitive !== false; // Default to true
129
+ enumeratedValues = literalConfig?.enumeratedValues ?? [];
130
+ hierarchy = literalConfig?.hierarchy ?? {};
131
+ }
132
+ else if (config.systemType === 'territory') {
133
+ const territoryConfig = config.configuration;
134
+ acceptLowercase = territoryConfig?.acceptLowercase === true;
135
+ allowedTerritories = territoryConfig?.allowedTerritories ?? [];
136
+ hierarchy = territoryConfig?.hierarchy ?? {};
137
+ }
137
138
  }
138
139
  }
139
140
  return {
@@ -1,6 +1,6 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { Config, QualifierTypes, ResourceTypes } from '@fgv/ts-res';
3
- import { IOrchestratorState, IOrchestratorActions } from '../../types';
3
+ import { IOrchestratorState, IOrchestratorActions, IResourceEditorFactory } from '../../types';
4
4
  import * as ObservabilityTools from '../../utils/observability';
5
5
  /**
6
6
  * Props for the ResourceOrchestrator component.
@@ -20,6 +20,12 @@ export interface IResourceOrchestratorProps {
20
20
  qualifierTypeFactory?: Config.IConfigInitFactory<QualifierTypes.Config.IAnyQualifierTypeConfig, QualifierTypes.QualifierType>;
21
21
  /** Optional resource type factory for creating custom resource types */
22
22
  resourceTypeFactory?: Config.IConfigInitFactory<ResourceTypes.Config.IResourceTypeConfig, ResourceTypes.ResourceType>;
23
+ /**
24
+ * Optional factory for creating type-specific resource editors.
25
+ * When provided, ResolutionView will use custom editors for supported resource types
26
+ * instead of the default JSON editor.
27
+ */
28
+ resourceEditorFactory?: IResourceEditorFactory;
23
29
  /** Callback fired when orchestrator state changes */
24
30
  onStateChange?: (state: Partial<IOrchestratorState>) => void;
25
31
  /** Optional observability context for logging and user feedback */
@@ -11,7 +11,7 @@ import * as DownloadUtils from '../../utils/downloadHelper';
11
11
  /**
12
12
  * Internal orchestrator component that has access to observability context via hook.
13
13
  */
14
- const ResourceOrchestratorInternal = ({ children, initialConfiguration, qualifierTypeFactory, resourceTypeFactory, onStateChange, viewState }) => {
14
+ const ResourceOrchestratorInternal = ({ children, initialConfiguration, qualifierTypeFactory, resourceTypeFactory, resourceEditorFactory, onStateChange, viewState }) => {
15
15
  // Get observability context from provider
16
16
  const o11y = useObservability();
17
17
  // Core hooks
@@ -27,9 +27,16 @@ const ResourceOrchestratorInternal = ({ children, initialConfiguration, qualifie
27
27
  resourceData.actions.updateProcessedResources(updatedResources);
28
28
  o11y.user.success('Resource system updated with edits');
29
29
  }, [resourceData.actions, o11y.user]);
30
- const resolutionData = useResolutionState(resourceData.state.processedResources, handleSystemUpdate);
31
30
  // Local state for filter results
32
31
  const [filterResult, setFilterResult] = useState(null);
32
+ // Use filtered resources for resolution when filtering is active and successful
33
+ const resolutionProcessedResources = useMemo(() => {
34
+ const isFilteringActive = filterState.state.enabled && filterResult?.success === true;
35
+ return isFilteringActive
36
+ ? filterResult?.processedResources ?? null
37
+ : resourceData.state.processedResources;
38
+ }, [filterState.state.enabled, filterResult, resourceData.state.processedResources]);
39
+ const resolutionData = useResolutionState(resolutionProcessedResources, handleSystemUpdate);
33
40
  // Track if filtering is in progress to prevent concurrent operations
34
41
  const isFilteringInProgress = React.useRef(false);
35
42
  // Initialize with configuration if provided
@@ -196,14 +203,16 @@ const ResourceOrchestratorInternal = ({ children, initialConfiguration, qualifie
196
203
  selectedResourceId: viewState.selectedResourceId,
197
204
  isProcessing: resourceData.state.isProcessing,
198
205
  error: resourceData.state.error,
199
- messages: viewState.messages
206
+ messages: viewState.messages,
207
+ resourceEditorFactory
200
208
  }), [
201
209
  resourceData.state,
202
210
  filterState.state,
203
211
  filterResult,
204
212
  resolutionData.state,
205
213
  viewState.selectedResourceId,
206
- viewState.messages
214
+ viewState.messages,
215
+ resourceEditorFactory
207
216
  ]);
208
217
  // Combined actions
209
218
  const actions = useMemo(() => ({
@@ -131,7 +131,7 @@ export const ResourcePicker = ({ resources, selectedResourceId, onResourceSelect
131
131
  : 'text-gray-600 hover:text-gray-900'}`, title: "Tree View" },
132
132
  React.createElement(FolderIcon, { className: "h-4 w-4" }),
133
133
  React.createElement("span", { className: "ml-1" }, "Tree"))))))),
134
- React.createElement("div", { className: "flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 !relative !z-auto !min-h-[300px]" }, viewMode === 'tree' ? (React.createElement(ResourcePickerTree, { resources: resources, pendingResources: pendingResources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, resourceAnnotations: resourceAnnotations, searchTerm: searchTerm, rootPath: rootPath, hideRootNode: hideRootNode, emptyMessage: emptyMessage })) : (React.createElement(ResourcePickerList, { resourceIds: resources.summary.resourceIds || [], pendingResources: pendingResources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, resourceAnnotations: resourceAnnotations, searchTerm: searchTerm, rootPath: rootPath, hideRootNode: hideRootNode, emptyMessage: emptyMessage })))));
134
+ React.createElement("div", { className: "flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 !relative !z-auto !min-h-[300px]" }, viewMode === 'tree' ? (React.createElement(ResourcePickerTree, { resources: resources, pendingResources: pendingResources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, resourceAnnotations: resourceAnnotations, searchTerm: searchTerm, rootPath: rootPath, hideRootNode: hideRootNode, emptyMessage: emptyMessage })) : (React.createElement(ResourcePickerList, { resourceIds: resourceIds, pendingResources: pendingResources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, resourceAnnotations: resourceAnnotations, searchTerm: searchTerm, rootPath: rootPath, hideRootNode: hideRootNode, emptyMessage: emptyMessage })))));
135
135
  };
136
136
  export default ResourcePicker;
137
137
  //# sourceMappingURL=index.js.map
@@ -55,6 +55,11 @@ import { useSmartObservability } from '../../../hooks/useSmartObservability';
55
55
  */
56
56
  export const GridView = ({ gridConfig, resources, resolutionState, resolutionActions, availableQualifiers = [], contextOptions, filterState, filterResult, showContextControls = true, showChangeControls = true, className = '' }) => {
57
57
  const o11y = useSmartObservability();
58
+ // Store o11y in a ref to avoid re-render issues
59
+ const o11yRef = React.useRef(o11y);
60
+ React.useEffect(() => {
61
+ o11yRef.current = o11y;
62
+ }, [o11y]);
58
63
  // Use filtered resources when filtering is active and successful
59
64
  const isFilteringActive = filterState?.enabled && filterResult?.success === true;
60
65
  const baseProcessedResources = isFilteringActive ? filterResult?.processedResources : resources;
@@ -65,11 +70,12 @@ export const GridView = ({ gridConfig, resources, resolutionState, resolutionAct
65
70
  }
66
71
  const selectionResult = selectResources(gridConfig.resourceSelection, baseProcessedResources);
67
72
  if (selectionResult.isFailure()) {
68
- o11y.user.error(`Resource selection failed: ${selectionResult.message}`);
73
+ // Use ref to avoid dependency on o11y
74
+ o11yRef.current.user.error(`Resource selection failed: ${selectionResult.message}`);
69
75
  return [];
70
76
  }
71
77
  return selectionResult.value;
72
- }, [baseProcessedResources, gridConfig.resourceSelection, o11y]);
78
+ }, [baseProcessedResources, gridConfig.resourceSelection]); // Removed o11y from dependencies
73
79
  // Resolve all selected resources with current context
74
80
  const resourceResolutions = useMemo(() => {
75
81
  if (!resolutionState?.currentResolver || !selectedResourceIds.length) {
@@ -116,9 +116,11 @@ export const MessagesWindow = ({ messages, onClearMessages, className = '' }) =>
116
116
  o11y.diag.error('Failed to copy messages:', err);
117
117
  });
118
118
  };
119
- if (messages.length === 0) {
120
- return null;
121
- }
119
+ // Always show the messages window, even when empty
120
+ // This ensures consistent UI layout and visibility
121
+ // if (messages.length === 0) {
122
+ // return null;
123
+ // }
122
124
  const getMessageIcon = (type) => {
123
125
  switch (type) {
124
126
  case 'info':
@@ -39,10 +39,13 @@ import { useSmartObservability } from '../../../hooks/useSmartObservability';
39
39
  *
40
40
  * @public
41
41
  */
42
- export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPresentation = 'hidden', className = '' }) => {
42
+ export const SourceView = ({ resources, filterState, filterResult, onExport, pickerOptions, pickerOptionsPresentation = 'hidden', className = '' }) => {
43
43
  const o11y = useSmartObservability();
44
44
  const [selectedResourceId, setSelectedResourceId] = useState(null);
45
45
  const [showJsonView, setShowJsonView] = useState(false);
46
+ // Use filtered resources when filtering is active and successful
47
+ const isFilteringActive = filterState?.enabled && filterResult?.success === true;
48
+ const activeProcessedResources = isFilteringActive ? filterResult?.processedResources : resources;
46
49
  // State for picker options control
47
50
  const [currentPickerOptions, setCurrentPickerOptions] = useState((pickerOptions ?? {}));
48
51
  // Merge picker options with view-specific defaults
@@ -68,19 +71,20 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
68
71
  }, [o11y]);
69
72
  // Get full resource collection data using the new method
70
73
  const getResourceCollectionData = useCallback(() => {
71
- if (!resources?.system.resourceManager) {
74
+ if (!activeProcessedResources?.system.resourceManager) {
72
75
  return null;
73
76
  }
74
77
  // Check if this is a ResourceManagerBuilder (has getResourceCollectionDecl method)
75
- if ('getResourceCollectionDecl' in resources.system.resourceManager) {
76
- const collectionResult = resources.system.resourceManager.getResourceCollectionDecl();
78
+ if ('getResourceCollectionDecl' in activeProcessedResources.system.resourceManager) {
79
+ const collectionResult = activeProcessedResources.system.resourceManager.getResourceCollectionDecl();
77
80
  if (collectionResult.isSuccess()) {
78
81
  return {
79
82
  ...collectionResult.value,
80
83
  metadata: {
81
84
  exportedAt: new Date().toISOString(),
82
- totalResources: resources.summary.totalResources,
83
- type: 'ts-res-resource-collection'
85
+ totalResources: activeProcessedResources.summary.totalResources,
86
+ type: isFilteringActive ? 'ts-res-filtered-resource-collection' : 'ts-res-resource-collection',
87
+ ...(isFilteringActive && { filterContext: filterState?.appliedValues })
84
88
  }
85
89
  };
86
90
  }
@@ -89,14 +93,15 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
89
93
  return null;
90
94
  }
91
95
  }
92
- else if (resources.compiledCollection) {
96
+ else if (activeProcessedResources.compiledCollection) {
93
97
  // For IResourceManager from bundles, use the compiled collection directly
94
98
  return {
95
- resources: resources.compiledCollection.resources || [],
99
+ resources: activeProcessedResources.compiledCollection.resources ?? [],
96
100
  metadata: {
97
101
  exportedAt: new Date().toISOString(),
98
- totalResources: resources.summary.totalResources,
99
- type: 'ts-res-resource-collection'
102
+ totalResources: activeProcessedResources.summary.totalResources,
103
+ type: isFilteringActive ? 'ts-res-filtered-resource-collection' : 'ts-res-resource-collection',
104
+ ...(isFilteringActive && { filterContext: filterState?.appliedValues })
100
105
  }
101
106
  };
102
107
  }
@@ -104,7 +109,7 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
104
109
  o11y.user.error('Resource collection data not available');
105
110
  return null;
106
111
  }
107
- }, [resources, o11y]);
112
+ }, [activeProcessedResources, isFilteringActive, filterState?.appliedValues, o11y]);
108
113
  // Export source data to JSON file
109
114
  const handleExportSourceData = useCallback(() => {
110
115
  try {
@@ -120,7 +125,7 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
120
125
  o11y.user.error(`Failed to export resource collection: ${error instanceof Error ? error.message : String(error)}`);
121
126
  }
122
127
  }, [getResourceCollectionData, onExport, o11y]);
123
- if (!resources) {
128
+ if (!activeProcessedResources) {
124
129
  return (React.createElement("div", { className: `p-6 ${className}` },
125
130
  React.createElement("div", { className: "flex items-center space-x-3 mb-6" },
126
131
  React.createElement(DocumentTextIcon, { className: "h-8 w-8 text-blue-600" }),
@@ -138,13 +143,14 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
138
143
  React.createElement("div", { className: "flex items-center justify-between mb-6" },
139
144
  React.createElement("div", { className: "flex items-center space-x-3" },
140
145
  React.createElement(DocumentTextIcon, { className: "h-8 w-8 text-blue-600" }),
141
- React.createElement("h2", { className: "text-2xl font-bold text-gray-900" }, "Source Browser")),
142
- resources && (React.createElement("div", { className: "flex items-center space-x-2" },
146
+ React.createElement("h2", { className: "text-2xl font-bold text-gray-900" }, "Source Browser"),
147
+ isFilteringActive && (React.createElement("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800" }, "Filtered"))),
148
+ activeProcessedResources && (React.createElement("div", { className: "flex items-center space-x-2" },
143
149
  React.createElement("button", { onClick: handleExportSourceData, className: "inline-flex items-center px-3 py-1.5 border border-gray-300 text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" },
144
150
  React.createElement(DocumentArrowDownIcon, { className: "h-4 w-4 mr-1" }),
145
151
  "Export JSON")))),
146
152
  React.createElement(ResourcePickerOptionsControl, { options: currentPickerOptions, onOptionsChange: setCurrentPickerOptions, presentation: pickerOptionsPresentation, title: "Source Browser Picker Options", className: "mb-6" }),
147
- resources && (React.createElement("div", { className: "bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6" },
153
+ activeProcessedResources && (React.createElement("div", { className: "bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6" },
148
154
  React.createElement("button", { onClick: () => setShowJsonView(!showJsonView), className: "inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" },
149
155
  React.createElement(CodeBracketIcon, { className: "h-4 w-4 mr-2" }),
150
156
  showJsonView ? 'Hide' : 'Show',
@@ -164,8 +170,8 @@ export const SourceView = ({ resources, onExport, pickerOptions, pickerOptionsPr
164
170
  React.createElement("div", { className: "flex items-center mb-4" },
165
171
  React.createElement("h3", { className: "text-lg font-semibold text-gray-900" }, "Resources")),
166
172
  React.createElement("div", { className: "flex-1 min-h-0" },
167
- React.createElement(ResourcePicker, { resources: resources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, options: effectivePickerOptions }))),
168
- React.createElement("div", { className: "lg:w-1/2 flex flex-col" }, selectedResourceId ? (React.createElement(SourceResourceDetail, { resourceId: selectedResourceId, processedResources: resources })) : (React.createElement("div", { className: "flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50" },
173
+ React.createElement(ResourcePicker, { resources: activeProcessedResources, selectedResourceId: selectedResourceId, onResourceSelect: handleResourceSelect, options: effectivePickerOptions }))),
174
+ React.createElement("div", { className: "lg:w-1/2 flex flex-col" }, selectedResourceId ? (React.createElement(SourceResourceDetail, { resourceId: selectedResourceId, processedResources: activeProcessedResources })) : (React.createElement("div", { className: "flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50" },
169
175
  React.createElement("div", { className: "text-center" },
170
176
  React.createElement(DocumentTextIcon, { className: "h-12 w-12 text-gray-400 mx-auto mb-4" }),
171
177
  React.createElement("p", { className: "text-gray-500" }, "Select a resource to view details")))))))));
package/lib/index.d.ts CHANGED
@@ -11,10 +11,13 @@ export { MessagesWindow } from './components/views/MessagesWindow';
11
11
  export { GridView } from './components/views/GridView';
12
12
  export { MultiGridView } from './components/views/GridView/MultiGridView';
13
13
  export { ResourceOrchestrator } from './components/orchestrator/ResourceOrchestrator';
14
- export { ObservabilityProvider, useObservability, type IObservabilityProviderProps } from './contexts';
15
- export { ResourceTreeView } from './components/common/ResourceTreeView';
16
- export { ResourceListView } from './components/common/ResourceListView';
17
- export { SourceResourceDetail } from './components/common/SourceResourceDetail';
18
- export { ResolutionResults } from './components/common/ResolutionResults';
14
+ import { ObservabilityProvider, useObservability, type IObservabilityProviderProps } from './contexts';
15
+ import { useSmartObservability } from './hooks/useSmartObservability';
16
+ export { useSmartObservability, ObservabilityProvider, useObservability, type IObservabilityProviderProps };
17
+ import { ResourceTreeView } from './components/common/ResourceTreeView';
18
+ import { ResourceListView } from './components/common/ResourceListView';
19
+ import { SourceResourceDetail } from './components/common/SourceResourceDetail';
20
+ import { ResolutionResults } from './components/common/ResolutionResults';
21
+ export { ResourceTreeView, ResourceListView, SourceResourceDetail, ResolutionResults };
19
22
  export { DownloadTools, FilterTools, ResolutionTools, ConfigurationTools, ResourceTools, ImportTools, TsResTools, ZipTools, ViewStateTools, PickerTools, GridTools, ObservabilityTools } from './namespaces';
20
23
  //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -22,12 +22,15 @@ export { MultiGridView } from './components/views/GridView/MultiGridView';
22
22
  // Export orchestrator
23
23
  export { ResourceOrchestrator } from './components/orchestrator/ResourceOrchestrator';
24
24
  // Export contexts and hooks
25
- export { ObservabilityProvider, useObservability } from './contexts';
25
+ import { ObservabilityProvider, useObservability } from './contexts';
26
+ import { useSmartObservability } from './hooks/useSmartObservability';
27
+ export { useSmartObservability, ObservabilityProvider, useObservability };
26
28
  // Export common components (likely to be used by consumers)
27
- export { ResourceTreeView } from './components/common/ResourceTreeView';
28
- export { ResourceListView } from './components/common/ResourceListView';
29
- export { SourceResourceDetail } from './components/common/SourceResourceDetail';
30
- export { ResolutionResults } from './components/common/ResolutionResults';
29
+ import { ResourceTreeView } from './components/common/ResourceTreeView';
30
+ import { ResourceListView } from './components/common/ResourceListView';
31
+ import { SourceResourceDetail } from './components/common/SourceResourceDetail';
32
+ import { ResolutionResults } from './components/common/ResolutionResults';
33
+ export { ResourceTreeView, ResourceListView, SourceResourceDetail, ResolutionResults };
31
34
  // Form components are now available through ConfigurationTools namespace:
32
35
  // - ConfigurationTools.QualifierTypeEditForm
33
36
  // - ConfigurationTools.QualifierEditForm
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.52.10"
8
+ "packageVersion": "7.52.12"
9
9
  }
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  import { Result } from '@fgv/ts-utils';
2
2
  import { ResourceJson, Config, Bundle, Resources, Runtime, QualifierTypes, Qualifiers, ResourceTypes, Import } from '@fgv/ts-res';
3
- import { JsonValue } from '@fgv/ts-json-base';
3
+ import { JsonCompatible, JsonValue } from '@fgv/ts-json-base';
4
4
  import type { IObservabilityContext } from '../utils/observability';
5
5
  /**
6
6
  * Configuration options for ResourcePicker components.
@@ -240,6 +240,10 @@ export interface IImportViewProps extends IViewBaseProps {
240
240
  export interface ISourceViewProps extends IViewBaseProps {
241
241
  /** The processed resource system to display */
242
242
  resources?: IExtendedProcessedResources | null;
243
+ /** Optional filter state for filtered views */
244
+ filterState?: IFilterState;
245
+ /** Result of filtering if applied */
246
+ filterResult?: IFilterResult | null;
243
247
  /** Currently selected resource ID for detailed view */
244
248
  selectedResourceId?: string | null;
245
249
  /** Callback when a resource is selected */
@@ -297,11 +301,11 @@ export interface ICompiledViewProps extends IViewBaseProps {
297
301
  *
298
302
  * @public
299
303
  */
300
- export type ResourceEditorResult = {
304
+ export type ResourceEditorResult<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> = {
301
305
  /** Indicates whether the factory was able to create an editor for the resource */
302
306
  success: true;
303
307
  /** The React component to render for editing this resource */
304
- editor: React.ComponentType<IResourceEditorProps>;
308
+ editor: React.ComponentType<IResourceEditorProps<T, TV>>;
305
309
  } | {
306
310
  /** Indicates the factory could not create an editor for this resource */
307
311
  success: false;
@@ -314,17 +318,17 @@ export type ResourceEditorResult = {
314
318
  *
315
319
  * @public
316
320
  */
317
- export interface IResourceEditorProps {
321
+ export interface IResourceEditorProps<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
318
322
  /** The original JSON value to edit */
319
- value: JsonValue;
323
+ value: TV;
320
324
  /** The resource ID for tracking edits */
321
325
  resourceId: string;
322
326
  /** Whether this resource has been edited */
323
327
  isEdited?: boolean;
324
328
  /** The current edited value if any */
325
- editedValue?: JsonValue;
329
+ editedValue?: TV;
326
330
  /** Callback when the user saves an edit */
327
- onSave?: (resourceId: string, editedValue: JsonValue, originalValue: JsonValue) => void;
331
+ onSave?: (resourceId: string, editedValue: TV, originalValue: TV) => void;
328
332
  /** Callback when the user cancels an edit */
329
333
  onCancel?: (resourceId: string) => void;
330
334
  /** Whether editing is currently disabled */
@@ -338,7 +342,7 @@ export interface IResourceEditorProps {
338
342
  *
339
343
  * @public
340
344
  */
341
- export interface IResourceEditorFactory {
345
+ export interface IResourceEditorFactory<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
342
346
  /**
343
347
  * Attempts to create a resource editor for the given resource.
344
348
  *
@@ -347,7 +351,7 @@ export interface IResourceEditorFactory {
347
351
  * @param value - The current value of the resource
348
352
  * @returns ResourceEditorResult indicating success/failure and the editor component or error message
349
353
  */
350
- createEditor(resourceId: string, resourceType: string, value: JsonValue): ResourceEditorResult;
354
+ createEditor(resourceId: string, resourceType: string, value: TV): ResourceEditorResult<T, TV>;
351
355
  }
352
356
  /**
353
357
  * Props for the ResolutionView component.
@@ -400,11 +404,11 @@ export interface IResolutionViewProps extends IViewBaseProps {
400
404
  *
401
405
  * @public
402
406
  */
403
- export interface IEditedResourceInfo {
407
+ export interface IEditedResourceInfo<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
404
408
  /** Unique identifier of the resource being edited */
405
409
  resourceId: string;
406
- originalValue: JsonValue;
407
- editedValue: JsonValue;
410
+ originalValue: TV;
411
+ editedValue: TV;
408
412
  timestamp: Date;
409
413
  }
410
414
  /**
@@ -464,13 +468,13 @@ export interface IResolutionState {
464
468
  *
465
469
  * @public
466
470
  */
467
- export interface ICreatePendingResourceParams {
471
+ export interface ICreatePendingResourceParams<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
468
472
  /** Full resource ID (e.g., 'platform.languages.az-AZ') - must be unique */
469
473
  id: string;
470
474
  /** Name of the resource type to use for validation and template creation */
471
475
  resourceTypeName: string;
472
476
  /** JSON content for the resource candidate. If undefined, the resource type's base template will be used. */
473
- json?: JsonValue;
477
+ json?: TV;
474
478
  }
475
479
  /**
476
480
  * Parameters for starting a new resource with optional pre-seeding.
@@ -492,7 +496,7 @@ export interface ICreatePendingResourceParams {
492
496
  *
493
497
  * @public
494
498
  */
495
- export interface IStartNewResourceParams {
499
+ export interface IStartNewResourceParams<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
496
500
  /** Resource type to use (optional - will use first available if not provided) */
497
501
  defaultTypeName?: string;
498
502
  /** Pre-seed with specific ID (optional) */
@@ -500,7 +504,7 @@ export interface IStartNewResourceParams {
500
504
  /** Pre-seed with specific resource type name (optional) */
501
505
  resourceTypeName?: string;
502
506
  /** Pre-seed with specific JSON content (optional) */
503
- json?: JsonValue;
507
+ json?: TV;
504
508
  }
505
509
  /**
506
510
  * Actions available for managing resource resolution testing and editing.
@@ -674,7 +678,7 @@ export interface IResolutionContextOptions {
674
678
  *
675
679
  * @public
676
680
  */
677
- export interface IResolutionResult {
681
+ export interface IResolutionResult<T = JsonValue, TV extends JsonCompatible<T> = JsonCompatible<T>> {
678
682
  /** Whether the resolution was successful */
679
683
  success: boolean;
680
684
  /** ID of the resource that was resolved */
@@ -688,7 +692,7 @@ export interface IResolutionResult {
688
692
  /** Detailed information about each candidate's matching process */
689
693
  candidateDetails?: ICandidateInfo[];
690
694
  /** The final composed/resolved value */
691
- composedValue?: JsonValue;
695
+ composedValue?: TV;
692
696
  /** Error message if resolution failed */
693
697
  error?: string;
694
698
  }
@@ -1149,6 +1153,7 @@ export interface IOrchestratorState {
1149
1153
  isProcessing: boolean;
1150
1154
  error: string | null;
1151
1155
  messages: IMessage[];
1156
+ resourceEditorFactory?: IResourceEditorFactory;
1152
1157
  }
1153
1158
  /**
1154
1159
  * Complete actions interface for the resource orchestrator system.
@@ -1266,16 +1271,20 @@ export interface IOrchestratorActions {
1266
1271
  updateFilterState: (state: Partial<IFilterState>) => void;
1267
1272
  applyFilter: () => Promise<IFilterResult | null>;
1268
1273
  resetFilter: () => void;
1269
- updateResolutionContext: (qualifierName: string, value: string | undefined) => void;
1270
- applyResolutionContext: () => void;
1271
- selectResourceForResolution: (resourceId: string) => void;
1274
+ updateResolutionContext: (qualifierName: string, value: string | undefined) => Result<void>;
1275
+ applyResolutionContext: (hostManagedValues?: Record<string, string | undefined>) => Result<void>;
1276
+ selectResourceForResolution: (resourceId: string) => Result<void>;
1272
1277
  setResolutionViewMode: (mode: 'composed' | 'best' | 'all' | 'raw') => void;
1273
- resetResolutionCache: () => void;
1274
- saveResourceEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => void;
1278
+ resetResolutionCache: () => Result<void>;
1279
+ saveResourceEdit: (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => Result<void>;
1275
1280
  getEditedValue: (resourceId: string) => JsonValue | undefined;
1276
1281
  hasResourceEdit: (resourceId: string) => boolean;
1277
- clearResourceEdits: () => void;
1278
- discardResourceEdits: () => void;
1282
+ clearResourceEdits: () => Result<{
1283
+ clearedCount: number;
1284
+ }>;
1285
+ discardResourceEdits: () => Result<{
1286
+ discardedCount: number;
1287
+ }>;
1279
1288
  createPendingResource: (params: ICreatePendingResourceParams) => Result<void>;
1280
1289
  startNewResource: (params?: IStartNewResourceParams) => Result<{
1281
1290
  draft: IResolutionState['newResourceDraft'];
@@ -1298,7 +1307,7 @@ export interface IOrchestratorActions {
1298
1307
  diagnostics: string[];
1299
1308
  }>;
1300
1309
  cancelNewResource: () => void;
1301
- removePendingResource: (resourceId: string) => void;
1310
+ removePendingResource: (resourceId: string) => Result<void>;
1302
1311
  markResourceForDeletion: (resourceId: string) => void;
1303
1312
  applyPendingResources: () => Promise<Result<{
1304
1313
  appliedCount: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-res-ui-components",
3
- "version": "5.0.0-26",
3
+ "version": "5.0.0-28",
4
4
  "description": "Reusable React components for ts-res resource visualization and management",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -23,11 +23,11 @@
23
23
  "@heroicons/react": "~2.2.0",
24
24
  "tslib": "^2.8.1",
25
25
  "json-edit-react": "~1.28.2",
26
- "@fgv/ts-res": "5.0.0-26",
27
- "@fgv/ts-json": "5.0.0-26",
28
- "@fgv/ts-json-base": "5.0.0-26",
29
- "@fgv/ts-utils": "5.0.0-26",
30
- "@fgv/ts-extras": "5.0.0-26"
26
+ "@fgv/ts-res": "5.0.0-28",
27
+ "@fgv/ts-utils": "5.0.0-28",
28
+ "@fgv/ts-json-base": "5.0.0-28",
29
+ "@fgv/ts-json": "5.0.0-28",
30
+ "@fgv/ts-extras": "5.0.0-28"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=18.0.0",
@@ -55,14 +55,14 @@
55
55
  "rimraf": "^6.0.1",
56
56
  "ts-jest": "^29.4.1",
57
57
  "typescript": "5.8.3",
58
- "@rushstack/heft": "0.74.3",
59
- "@rushstack/heft-web-rig": "0.29.8",
58
+ "@rushstack/heft": "0.74.4",
59
+ "@rushstack/heft-web-rig": "0.29.9",
60
60
  "@rushstack/eslint-config": "4.4.0",
61
- "@rushstack/heft-jest-plugin": "0.16.12",
61
+ "@rushstack/heft-jest-plugin": "0.16.13",
62
62
  "@microsoft/api-documenter": "^7.26.31",
63
63
  "@microsoft/api-extractor": "^7.52.10",
64
64
  "@testing-library/dom": "^10.4.0",
65
- "@fgv/ts-utils-jest": "5.0.0-26"
65
+ "@fgv/ts-utils-jest": "5.0.0-28"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "heft build --clean",