@vendure/dashboard 3.3.6-master-202507090236 → 3.3.6-master-202507110238

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/dist/plugin/tests/barrel-exports.spec.js +1 -1
  2. package/package.json +4 -4
  3. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +30 -37
  4. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +33 -53
  5. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +14 -7
  6. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +23 -12
  7. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +364 -0
  8. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +222 -0
  9. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +146 -85
  10. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +268 -31
  11. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +80 -0
  12. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +102 -0
  13. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +144 -0
  14. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +118 -2
  15. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +144 -52
  16. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +550 -0
  17. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -17
  18. package/src/app/routes/_authenticated/_orders/utils/order-types.ts +5 -2
  19. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +4 -3
  20. package/src/app/routes/_authenticated/_products/products_.$id.tsx +0 -1
  21. package/src/lib/components/data-display/date-time.tsx +7 -1
  22. package/src/lib/components/data-input/relation-input.tsx +11 -0
  23. package/src/lib/components/data-input/relation-selector.tsx +9 -2
  24. package/src/lib/components/data-table/data-table-utils.ts +34 -0
  25. package/src/lib/components/data-table/data-table-view-options.tsx +2 -2
  26. package/src/lib/components/data-table/data-table.tsx +5 -2
  27. package/src/lib/components/data-table/use-generated-columns.tsx +307 -0
  28. package/src/lib/components/shared/paginated-list-data-table.tsx +15 -286
  29. package/src/lib/components/shared/product-variant-selector.tsx +28 -4
  30. package/src/lib/framework/component-registry/dynamic-component.tsx +3 -3
  31. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +321 -2
  32. package/src/lib/framework/document-introspection/get-document-structure.ts +149 -31
  33. package/src/lib/framework/extension-api/types/layout.ts +21 -6
  34. package/src/lib/framework/layout-engine/layout-extensions.ts +1 -4
  35. package/src/lib/framework/layout-engine/page-layout.tsx +61 -10
  36. package/src/lib/framework/page/use-detail-page.ts +10 -7
  37. package/vite/tests/barrel-exports.spec.ts +1 -1
@@ -1,7 +1,11 @@
1
1
  import { graphql } from 'gql.tada';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { getListQueryFields, getOperationVariablesFields } from './get-document-structure.js';
4
+ import {
5
+ getFieldsFromDocumentNode,
6
+ getListQueryFields,
7
+ getOperationVariablesFields,
8
+ } from './get-document-structure.js';
5
9
 
6
10
  vi.mock('virtual:admin-api-schema', () => {
7
11
  return {
@@ -11,6 +15,7 @@ vi.mock('virtual:admin-api-schema', () => {
11
15
  products: ['ProductList', false, false, true],
12
16
  product: ['Product', false, false, false],
13
17
  collection: ['Collection', false, false, false],
18
+ order: ['Order', false, false, false],
14
19
  },
15
20
  Mutation: {
16
21
  updateProduct: ['Product', false, false, false],
@@ -148,7 +153,17 @@ vi.mock('virtual:admin-api-schema', () => {
148
153
  quantity: ['Int', false, false, false],
149
154
  },
150
155
  },
151
- scalars: ['ID', 'String', 'Int', 'Boolean', 'Float', 'JSON', 'DateTime', 'Upload', 'Money'],
156
+ scalars: [
157
+ 'ID',
158
+ 'String',
159
+ 'Int',
160
+ 'Boolean',
161
+ 'Float',
162
+ 'JSON',
163
+ 'DateTime',
164
+ 'Upload',
165
+ 'CurrencyCode',
166
+ ],
152
167
  enums: {},
153
168
  },
154
169
  };
@@ -372,6 +387,9 @@ describe('getOperationVariablesFields', () => {
372
387
  mutation AdjustDraftOrderLine($orderId: ID!, $input: AdjustDraftOrderLineInput!) {
373
388
  adjustDraftOrderLine(orderId: $orderId, input: $input) {
374
389
  id
390
+ lines {
391
+ id
392
+ }
375
393
  }
376
394
  }
377
395
  `);
@@ -418,3 +436,304 @@ describe('getOperationVariablesFields', () => {
418
436
  ]);
419
437
  });
420
438
  });
439
+
440
+ describe('getFieldsFromDocumentNode', () => {
441
+ it('should extract fields from a simple path', () => {
442
+ const doc = graphql(`
443
+ query {
444
+ order(id: "1") {
445
+ id
446
+ lines {
447
+ id
448
+ quantity
449
+ }
450
+ }
451
+ }
452
+ `);
453
+
454
+ const fields = getFieldsFromDocumentNode(doc, ['order', 'lines']);
455
+ expect(fields).toEqual([
456
+ {
457
+ isPaginatedList: false,
458
+ isScalar: true,
459
+ list: false,
460
+ name: 'id',
461
+ nullable: false,
462
+ type: 'ID',
463
+ },
464
+ {
465
+ isPaginatedList: false,
466
+ isScalar: true,
467
+ list: false,
468
+ name: 'quantity',
469
+ nullable: false,
470
+ type: 'Int',
471
+ },
472
+ ]);
473
+ });
474
+
475
+ it('should extract fields from root level', () => {
476
+ const doc = graphql(`
477
+ query {
478
+ product {
479
+ id
480
+ name
481
+ description
482
+ }
483
+ }
484
+ `);
485
+
486
+ const fields = getFieldsFromDocumentNode(doc, ['product']);
487
+ expect(fields).toEqual([
488
+ {
489
+ isPaginatedList: false,
490
+ isScalar: true,
491
+ list: false,
492
+ name: 'id',
493
+ nullable: false,
494
+ type: 'ID',
495
+ },
496
+ {
497
+ isPaginatedList: false,
498
+ isScalar: true,
499
+ list: false,
500
+ name: 'name',
501
+ nullable: false,
502
+ type: 'String',
503
+ },
504
+ {
505
+ isPaginatedList: false,
506
+ isScalar: true,
507
+ list: false,
508
+ name: 'description',
509
+ nullable: false,
510
+ type: 'String',
511
+ },
512
+ ]);
513
+ });
514
+
515
+ it('should handle fragments in the target selection', () => {
516
+ const doc = graphql(`
517
+ query {
518
+ product {
519
+ id
520
+ featuredAsset {
521
+ ...AssetFields
522
+ }
523
+ }
524
+ }
525
+
526
+ fragment AssetFields on Asset {
527
+ id
528
+ name
529
+ preview
530
+ }
531
+ `);
532
+
533
+ const fields = getFieldsFromDocumentNode(doc, ['product', 'featuredAsset']);
534
+ expect(fields).toEqual([
535
+ {
536
+ isPaginatedList: false,
537
+ isScalar: true,
538
+ list: false,
539
+ name: 'id',
540
+ nullable: false,
541
+ type: 'ID',
542
+ },
543
+ {
544
+ isPaginatedList: false,
545
+ isScalar: true,
546
+ list: false,
547
+ name: 'name',
548
+ nullable: false,
549
+ type: 'String',
550
+ },
551
+ {
552
+ isPaginatedList: false,
553
+ isScalar: true,
554
+ list: false,
555
+ name: 'preview',
556
+ nullable: false,
557
+ type: 'String',
558
+ },
559
+ ]);
560
+ });
561
+
562
+ it('should handle deep nested paths', () => {
563
+ const doc = graphql(`
564
+ query {
565
+ product {
566
+ variants {
567
+ prices {
568
+ currencyCode
569
+ price
570
+ }
571
+ }
572
+ }
573
+ }
574
+ `);
575
+
576
+ const fields = getFieldsFromDocumentNode(doc, ['product', 'variants', 'prices']);
577
+ expect(fields).toEqual([
578
+ {
579
+ isPaginatedList: false,
580
+ isScalar: true,
581
+ list: false,
582
+ name: 'currencyCode',
583
+ nullable: false,
584
+ type: 'CurrencyCode',
585
+ },
586
+ {
587
+ isPaginatedList: false,
588
+ isScalar: false,
589
+ list: false,
590
+ name: 'price',
591
+ nullable: false,
592
+ type: 'Money',
593
+ },
594
+ ]);
595
+ });
596
+
597
+ it('should throw error for non-existent field', () => {
598
+ const doc = graphql(`
599
+ query {
600
+ product {
601
+ id
602
+ }
603
+ }
604
+ `);
605
+
606
+ expect(() => getFieldsFromDocumentNode(doc, ['product', 'nonExistentField'])).toThrow(
607
+ 'Field "nonExistentField" not found at path product.nonExistentField',
608
+ );
609
+ });
610
+
611
+ it('should throw error for non-existent path', () => {
612
+ const doc = graphql(`
613
+ query {
614
+ product {
615
+ id
616
+ }
617
+ }
618
+ `);
619
+
620
+ expect(() => getFieldsFromDocumentNode(doc, ['nonExistentProduct'])).toThrow(
621
+ 'Field "nonExistentProduct" not found at path nonExistentProduct',
622
+ );
623
+ });
624
+
625
+ it('should throw error when field has no selection set but path continues', () => {
626
+ const doc = graphql(`
627
+ query {
628
+ product {
629
+ id
630
+ }
631
+ }
632
+ `);
633
+
634
+ expect(() => getFieldsFromDocumentNode(doc, ['product', 'id', 'something'])).toThrow(
635
+ 'Field "id" has no selection set but path continues',
636
+ );
637
+ });
638
+
639
+ it('should handle empty selection set', () => {
640
+ const doc = graphql(`
641
+ query {
642
+ product {
643
+ id
644
+ name
645
+ }
646
+ }
647
+ `);
648
+
649
+ // Test with a path that leads to a field with no selection set
650
+ expect(() => getFieldsFromDocumentNode(doc, ['product', 'id', 'something'])).toThrow(
651
+ 'Field "id" has no selection set but path continues',
652
+ );
653
+ });
654
+
655
+ it('should handle mixed field types and fragments', () => {
656
+ const doc = graphql(`
657
+ query {
658
+ product {
659
+ id
660
+ featuredAsset {
661
+ ...AssetFields
662
+ fileSize
663
+ }
664
+ }
665
+ }
666
+
667
+ fragment AssetFields on Asset {
668
+ id
669
+ name
670
+ }
671
+ `);
672
+
673
+ const fields = getFieldsFromDocumentNode(doc, ['product', 'featuredAsset']);
674
+ expect(fields).toEqual([
675
+ {
676
+ isPaginatedList: false,
677
+ isScalar: true,
678
+ list: false,
679
+ name: 'id',
680
+ nullable: false,
681
+ type: 'ID',
682
+ },
683
+ {
684
+ isPaginatedList: false,
685
+ isScalar: true,
686
+ list: false,
687
+ name: 'name',
688
+ nullable: false,
689
+ type: 'String',
690
+ },
691
+ {
692
+ isPaginatedList: false,
693
+ isScalar: true,
694
+ list: false,
695
+ name: 'fileSize',
696
+ nullable: false,
697
+ type: 'Int',
698
+ },
699
+ ]);
700
+ });
701
+
702
+ it('should handle fields within fragment spreads', () => {
703
+ const doc = graphql(`
704
+ query {
705
+ order {
706
+ ...OrderFields
707
+ }
708
+ }
709
+
710
+ fragment OrderFields on Order {
711
+ id
712
+ lines {
713
+ id
714
+ quantity
715
+ }
716
+ }
717
+ `);
718
+
719
+ const fields = getFieldsFromDocumentNode(doc, ['order', 'lines']);
720
+ expect(fields).toEqual([
721
+ {
722
+ isPaginatedList: false,
723
+ isScalar: true,
724
+ list: false,
725
+ name: 'id',
726
+ nullable: false,
727
+ type: 'ID',
728
+ },
729
+ {
730
+ isPaginatedList: false,
731
+ isScalar: true,
732
+ list: false,
733
+ name: 'quantity',
734
+ nullable: false,
735
+ type: 'Int',
736
+ },
737
+ ]);
738
+ });
739
+ });
@@ -1,5 +1,5 @@
1
1
  import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
- import { VariablesOf } from 'gql.tada';
2
+ import { ResultOf, VariablesOf } from 'gql.tada';
3
3
  import {
4
4
  DocumentNode,
5
5
  FieldNode,
@@ -32,33 +32,165 @@ export interface FieldInfo {
32
32
  */
33
33
  export function getListQueryFields(documentNode: DocumentNode): FieldInfo[] {
34
34
  const fields: FieldInfo[] = [];
35
- const fragments: Record<string, FragmentDefinitionNode> = {};
35
+ const fragments = collectFragments(documentNode);
36
+ const operationDefinition = findQueryOperation(documentNode);
36
37
 
37
- // Collect all fragment definitions
38
+ for (const query of operationDefinition.selectionSet.selections) {
39
+ if (query.kind === 'Field') {
40
+ const queryField = query;
41
+ const fieldInfo = getQueryInfo(queryField.name.value);
42
+ if (fieldInfo.isPaginatedList) {
43
+ processPaginatedList(queryField, fieldInfo, fields, fragments);
44
+ } else if (queryField.selectionSet) {
45
+ // Check for nested paginated lists
46
+ findNestedPaginatedLists(queryField, fieldInfo.type, fields, fragments);
47
+ }
48
+ }
49
+ }
50
+
51
+ return fields;
52
+ }
53
+
54
+ // Utility type to get all valid paths into a type
55
+ export type PathTo<T> = T extends object
56
+ ? {
57
+ [K in keyof T & (string | number)]: [K] | [K, ...PathTo<T[K]>];
58
+ }[keyof T & (string | number)]
59
+ : [];
60
+
61
+ /**
62
+ * @description
63
+ * This function is used to get the FieldInfo for the fields in the path of a DocumentNode.
64
+ *
65
+ * For example, in the following query:
66
+ *
67
+ * ```graphql
68
+ * query {
69
+ * product {
70
+ * id
71
+ * name
72
+ * variants {
73
+ * id
74
+ * name
75
+ * }
76
+ * }
77
+ * }
78
+ * ```
79
+ *
80
+ * The path `['product', 'variants']` will return the FieldInfo for the `variants` field,
81
+ * namely the `id` and `name` fields.
82
+ */
83
+ export function getFieldsFromDocumentNode<
84
+ T extends TypedDocumentNode<any, any>,
85
+ P extends PathTo<ResultOf<T>>,
86
+ >(documentNode: T, path: P): FieldInfo[] {
87
+ const fragments = collectFragments(documentNode);
88
+ const operationDefinition = findQueryOperation(documentNode);
89
+
90
+ // Navigate to the target path
91
+ let currentSelections = operationDefinition.selectionSet.selections;
92
+ let currentType = 'Query';
93
+
94
+ for (let i = 0; i < path.length; i++) {
95
+ const pathSegment = path[i] as string;
96
+ const { fieldNode } = findFieldInSelections(
97
+ currentSelections,
98
+ pathSegment,
99
+ fragments,
100
+ path.slice(0, i + 1),
101
+ );
102
+
103
+ const fieldInfo = getObjectFieldInfo(currentType, pathSegment);
104
+ if (!fieldInfo) {
105
+ throw new Error(`Could not determine type for field "${pathSegment}"`);
106
+ }
107
+
108
+ // If this is the last path segment, collect the fields
109
+ if (i === path.length - 1) {
110
+ return collectFieldsFromNode(fieldNode, fieldInfo.type, fragments);
111
+ }
112
+
113
+ // Continue navigating deeper
114
+ if (!fieldNode.selectionSet) {
115
+ throw new Error(`Field "${pathSegment}" has no selection set but path continues`);
116
+ }
117
+ currentSelections = fieldNode.selectionSet.selections;
118
+ currentType = fieldInfo.type;
119
+ }
120
+
121
+ return [];
122
+ }
123
+
124
+ function collectFragments(documentNode: DocumentNode): Record<string, FragmentDefinitionNode> {
125
+ const fragments: Record<string, FragmentDefinitionNode> = {};
38
126
  documentNode.definitions.forEach(def => {
39
127
  if (def.kind === 'FragmentDefinition') {
40
128
  fragments[def.name.value] = def;
41
129
  }
42
130
  });
131
+ return fragments;
132
+ }
43
133
 
134
+ function findQueryOperation(documentNode: DocumentNode): OperationDefinitionNode {
44
135
  const operationDefinition = documentNode.definitions.find(
45
136
  (def): def is OperationDefinitionNode =>
46
137
  def.kind === 'OperationDefinition' && def.operation === 'query',
47
138
  );
139
+ if (!operationDefinition) {
140
+ throw new Error('Could not find query operation definition');
141
+ }
142
+ return operationDefinition;
143
+ }
144
+
145
+ function findMutationOperation(documentNode: DocumentNode): OperationDefinitionNode {
146
+ const operationDefinition = documentNode.definitions.find(
147
+ (def): def is OperationDefinitionNode =>
148
+ def.kind === 'OperationDefinition' && def.operation === 'mutation',
149
+ );
150
+ if (!operationDefinition) {
151
+ throw new Error('Could not find mutation operation definition');
152
+ }
153
+ return operationDefinition;
154
+ }
48
155
 
49
- for (const query of operationDefinition?.selectionSet.selections ?? []) {
50
- if (query.kind === 'Field') {
51
- const queryField = query;
52
- const fieldInfo = getQueryInfo(queryField.name.value);
53
- if (fieldInfo.isPaginatedList) {
54
- processPaginatedList(queryField, fieldInfo, fields, fragments);
55
- } else if (queryField.selectionSet) {
56
- // Check for nested paginated lists
57
- findNestedPaginatedLists(queryField, fieldInfo.type, fields, fragments);
156
+ function findFieldInSelections(
157
+ selections: readonly any[],
158
+ pathSegment: string,
159
+ fragments: Record<string, FragmentDefinitionNode>,
160
+ currentPath: string[] = [],
161
+ ): { fieldNode: FieldNode; fragmentSelections: readonly any[] } {
162
+ for (const selection of selections) {
163
+ if (selection.kind === 'Field' && selection.name.value === pathSegment) {
164
+ return { fieldNode: selection, fragmentSelections: [] };
165
+ } else if (selection.kind === 'FragmentSpread') {
166
+ const fragment = fragments[selection.name.value];
167
+ if (fragment) {
168
+ const fragmentField = fragment.selectionSet.selections.find(
169
+ s => s.kind === 'Field' && s.name.value === pathSegment,
170
+ ) as FieldNode;
171
+ if (fragmentField) {
172
+ return { fieldNode: fragmentField, fragmentSelections: fragment.selectionSet.selections };
173
+ }
58
174
  }
59
175
  }
60
176
  }
177
+ const pathString = currentPath.join('.');
178
+ throw new Error(`Field "${pathSegment}" not found at path ${pathString}`);
179
+ }
61
180
 
181
+ function collectFieldsFromNode(
182
+ fieldNode: FieldNode,
183
+ typeName: string,
184
+ fragments: Record<string, FragmentDefinitionNode>,
185
+ ): FieldInfo[] {
186
+ const fields: FieldInfo[] = [];
187
+ if (fieldNode.selectionSet) {
188
+ for (const selection of fieldNode.selectionSet.selections) {
189
+ if (selection.kind === 'Field' || selection.kind === 'FragmentSpread') {
190
+ collectFields(typeName, selection, fields, fragments);
191
+ }
192
+ }
193
+ }
62
194
  return fields;
63
195
  }
64
196
 
@@ -204,11 +336,8 @@ function unwrapVariableDefinitionType(type: TypeNode): NamedTypeNode {
204
336
  * Helper function to get the first field selection from a query operation definition.
205
337
  */
206
338
  function getFirstQueryField(documentNode: DocumentNode): FieldNode {
207
- const operationDefinition = documentNode.definitions.find(
208
- (def): def is OperationDefinitionNode =>
209
- def.kind === 'OperationDefinition' && def.operation === 'query',
210
- );
211
- const firstSelection = operationDefinition?.selectionSet.selections[0];
339
+ const operationDefinition = findQueryOperation(documentNode);
340
+ const firstSelection = operationDefinition.selectionSet.selections[0];
212
341
  if (firstSelection?.kind === 'Field') {
213
342
  return firstSelection;
214
343
  } else {
@@ -305,15 +434,7 @@ export function getObjectPathToPaginatedList(
305
434
  documentNode: DocumentNode,
306
435
  currentPath: string[] = [],
307
436
  ): string[] {
308
- // get the query OperationDefinition
309
- const operationDefinition = documentNode.definitions.find(
310
- (def): def is OperationDefinitionNode =>
311
- def.kind === 'OperationDefinition' && def.operation === 'query',
312
- );
313
- if (!operationDefinition) {
314
- throw new Error('Could not find query operation definition');
315
- }
316
-
437
+ const operationDefinition = findQueryOperation(documentNode);
317
438
  return findPaginatedListPath(operationDefinition.selectionSet, 'Query', currentPath);
318
439
  }
319
440
 
@@ -365,11 +486,8 @@ function findPaginatedListPath(
365
486
  * The mutation name is `createProduct`.
366
487
  */
367
488
  export function getMutationName(documentNode: DocumentNode): string {
368
- const operationDefinition = documentNode.definitions.find(
369
- (def): def is OperationDefinitionNode =>
370
- def.kind === 'OperationDefinition' && def.operation === 'mutation',
371
- );
372
- const firstSelection = operationDefinition?.selectionSet.selections[0];
489
+ const operationDefinition = findMutationOperation(documentNode);
490
+ const firstSelection = operationDefinition.selectionSet.selections[0];
373
491
  if (firstSelection?.kind === 'Field') {
374
492
  return firstSelection.name.value;
375
493
  } else {
@@ -27,6 +27,27 @@ export interface DashboardActionBarItem {
27
27
  * A React component that will be rendered in the action bar.
28
28
  */
29
29
  component: React.FunctionComponent<{ context: PageContextValue }>;
30
+ /**
31
+ * @description
32
+ * The type of action bar item to display. Defaults to `button`.
33
+ * The 'dropdown' type is used to display the action bar item as a dropdown menu item.
34
+ *
35
+ * When using the dropdown type, use a suitable [dropdown item](https://ui.shadcn.com/docs/components/dropdown-menu)
36
+ * component, such as:
37
+ *
38
+ * ```tsx
39
+ * import { DropdownMenuItem } from '\@vendure/dashboard';
40
+ *
41
+ * // ...
42
+ *
43
+ * {
44
+ * component: () => <DropdownMenuItem>My Item</DropdownMenuItem>
45
+ * }
46
+ * ```
47
+ *
48
+ * @default 'button'
49
+ */
50
+ type?: 'button' | 'dropdown';
30
51
  /**
31
52
  * @description
32
53
  * Any permissions that are required to display this action bar item.
@@ -34,12 +55,6 @@ export interface DashboardActionBarItem {
34
55
  requiresPermission?: string | string[];
35
56
  }
36
57
 
37
- export interface DashboardActionBarDropdownMenuItem {
38
- locationId: string;
39
- component: React.FunctionComponent<{ context: PageContextValue }>;
40
- requiresPermission?: string | string[];
41
- }
42
-
43
58
  export type PageBlockPosition = { blockId: string; order: 'before' | 'after' | 'replace' };
44
59
 
45
60
  /**
@@ -1,7 +1,4 @@
1
- import {
2
- DashboardActionBarItem,
3
- DashboardPageBlockDefinition,
4
- } from '../extension-api/extension-api-types.js';
1
+ import { DashboardActionBarItem, DashboardPageBlockDefinition } from '../extension-api/types/layout.js';
5
2
  import { globalRegistry } from '../registry/global-registry.js';
6
3
 
7
4
  globalRegistry.register('dashboardActionBarItemRegistry', new Map<string, DashboardActionBarItem[]>());