@@ -140,6 +140,50 @@ export function LandscapeEditor({ landscape, onLandscapeUpdated, onError }: Land
140140 return null ;
141141 } ;
142142
143+ /**
144+ * Helper function to create or find a package hierarchy from a dot-separated package name.
145+ * Returns the deepest package and all newly created packages.
146+ */
147+ const createPackageHierarchy = (
148+ parentPackages : CleanedPackage [ ] ,
149+ packageNameParts : string [ ]
150+ ) : { deepestPackage : CleanedPackage ; allCreatedPackages : CleanedPackage [ ] } => {
151+ const allCreatedPackages : CleanedPackage [ ] = [ ] ;
152+ let currentPackages = parentPackages ;
153+ let deepestPackage : CleanedPackage | null = null ;
154+
155+ for ( let i = 0 ; i < packageNameParts . length ; i ++ ) {
156+ const partName = packageNameParts [ i ] ;
157+ let foundPackage = currentPackages . find ( ( pkg ) => pkg . name === partName ) ;
158+
159+ if ( ! foundPackage ) {
160+ // Create new package
161+ foundPackage = {
162+ name : partName ,
163+ classes : [ ] ,
164+ subpackages : [ ] ,
165+ } ;
166+ allCreatedPackages . push ( foundPackage ) ;
167+ // Add to current packages array (either parentPackages for root, or deepestPackage.subpackages for nested)
168+ if ( deepestPackage ) {
169+ deepestPackage . subpackages . push ( foundPackage ) ;
170+ } else {
171+ // This is the first level - add to parentPackages array
172+ parentPackages . push ( foundPackage ) ;
173+ }
174+ }
175+
176+ deepestPackage = foundPackage ;
177+ currentPackages = foundPackage . subpackages ;
178+ }
179+
180+ if ( ! deepestPackage ) {
181+ throw new Error ( 'Failed to create package hierarchy' ) ;
182+ }
183+
184+ return { deepestPackage, allCreatedPackages } ;
185+ } ;
186+
143187 const updateLocalLandscape = ( updated : CleanedLandscape [ ] ) => {
144188 isInternalUpdateRef . current = true ;
145189 setLocalLandscape ( updated ) ;
@@ -247,15 +291,18 @@ export function LandscapeEditor({ landscape, onLandscapeUpdated, onError }: Land
247291 if ( packageName && packageName . trim ( ) !== '' ) {
248292 const updated = [ ...localLandscape ] ;
249293 const app = updated [ appIdx ] ;
250- const newPkg : CleanedPackage = {
251- name : packageName . trim ( ) ,
252- classes : [ ] ,
253- subpackages : [ ] ,
254- } ;
294+ const packageNameParts = packageName . trim ( ) . split ( '.' ) ;
295+
296+ // Create a copy of rootPackages to avoid mutating the original
297+ const rootPackagesCopy = [ ...app . rootPackages ] ;
298+
299+ // Create or find the package hierarchy
300+ const { allCreatedPackages } = createPackageHierarchy ( rootPackagesCopy , packageNameParts ) ;
301+
255302 updated [ appIdx ] = {
256303 ...app ,
257- rootPackages : [ ... app . rootPackages , newPkg ] ,
258- packages : [ ...app . packages , newPkg ] ,
304+ rootPackages : rootPackagesCopy ,
305+ packages : [ ...app . packages , ... allCreatedPackages ] ,
259306 } ;
260307 updateLocalLandscape ( updated ) ;
261308 }
@@ -264,6 +311,30 @@ export function LandscapeEditor({ landscape, onLandscapeUpdated, onError }: Land
264311 const addPackage = ( appIdx : number ) => {
265312 const packageName = prompt ( 'Enter package name:' , 'newpackage' ) ;
266313 if ( packageName && packageName . trim ( ) !== '' ) {
314+ const trimmedName = packageName . trim ( ) ;
315+
316+ // If the package name contains dots, treat it as a hierarchy and add as root package
317+ if ( trimmedName . includes ( '.' ) ) {
318+ const updated = [ ...localLandscape ] ;
319+ const app = updated [ appIdx ] ;
320+ const packageNameParts = trimmedName . split ( '.' ) ;
321+
322+ // Create a copy of rootPackages to avoid mutating the original
323+ const rootPackagesCopy = [ ...app . rootPackages ] ;
324+
325+ // Create or find the package hierarchy
326+ const { allCreatedPackages } = createPackageHierarchy ( rootPackagesCopy , packageNameParts ) ;
327+
328+ updated [ appIdx ] = {
329+ ...app ,
330+ rootPackages : rootPackagesCopy ,
331+ packages : [ ...app . packages , ...allCreatedPackages ] ,
332+ } ;
333+ updateLocalLandscape ( updated ) ;
334+ return ;
335+ }
336+
337+ // For non-hierarchical names, use the existing behavior
267338 const updated = [ ...localLandscape ] ;
268339 const app = updated [ appIdx ] ;
269340 // Find the first root package to add subpackage to
@@ -280,7 +351,7 @@ export function LandscapeEditor({ landscape, onLandscapeUpdated, onError }: Land
280351 }
281352 }
282353 const newPkg : CleanedPackage = {
283- name : packageName . trim ( ) ,
354+ name : trimmedName ,
284355 classes : [ ] ,
285356 subpackages : [ ] ,
286357 } ;
@@ -304,21 +375,44 @@ export function LandscapeEditor({ landscape, onLandscapeUpdated, onError }: Land
304375 if ( packageName && packageName . trim ( ) !== '' ) {
305376 const updated = [ ...localLandscape ] ;
306377 const app = updated [ appIdx ] ;
307- const newPkg : CleanedPackage = {
308- name : packageName . trim ( ) ,
309- classes : [ ] ,
310- subpackages : [ ] ,
378+ const packageNameParts = packageName . trim ( ) . split ( '.' ) ;
379+
380+ // Find the parent package in the updated landscape
381+ const findPkgInUpdated = ( p : CleanedPackage ) : CleanedPackage | null => {
382+ if ( p . name === parentPackageName ) return p ;
383+ for ( const subPkg of p . subpackages ) {
384+ const found = findPkgInUpdated ( subPkg ) ;
385+ if ( found ) return found ;
386+ }
387+ return null ;
311388 } ;
389+
390+ let parentPkg : CleanedPackage | null = null ;
391+ for ( const rootPkg of app . rootPackages ) {
392+ parentPkg = findPkgInUpdated ( rootPkg ) ;
393+ if ( parentPkg ) break ;
394+ }
395+
396+ if ( ! parentPkg ) {
397+ onError ( `Parent package "${ parentPackageName } " not found` ) ;
398+ return ;
399+ }
400+
401+ // Create or find the package hierarchy under the parent
402+ const { allCreatedPackages } = createPackageHierarchy ( parentPkg . subpackages , packageNameParts ) ;
403+
404+ // Update the parent package in the tree to ensure React detects the change
312405 const updatePkg = ( p : CleanedPackage ) : CleanedPackage => {
313406 if ( p . name === parentPackageName ) {
314- return { ...p , subpackages : [ ...p . subpackages , newPkg ] } ;
407+ return { ...p , subpackages : [ ...parentPkg ! . subpackages ] } ;
315408 }
316409 return { ...p , subpackages : p . subpackages . map ( updatePkg ) } ;
317410 } ;
411+
318412 updated [ appIdx ] = {
319413 ...app ,
320414 rootPackages : app . rootPackages . map ( updatePkg ) ,
321- packages : [ ...app . packages , newPkg ] ,
415+ packages : [ ...app . packages , ... allCreatedPackages ] ,
322416 } ;
323417 updateLocalLandscape ( updated ) ;
324418 }
0 commit comments