@aws-amplify/ui 6.7.0 → 6.7.2

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.
@@ -99,6 +99,11 @@ const darkModeTokens = {
99
99
  secondary: '{colors.neutral.40}',
100
100
  tertiary: '{colors.neutral.20}',
101
101
  },
102
+ shadow: {
103
+ primary: { value: 'hsla(100, 100%, 100%, 0.25)' },
104
+ secondary: { value: 'hsla(100, 100%, 100%, 0.15)' },
105
+ tertiary: { value: 'hsla(100, 100%, 100%, 0.05)' },
106
+ },
102
107
  overlay: {
103
108
  5: 'hsla(0, 0%, 100%, 0.05)',
104
109
  10: 'hsla(0, 0%, 100%, 0.1)',
@@ -30,6 +30,7 @@ const ComponentClassName = {
30
30
  AIConversationAttachmentRemove: 'amplify-ai-conversation__attachment__remove',
31
31
  AIConversationForm: 'amplify-ai-conversation__form',
32
32
  AIConversationFormAttach: 'amplify-ai-conversation__form__attach',
33
+ AIConversationFormError: 'amplify-ai-conversation__form__error',
33
34
  AIConversationFormSend: 'amplify-ai-conversation__form__send',
34
35
  AIConversationFormField: 'amplify-ai-conversation__form__field',
35
36
  AIConversationFormDropzone: 'amplify-ai-conversation__form__dropzone',
@@ -55,5 +55,17 @@ const STORAGE_MANAGER_INPUT_BASE = {
55
55
  apis: [StorageAction.UploadData],
56
56
  category: Category.Storage,
57
57
  };
58
+ const STORAGE_BROWSER_INPUT_BASE = {
59
+ apis: [
60
+ StorageAction.UploadData,
61
+ StorageAction.Copy,
62
+ StorageAction.GetUrl,
63
+ StorageAction.List,
64
+ StorageAction.Remove,
65
+ StorageAction.GetDataAccess,
66
+ StorageAction.ListCallerAccessGrants,
67
+ ],
68
+ category: Category.Storage,
69
+ };
58
70
 
59
- export { ACCOUNT_SETTINGS_INPUT_BASE, AI_INPUT_BASE, AUTHENTICATOR_INPUT_BASE, FILE_UPLOADER_BASE_INPUT, IN_APP_MESSAGING_INPUT_BASE, LOCATION_SEARCH_INPUT_BASE, MAP_VIEW_INPUT_BASE, STORAGE_MANAGER_INPUT_BASE };
71
+ export { ACCOUNT_SETTINGS_INPUT_BASE, AI_INPUT_BASE, AUTHENTICATOR_INPUT_BASE, FILE_UPLOADER_BASE_INPUT, IN_APP_MESSAGING_INPUT_BASE, LOCATION_SEARCH_INPUT_BASE, MAP_VIEW_INPUT_BASE, STORAGE_BROWSER_INPUT_BASE, STORAGE_MANAGER_INPUT_BASE };
@@ -1,5 +1,5 @@
1
1
  import { setCustomUserAgent } from '@aws-amplify/core/internals/utils';
2
- import { STORAGE_MANAGER_INPUT_BASE, MAP_VIEW_INPUT_BASE, LOCATION_SEARCH_INPUT_BASE, IN_APP_MESSAGING_INPUT_BASE, FILE_UPLOADER_BASE_INPUT, ACCOUNT_SETTINGS_INPUT_BASE, AUTHENTICATOR_INPUT_BASE, AI_INPUT_BASE } from './constants.mjs';
2
+ import { STORAGE_BROWSER_INPUT_BASE, STORAGE_MANAGER_INPUT_BASE, MAP_VIEW_INPUT_BASE, LOCATION_SEARCH_INPUT_BASE, IN_APP_MESSAGING_INPUT_BASE, FILE_UPLOADER_BASE_INPUT, ACCOUNT_SETTINGS_INPUT_BASE, AUTHENTICATOR_INPUT_BASE, AI_INPUT_BASE } from './constants.mjs';
3
3
  import { noop } from '../utils.mjs';
4
4
 
5
5
  /**
@@ -72,6 +72,13 @@ const setUserAgent = ({ componentName, packageName, version, }) => {
72
72
  });
73
73
  break;
74
74
  }
75
+ case 'StorageBrowser': {
76
+ setCustomUserAgent({
77
+ ...STORAGE_BROWSER_INPUT_BASE,
78
+ additionalDetails: [[componentName], packageData],
79
+ });
80
+ break;
81
+ }
75
82
  }
76
83
  return noop;
77
84
  };
package/dist/index.js CHANGED
@@ -91,6 +91,18 @@ const STORAGE_MANAGER_INPUT_BASE = {
91
91
  apis: [utils.StorageAction.UploadData],
92
92
  category: utils.Category.Storage,
93
93
  };
94
+ const STORAGE_BROWSER_INPUT_BASE = {
95
+ apis: [
96
+ utils.StorageAction.UploadData,
97
+ utils.StorageAction.Copy,
98
+ utils.StorageAction.GetUrl,
99
+ utils.StorageAction.List,
100
+ utils.StorageAction.Remove,
101
+ utils.StorageAction.GetDataAccess,
102
+ utils.StorageAction.ListCallerAccessGrants,
103
+ ],
104
+ category: utils.Category.Storage,
105
+ };
94
106
 
95
107
  /**
96
108
  * Some libraries may not follow Node ES module spec and could be loaded as CommonJS modules,
@@ -416,6 +428,13 @@ const setUserAgent = ({ componentName, packageName, version, }) => {
416
428
  });
417
429
  break;
418
430
  }
431
+ case 'StorageBrowser': {
432
+ utils.setCustomUserAgent({
433
+ ...STORAGE_BROWSER_INPUT_BASE,
434
+ additionalDetails: [[componentName], packageData],
435
+ });
436
+ break;
437
+ }
419
438
  }
420
439
  return noop;
421
440
  };
@@ -3009,6 +3028,7 @@ const ComponentClassName = {
3009
3028
  AIConversationAttachmentRemove: 'amplify-ai-conversation__attachment__remove',
3010
3029
  AIConversationForm: 'amplify-ai-conversation__form',
3011
3030
  AIConversationFormAttach: 'amplify-ai-conversation__form__attach',
3031
+ AIConversationFormError: 'amplify-ai-conversation__form__error',
3012
3032
  AIConversationFormSend: 'amplify-ai-conversation__form__send',
3013
3033
  AIConversationFormField: 'amplify-ai-conversation__form__field',
3014
3034
  AIConversationFormDropzone: 'amplify-ai-conversation__form__dropzone',
@@ -9429,6 +9449,11 @@ const darkModeTokens = {
9429
9449
  secondary: '{colors.neutral.40}',
9430
9450
  tertiary: '{colors.neutral.20}',
9431
9451
  },
9452
+ shadow: {
9453
+ primary: { value: 'hsla(100, 100%, 100%, 0.25)' },
9454
+ secondary: { value: 'hsla(100, 100%, 100%, 0.15)' },
9455
+ tertiary: { value: 'hsla(100, 100%, 100%, 0.05)' },
9456
+ },
9432
9457
  overlay: {
9433
9458
  5: 'hsla(0, 0%, 100%, 0.05)',
9434
9459
  10: 'hsla(0, 0%, 100%, 0.1)',
@@ -89,12 +89,16 @@
89
89
  flex-direction: row;
90
90
  align-items: flex-start;
91
91
  gap: var(--amplify-components-ai-conversation-form-gap);
92
- padding: var(--amplify-components-ai-conversation-form-padding);
93
92
  }
94
93
  .amplify-ai-conversation__form__dropzone {
95
94
  text-align: initial;
96
95
  border: none;
96
+ padding: var(--amplify-components-ai-conversation-form-padding);
97
+ }
98
+ .amplify-ai-conversation__form__error {
97
99
  padding: 0;
100
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
101
+ gap: var(--amplify-components-ai-conversation-attachment-gap);
98
102
  }
99
103
  .amplify-ai-conversation__attachment {
100
104
  display: flex;
@@ -114,7 +118,7 @@
114
118
  flex-direction: row;
115
119
  flex-wrap: wrap;
116
120
  gap: var(--amplify-components-ai-conversation-attachment-list-gap);
117
- padding-block-start: var(--amplify-components-ai-conversation-attachment-padding-block-start);
121
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
118
122
  }
119
123
  .amplify-ai-conversation__attachment__image {
120
124
  width: var(--amplify-components-ai-conversation-attachment-image-width);
@@ -90,12 +90,16 @@
90
90
  flex-direction: row;
91
91
  align-items: flex-start;
92
92
  gap: var(--amplify-components-ai-conversation-form-gap);
93
- padding: var(--amplify-components-ai-conversation-form-padding);
94
93
  }
95
94
  .amplify-ai-conversation__form__dropzone {
96
95
  text-align: initial;
97
96
  border: none;
97
+ padding: var(--amplify-components-ai-conversation-form-padding);
98
+ }
99
+ .amplify-ai-conversation__form__error {
98
100
  padding: 0;
101
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
102
+ gap: var(--amplify-components-ai-conversation-attachment-gap);
99
103
  }
100
104
  .amplify-ai-conversation__attachment {
101
105
  display: flex;
@@ -115,7 +119,7 @@
115
119
  flex-direction: row;
116
120
  flex-wrap: wrap;
117
121
  gap: var(--amplify-components-ai-conversation-attachment-list-gap);
118
- padding-block-start: var(--amplify-components-ai-conversation-attachment-padding-block-start);
122
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
119
123
  }
120
124
  .amplify-ai-conversation__attachment__image {
121
125
  width: var(--amplify-components-ai-conversation-attachment-image-width);
package/dist/styles.css CHANGED
@@ -6842,12 +6842,16 @@ html[dir=rtl] .amplify-field-group__inner-start {
6842
6842
  flex-direction: row;
6843
6843
  align-items: flex-start;
6844
6844
  gap: var(--amplify-components-ai-conversation-form-gap);
6845
- padding: var(--amplify-components-ai-conversation-form-padding);
6846
6845
  }
6847
6846
  .amplify-ai-conversation__form__dropzone {
6848
6847
  text-align: initial;
6849
6848
  border: none;
6849
+ padding: var(--amplify-components-ai-conversation-form-padding);
6850
+ }
6851
+ .amplify-ai-conversation__form__error {
6850
6852
  padding: 0;
6853
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
6854
+ gap: var(--amplify-components-ai-conversation-attachment-gap);
6851
6855
  }
6852
6856
  .amplify-ai-conversation__attachment {
6853
6857
  display: flex;
@@ -6867,7 +6871,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
6867
6871
  flex-direction: row;
6868
6872
  flex-wrap: wrap;
6869
6873
  gap: var(--amplify-components-ai-conversation-attachment-list-gap);
6870
- padding-block-start: var(--amplify-components-ai-conversation-attachment-padding-block-start);
6874
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
6871
6875
  }
6872
6876
  .amplify-ai-conversation__attachment__image {
6873
6877
  width: var(--amplify-components-ai-conversation-attachment-image-width);
@@ -6843,12 +6843,16 @@ html[dir=rtl] .amplify-field-group__inner-start {
6843
6843
  flex-direction: row;
6844
6844
  align-items: flex-start;
6845
6845
  gap: var(--amplify-components-ai-conversation-form-gap);
6846
- padding: var(--amplify-components-ai-conversation-form-padding);
6847
6846
  }
6848
6847
  .amplify-ai-conversation__form__dropzone {
6849
6848
  text-align: initial;
6850
6849
  border: none;
6850
+ padding: var(--amplify-components-ai-conversation-form-padding);
6851
+ }
6852
+ .amplify-ai-conversation__form__error {
6851
6853
  padding: 0;
6854
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
6855
+ gap: var(--amplify-components-ai-conversation-attachment-gap);
6852
6856
  }
6853
6857
  .amplify-ai-conversation__attachment {
6854
6858
  display: flex;
@@ -6868,7 +6872,7 @@ html[dir=rtl] .amplify-field-group__inner-start {
6868
6872
  flex-direction: row;
6869
6873
  flex-wrap: wrap;
6870
6874
  gap: var(--amplify-components-ai-conversation-attachment-list-gap);
6871
- padding-block-start: var(--amplify-components-ai-conversation-attachment-padding-block-start);
6875
+ padding-block-start: var(--amplify-components-ai-conversation-attachment-list-padding-block-start);
6872
6876
  }
6873
6877
  .amplify-ai-conversation__attachment__image {
6874
6878
  width: var(--amplify-components-ai-conversation-attachment-image-width);
@@ -12,6 +12,7 @@ export type AIConversationTheme<Required extends boolean = false> = ComponentSty
12
12
  form__dropzone?: ComponentStyles;
13
13
  form__attatch?: ComponentStyles;
14
14
  form__send?: ComponentStyles;
15
+ form_error?: ComponentStyles;
15
16
  form_field?: ComponentStyles;
16
17
  attachment?: ComponentStyles;
17
18
  attachment__list?: ComponentStyles;
@@ -47,7 +47,7 @@ import { ToggleButtonTheme, ToggleButtonGroupTheme } from './toggleButton';
47
47
  import { ComponentTheme, BaseComponentTheme, BaseTheme } from './utils';
48
48
  export { ClassNameFunction } from '../createTheme/createComponentClasses';
49
49
  export type { ComponentTheme, BaseComponentTheme, BaseTheme };
50
- export type ComponentsTheme<TokensType extends WebTokens = WebTokens> = BaseComponentTheme<BaseTheme, string, TokensType> | BaseComponentTheme<AccordionTheme, 'accordion', TokensType> | BaseComponentTheme<AIConversationTheme, 'ai-conversation', TokensType> | BaseComponentTheme<AlertTheme, 'alert', TokensType> | BaseComponentTheme<AutoCompleteTheme, 'autocomplete', TokensType> | BaseComponentTheme<AvatarTheme, 'avatar', TokensType> | BaseComponentTheme<BadgeTheme, 'badge', TokensType> | BaseComponentTheme<BreadcrumbsTheme, 'breadcrumbs', TokensType> | BaseComponentTheme<ButtonTheme, 'button', TokensType> | BaseComponentTheme<ButtonGroupTheme, 'buttongroup', TokensType> | BaseComponentTheme<CardTheme, 'card', TokensType> | BaseComponentTheme<CheckboxTheme, 'checkbox', TokensType> | BaseComponentTheme<CheckboxFieldTheme, 'checkboxfield', TokensType> | BaseComponentTheme<CollectionTheme, 'collection', TokensType> | BaseComponentTheme<DividerTheme, 'divider', TokensType> | BaseComponentTheme<DropZoneTheme, 'dropzone', TokensType> | BaseComponentTheme<FieldTheme, 'field', TokensType> | BaseComponentTheme<FieldGroupTheme, 'field-group', TokensType> | BaseComponentTheme<FieldsetTheme, 'fieldset', TokensType> | BaseComponentTheme<FileUploaderTheme, 'fileuploader', TokensType> | BaseComponentTheme<HeadingTheme, 'heading', TokensType> | BaseComponentTheme<HighlightMatchTheme, 'highlightmatch', TokensType> | BaseComponentTheme<InputTheme, 'input', TokensType> | BaseComponentTheme<LoaderTheme, 'loader', TokensType> | BaseComponentTheme<MenuTheme, 'menu', TokensType> | BaseComponentTheme<MessageTheme, 'message', TokensType> | BaseComponentTheme<PaginationTheme, 'pagination', TokensType> | BaseComponentTheme<PlaceholderTheme, 'placeholder', TokensType> | BaseComponentTheme<RatingTheme, 'rating', TokensType> | BaseComponentTheme<RadioTheme, 'radio', TokensType> | BaseComponentTheme<ScrollViewTheme, 'scrollview', TokensType> | BaseComponentTheme<SearchFieldTheme, 'searchfield', TokensType> | BaseComponentTheme<SelectTheme, 'select', TokensType> | BaseComponentTheme<SelectFieldTheme, 'selectfield', TokensType> | BaseComponentTheme<SliderFieldTheme, 'sliderfield', TokensType> | BaseComponentTheme<StepperFieldTheme, 'stepperfield', TokensType> | BaseComponentTheme<StorageBrowserTheme, 'storagebrowser', TokensType> | BaseComponentTheme<StorageManagerTheme, 'storagemanager', TokensType> | BaseComponentTheme<SwitchTheme, 'switch', TokensType> | BaseComponentTheme<SwitchFieldTheme, 'switchfield', TokensType> | BaseComponentTheme<TabsTheme, 'tabs', TokensType> | BaseComponentTheme<TableTheme, 'table', TokensType> | BaseComponentTheme<TextTheme, 'text', TokensType> | BaseComponentTheme<TextareaTheme, 'textarea', TokensType> | BaseComponentTheme<TextareaFieldTheme, 'textareafield', TokensType> | BaseComponentTheme<TextFieldTheme, 'textfield', TokensType> | BaseComponentTheme<ToggleButtonTheme, 'togglebutton', TokensType> | BaseComponentTheme<ToggleButtonGroupTheme, 'togglebuttongroup', TokensType>;
50
+ export type ComponentsTheme<TokensType extends WebTokens = WebTokens> = BaseComponentTheme<BaseTheme, string, TokensType> | BaseComponentTheme<AccordionTheme, 'accordion', TokensType> | BaseComponentTheme<AIConversationTheme, 'ai-conversation', TokensType> | BaseComponentTheme<AlertTheme, 'alert', TokensType> | BaseComponentTheme<AutoCompleteTheme, 'autocomplete', TokensType> | BaseComponentTheme<AvatarTheme, 'avatar', TokensType> | BaseComponentTheme<BadgeTheme, 'badge', TokensType> | BaseComponentTheme<BreadcrumbsTheme, 'breadcrumbs', TokensType> | BaseComponentTheme<ButtonTheme, 'button', TokensType> | BaseComponentTheme<ButtonGroupTheme, 'buttongroup', TokensType> | BaseComponentTheme<CardTheme, 'card', TokensType> | BaseComponentTheme<CheckboxTheme, 'checkbox', TokensType> | BaseComponentTheme<CheckboxFieldTheme, 'checkboxfield', TokensType> | BaseComponentTheme<CollectionTheme, 'collection', TokensType> | BaseComponentTheme<DividerTheme, 'divider', TokensType> | BaseComponentTheme<DropZoneTheme, 'dropzone', TokensType> | BaseComponentTheme<FieldTheme, 'field', TokensType> | BaseComponentTheme<FieldGroupTheme, 'field-group', TokensType> | BaseComponentTheme<FieldsetTheme, 'fieldset', TokensType> | BaseComponentTheme<FileUploaderTheme, 'fileuploader', TokensType> | BaseComponentTheme<HeadingTheme, 'heading', TokensType> | BaseComponentTheme<HighlightMatchTheme, 'highlightmatch', TokensType> | BaseComponentTheme<InputTheme, 'input', TokensType> | BaseComponentTheme<LoaderTheme, 'loader', TokensType> | BaseComponentTheme<MenuTheme, 'menu', TokensType> | BaseComponentTheme<MessageTheme, 'message', TokensType> | BaseComponentTheme<PaginationTheme, 'pagination', TokensType> | BaseComponentTheme<PlaceholderTheme, 'placeholder', TokensType> | BaseComponentTheme<RatingTheme, 'rating', TokensType> | BaseComponentTheme<RadioTheme, 'radio', TokensType> | BaseComponentTheme<ScrollViewTheme, 'scrollview', TokensType> | BaseComponentTheme<SearchFieldTheme, 'searchfield', TokensType> | BaseComponentTheme<SelectTheme, 'select', TokensType> | BaseComponentTheme<SelectFieldTheme, 'selectfield', TokensType> | BaseComponentTheme<SliderFieldTheme, 'sliderfield', TokensType> | BaseComponentTheme<StepperFieldTheme, 'stepperfield', TokensType> | BaseComponentTheme<StorageBrowserTheme, 'storage-browser', TokensType> | BaseComponentTheme<StorageManagerTheme, 'storagemanager', TokensType> | BaseComponentTheme<SwitchTheme, 'switch', TokensType> | BaseComponentTheme<SwitchFieldTheme, 'switchfield', TokensType> | BaseComponentTheme<TabsTheme, 'tabs', TokensType> | BaseComponentTheme<TableTheme, 'table', TokensType> | BaseComponentTheme<TextTheme, 'text', TokensType> | BaseComponentTheme<TextareaTheme, 'textarea', TokensType> | BaseComponentTheme<TextareaFieldTheme, 'textareafield', TokensType> | BaseComponentTheme<TextFieldTheme, 'textfield', TokensType> | BaseComponentTheme<ToggleButtonTheme, 'togglebutton', TokensType> | BaseComponentTheme<ToggleButtonGroupTheme, 'togglebuttongroup', TokensType>;
51
51
  export type AllComponentThemes = {
52
52
  accordion: AccordionTheme;
53
53
  'ai-conversation': AIConversationTheme;
@@ -84,7 +84,7 @@ export type AllComponentThemes = {
84
84
  selectfield: SelectFieldTheme;
85
85
  sliderfield: SliderFieldTheme;
86
86
  stepperfield: StepperFieldTheme;
87
- storagebrowser: StorageBrowserTheme;
87
+ 'storage-browser': StorageBrowserTheme;
88
88
  storagemanager: StorageManagerTheme;
89
89
  switch: SwitchTheme;
90
90
  switchfield: SwitchFieldTheme;
@@ -30,6 +30,7 @@ export declare const ComponentClassName: {
30
30
  readonly AIConversationAttachmentRemove: "amplify-ai-conversation__attachment__remove";
31
31
  readonly AIConversationForm: "amplify-ai-conversation__form";
32
32
  readonly AIConversationFormAttach: "amplify-ai-conversation__form__attach";
33
+ readonly AIConversationFormError: "amplify-ai-conversation__form__error";
33
34
  readonly AIConversationFormSend: "amplify-ai-conversation__form__send";
34
35
  readonly AIConversationFormField: "amplify-ai-conversation__form__field";
35
36
  readonly AIConversationFormDropzone: "amplify-ai-conversation__form__dropzone";
@@ -7,3 +7,4 @@ export declare const IN_APP_MESSAGING_INPUT_BASE: Omit<InAppMessagingUserAgentIn
7
7
  export declare const LOCATION_SEARCH_INPUT_BASE: Omit<GeoUserAgentInput, 'additionalDetails'>;
8
8
  export declare const MAP_VIEW_INPUT_BASE: Omit<GeoUserAgentInput, 'additionalDetails'>;
9
9
  export declare const STORAGE_MANAGER_INPUT_BASE: Omit<StorageUserAgentInput, 'additionalDetails'>;
10
+ export declare const STORAGE_BROWSER_INPUT_BASE: Omit<StorageUserAgentInput, 'additionalDetails'>;
@@ -1,5 +1,5 @@
1
1
  export type PackageName = 'angular' | 'react' | 'react-ai' | 'react-auth' | 'react-geo' | 'react-liveness' | 'react-native' | 'react-native-auth' | 'react-notifications' | 'react-storage' | 'vue';
2
- export type ComponentName = 'AIConversation' | 'Authenticator' | 'ChangePassword' | 'DeleteUser' | 'FaceLivenessDetector' | 'FileUploader' | 'InAppMessaging' | 'LocationSearch' | 'MapView' | 'StorageManager' | 'StorageImage';
2
+ export type ComponentName = 'AIConversation' | 'Authenticator' | 'ChangePassword' | 'DeleteUser' | 'FaceLivenessDetector' | 'FileUploader' | 'InAppMessaging' | 'LocationSearch' | 'MapView' | 'StorageBrowser' | 'StorageManager' | 'StorageImage';
3
3
  export type Version = `${string}.${string}.${string}`;
4
4
  export interface SetUserAgentOptions {
5
5
  componentName: ComponentName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/ui",
3
- "version": "6.7.0",
3
+ "version": "6.7.2",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/esm/index.mjs",
6
6
  "exports": {