saasufy-components 4.4.0 → 5.0.0

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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Components for Saasufy
6
6
  - [socket-provider](#socket-provider)
7
7
 
8
8
  ### Collections of resources
9
- - [collection-browser](#collection-browser)
9
+ - [collection-viewer](#collection-viewer)
10
10
  - [collection-adder](#collection-adder)
11
11
  - [collection-adder-form](#collection-adder-form)
12
12
  - [collection-deleter](#collection-deleter)
@@ -256,7 +256,7 @@ A Saasufy component which integrates with data from Saasufy is known as a `socke
256
256
  - `socket-options`: Can be used to set options on the inner `socket`. Must be in the format `option1:type1=value1,option2:type2=value2`; the type of each option can be string, boolean or number. If not specified, the default type is string.
257
257
  - `disconnect-on-deauth`: If this attribute is set, the underlying socket will be disconnected if the socket becomes unauthenticated. This means that components will not receive real-time updates until the user's next interaction (which will cause the socket to reconnect). If this attribute is not set, the socket will attempt to reconnect immediately after losing authentication.
258
258
 
259
- ### collection-browser
259
+ ### collection-viewer
260
260
 
261
261
  Used for rendering collections as lists, tables or other sequences based on a specific view using a template.
262
262
  Supports pagination by allowing you to specify custom buttons or links to navigate between pages.
@@ -265,13 +265,13 @@ Can also perform basic CRUD operations such as deleting or creating records by l
265
265
  **Import**
266
266
 
267
267
  ```html
268
- <script src="https://saasufy.com/node_modules/saasufy-components/collection-browser.js" type="module" defer></script>
268
+ <script src="https://saasufy.com/node_modules/saasufy-components/collection-viewer.js" type="module" defer></script>
269
269
  ```
270
270
 
271
271
  **Example usage**
272
272
 
273
273
  ```html
274
- <collection-browser
274
+ <collection-viewer
275
275
  collection-type="Chat"
276
276
  collection-fields="username,message,createdAt"
277
277
  collection-view="recentView"
@@ -295,7 +295,7 @@ Can also perform basic CRUD operations such as deleting or creating records by l
295
295
  </template>
296
296
 
297
297
  <div slot="viewport" class="chat-viewport"></div>
298
- </collection-browser>
298
+ </collection-viewer>
299
299
  ```
300
300
 
301
301
  **Attributes**
@@ -305,11 +305,11 @@ Can also perform basic CRUD operations such as deleting or creating records by l
305
305
  - `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
306
306
  - `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
307
307
  - `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
308
- - `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the browser which page of data you want to display initially.
308
+ - `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the viewer which page of data you want to display initially.
309
309
  - `collection-get-count`: If this attribute is present, the component will get the record count of the target view. The count can be rendered into the template by prefixing the model name with a dollar sign and accessing the `count` property like this: `{{$MyModelName.count}}`.
310
310
  - `collection-view-primary-fields`: An optional list of field names to specify which specific `collection-view-params` to watch for real time updates. Fewer primary fields means that the view will be exposed to a broader range of real time updates but at the cost of performance. It is generally recommended to have just one primary field.
311
311
  - `auto-reset-page-offset`: If this attribute is present, the `collection-page-offset` will be reset to zero whenever the view params change.
312
- - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
312
+ - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-viewer` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
313
313
  - `hide-error-logs`: A flag which, when present, suppresses error logs from being printed to the console.
314
314
  - `max-show-loader`: If this attribute is present, your slotted `loader` element will be shown as often as possible; this includes situations where the collection is merely refreshing itself. It is disabled by default.
315
315
 
@@ -389,8 +389,8 @@ This component is similar to the `collection-adder` component except that it req
389
389
 
390
390
  ### collection-deleter
391
391
 
392
- A component which can be placed anywhere inside a `collection-browser` component to delete a specific item from a collection as a result of a user action (e.g. on click).
393
- It supports either immediate deletion or deletion upon confirmation; in the latter case, the parent `collection-browser` must have a `confirm-modal` component slotted into its `modal` slot.
392
+ A component which can be placed anywhere inside a `collection-viewer` component to delete a specific item from a collection as a result of a user action (e.g. on click).
393
+ It supports either immediate deletion or deletion upon confirmation; in the latter case, the parent `collection-viewer` must have a `confirm-modal` component slotted into its `modal` slot.
394
394
 
395
395
  **Import**
396
396
 
@@ -401,14 +401,14 @@ It supports either immediate deletion or deletion upon confirmation; in the latt
401
401
  **Example usage**
402
402
 
403
403
  ```html
404
- <!-- Must be placed somewhere inside a collection-browser component, typically inside the slotted item template. -->
404
+ <!-- Must be placed somewhere inside a collection-viewer component, typically inside the slotted item template. -->
405
405
  <collection-deleter model-id="{{Product.id}}" onclick="deleteItem()">&#x2715;</collection-deleter>
406
406
  ```
407
407
 
408
408
  OR (with confirmation step)
409
409
 
410
410
  ```html
411
- <!-- Must be placed somewhere inside a collection-browser component, typically inside the slotted item template. -->
411
+ <!-- Must be placed somewhere inside a collection-viewer component, typically inside the slotted item template. -->
412
412
  <collection-deleter model-id="{{Product.id}}" confirm-message="Are you sure you want to delete the {{Product.name}} product?" onclick="confirmDeleteItem()">&#x2715;</collection-deleter>
413
413
  ```
414
414
 
@@ -418,10 +418,10 @@ OR (with confirmation step)
418
418
  - `onclick` (required): The logic to execute to delete the item. Should be either `deleteItem()` or `confirmDeleteItem()`.
419
419
  - `confirm-message`: The confirmation message to show the user when this component's `confirmDeleteItem()` function is invoked.
420
420
 
421
- If `confirmDeleteItem()` is used, then the parent `collection-browser` must have a `confirm-modal` element slotted into it as shown here:
421
+ If `confirmDeleteItem()` is used, then the parent `collection-viewer` must have a `confirm-modal` element slotted into it as shown here:
422
422
 
423
423
  ```html
424
- <collection-browser
424
+ <collection-viewer
425
425
  collection-type="Product"
426
426
  collection-fields="name,qty"
427
427
  collection-view="alphabeticalView"
@@ -439,12 +439,12 @@ If `confirmDeleteItem()` is used, then the parent `collection-browser` must have
439
439
 
440
440
  <!-- The confirm-modal element must be specified here with slot="modal" to prompt the user for confirmation -->
441
441
  <confirm-modal slot="modal" title="Delete confirmation" message="" confirm-button-label="Delete"></confirm-modal>
442
- </collection-browser>
442
+ </collection-viewer>
443
443
  ```
444
444
 
445
445
  ### collection-reducer
446
446
 
447
- Similar to the `collection-browser` component but designed to render collections in combined format. For example, to combine values from multiple records into a single item.
447
+ Similar to the `collection-viewer` component but designed to render collections in combined format. For example, to combine values from multiple records into a single item.
448
448
  A common use case is to extract and join values to pass to other child components via their attributes.
449
449
 
450
450
  **Import**
@@ -489,7 +489,7 @@ A common use case is to extract and join values to pass to other child component
489
489
  </collection-reducer>
490
490
  ```
491
491
 
492
- Unlike with `collection-browser` or `model-viewer`, the template variable references not a single model instance but an array of model instances.
492
+ Unlike with `collection-viewer` or `model-viewer`, the template variable references not a single model instance but an array of model instances.
493
493
  In the example above, the name of the first element can be accessed with `{{Category[0].name}}`.
494
494
 
495
495
  **Attributes**
@@ -499,9 +499,9 @@ In the example above, the name of the first element can be accessed with `{{Cate
499
499
  - `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
500
500
  - `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
501
501
  - `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
502
- - `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the browser which page of data you want to display initially.
502
+ - `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the viewer which page of data you want to display initially.
503
503
  - `collection-get-count`: If this attribute is present, the component will get the record count of the target view. The count can be rendered into the template by prefixing the model name with a dollar sign and accessing the `count` property like this: `{{$MyModelName.count}}`.
504
- - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
504
+ - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-viewer` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
505
505
  - `hide-error-logs`: A flag which, when present, suppresses error logs from being printed to the console.
506
506
 
507
507
  ### model-input
@@ -532,6 +532,7 @@ Used for displaying and editing a single field of a model instance in real time.
532
532
  - `model-id` (required): The id of the model instance/record to bind to.
533
533
  - `model-field` (required): The field of the model instance/record to bind to for reading and updating.
534
534
  - `debounce-delay`: The delay in milliseconds to wait before sending an update to the server. This is useful to batch multiple updates together (as is common when user is typing). Default is 300ms.
535
+ - `disable-instant-flush`: If specified, changes will only be saved once they have settled (E.g. after the element has lost focus or when the enter key is pressed).
535
536
  - `list`: If specified, this sets the list attribute of the inner `input` element to provide input suggestions (only works with standard input elements).
536
537
  - `type`: The type of the input component; can be `text`, `number`, `select`, `textarea`, `checkbox` or `file`.
537
538
  - `placeholder`: Can be used to set the placeholder text on the inner input element.
@@ -585,7 +586,7 @@ Used to displaying a field of a model instance in real time.
585
586
 
586
587
  ### model-viewer
587
588
 
588
- Used for rendering a single model resource using a template. This is the single-model alternative to the `collection-browser` which works with collections of models.
589
+ Used for rendering a single model resource using a template. This is the single-model alternative to the `collection-viewer` which works with collections of models.
589
590
 
590
591
  **Import**
591
592
 
@@ -618,14 +619,14 @@ Used for rendering a single model resource using a template. This is the single-
618
619
  - `model-type` (required): Specifies the type of model to bind to. This should match a `Model` available in your Saasufy service.
619
620
  - `model-id` (required): The id of the model instance/record to bind to.
620
621
  - `model-fields` (required): A comma-separated list of fields to bind to. Note that unlike with `model-input` and `model-text`, the attribute name is plural and can bind to multiple fields.
621
- - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `model-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `MyProduct`, then `{{Product.name}}` would become `{{MyProduct.name}}`.
622
+ - `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `model-viewer` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `MyProduct`, then `{{Product.name}}` would become `{{MyProduct.name}}`.
622
623
  - `fields-slice-to`: Optional attribute which can be used to trim strings down to a maximum number of characters when reading. The format is `fieldName1=number1,fieldName2=number2`. This is useful when dealing with potentially very long field values. Note that it may affect caching if the same field is being referenced in multiple parts of the application at the same time within the same `socket-provider`.
623
624
  - `hide-error-logs`: A flag which, when present, suppresses error logs from being printed to the console.
624
625
 
625
626
  ### input-provider
626
627
 
627
628
  An input component which can pass data to other components (or HTML elements) via custom attributes.
628
- A common use case for it is to pass user input to `collection-browser` or `model-viewer` components to then fetch data from Saasufy.
629
+ A common use case for it is to pass user input to `collection-viewer` or `model-viewer` components to then fetch data from Saasufy.
629
630
  This component can be configured to provide its data to multiple components (consumers) via custom attributes.
630
631
 
631
632
  **Import**
@@ -650,6 +651,7 @@ This component can be configured to provide its data to multiple components (con
650
651
  - `input-id`: Can be used to set an `id` attribute on the inner `input` element.
651
652
  - `list`: If specified, this sets the list attribute of the inner `input` element to provide input suggestions (only works with standard input elements).
652
653
  - `type`: The type of the input component; can be `text`, `number`, `select`, `textarea` or `checkbox`.
654
+ - `disable-instant-flush`: If specified, changes will only be saved once they have settled (E.g. after the element has lost focus or when the enter key is pressed).
653
655
  - `placeholder`: Can be used to set the placeholder text on the inner input element.
654
656
  - `consumers`: This allows you to connect this `input-provider` to other elements on your page. It takes a list of selectors with optional attributes to target in the format `element-selector:attribute-name`. For example `.my-input:value` will find all elements with a `my-input` class and update their `value` attributes with the value of the `input-provider` component in real-time. You can specify multiple selectors separated by commas such as `.my-input,my-div`; in this case, because attribute names are not specified, values will be injected into the `value` attribute (for input elements) or into the `innerHTML` property (for other kinds of elements). The default attribute/property depends on the element type.
655
657
  - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers.
@@ -665,7 +667,7 @@ This component can be configured to provide its data to multiple components (con
665
667
  ### input-combiner
666
668
 
667
669
  A component which can be used to combine data from multiple `input-provider` components and pass the combined data to other components (or HTML elements) via custom attributes.
668
- A common use case for it is to pass user input to `collection-browser` or `model-viewer` components to then fetch data from Saasufy.
670
+ A common use case for it is to pass user input to `collection-viewer` or `model-viewer` components to then fetch data from Saasufy.
669
671
  This component can be configured to provide its data to multiple components (consumers) via custom attributes.
670
672
 
671
673
  **Import**
@@ -677,7 +679,7 @@ This component can be configured to provide its data to multiple components (con
677
679
  **Example usage**
678
680
 
679
681
  The following example shows how to bind multiple `input-provider` components to an `input-combiner`.
680
- In this example, the `input-combiner` component forwards the combined, formatted values to the `collection-view-params` attribute of a `company-browser` component.
682
+ In this example, the `input-combiner` component forwards the combined, formatted values to the `collection-view-params` attribute of a `company-viewer` component.
681
683
 
682
684
  Note that the `value` inside the `provider-template` attribute of the `input-combiner` is an object with keys that represent the `name` attribute of the different source `input-provider` components.
683
685
  The `combineFilters` function produces a combined query string (joined with `%AND%`) by iterating over the `value` object which holds different parts of the query as provided by different `input-provider` components.
@@ -705,7 +707,7 @@ The `combineFilters` function produces a combined query string (joined with `%AN
705
707
 
706
708
  <input-combiner
707
709
  class="search-combiner"
708
- consumers=".company-browser:collection-view-params"
710
+ consumers=".company-viewer:collection-view-params"
709
711
  provider-template="query='{{combineFilters(value)}}'"
710
712
  ></input-combiner>
711
713
  ```
@@ -916,7 +918,7 @@ The `render-group` will only render its content once all the components specifie
916
918
  ### if-group
917
919
 
918
920
  A component which exposes a `show-content` property which can be set to true or false to show or hide its content.
919
- It's intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false value of the `show-content` attribute can be computed using a template `{{expression}}` placeholder. This component requires a template and a viewport to be slotted in. The content of the template will not be processed unless the `show-content` condition is met.
921
+ It's intended to be placed inside a `model-viewer`, `collection-viewer` or `collection-reducer` component such that the true/false value of the `show-content` attribute can be computed using a template `{{expression}}` placeholder. This component requires a template and a viewport to be slotted in. The content of the template will not be processed unless the `show-content` condition is met.
920
922
 
921
923
  **Import**
922
924
 
@@ -940,7 +942,7 @@ It's intended to be placed inside a `model-viewer`, `collection-browser` or `col
940
942
  ### switch-group
941
943
 
942
944
  A component which exposes a `show-cases` property which can be set to key-value pairs in the format `key1=true,key2=false`. It can be used to conditionally display multiple slotted elements based on multiple conditions. It helps to keep HTML clean when the conditions are complex.
943
- This element is intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false values of the `show-cases` attribute can be computed using template `{{expression}}` placeholders. This component requires one or more templates and a viewport to be slotted in. The content of the templates will not be processed unless the `show-cases` condition is met. All templates must have a `slot="content"` attribute and a name attribute in the format `name="key1"` where the value `key1` corresponds to the key specified inside the `show-cases` attribute of the `switch-group`.
945
+ This element is intended to be placed inside a `model-viewer`, `collection-viewer` or `collection-reducer` component such that the true/false values of the `show-cases` attribute can be computed using template `{{expression}}` placeholders. This component requires one or more templates and a viewport to be slotted in. The content of the templates will not be processed unless the `show-cases` condition is met. All templates must have a `slot="content"` attribute and a name attribute in the format `name="key1"` where the value `key1` corresponds to the key specified inside the `show-cases` attribute of the `switch-group`.
944
946
 
945
947
  **Import**
946
948
 
@@ -1006,7 +1008,7 @@ A component which can be used to group together multiple `collection-adder` comp
1006
1008
 
1007
1009
  ### confirm-modal
1008
1010
 
1009
- A modal component to prompt the user for confirmation before performing sensitive operations. Can be slotted into a `collection-browser` component.
1011
+ A modal component to prompt the user for confirmation before performing sensitive operations. Can be slotted into a `collection-viewer` component.
1010
1012
 
1011
1013
  **Import**
1012
1014
 
@@ -1017,7 +1019,7 @@ A modal component to prompt the user for confirmation before performing sensitiv
1017
1019
  **Example usage**
1018
1020
 
1019
1021
  ```html
1020
- <collection-browser
1022
+ <collection-viewer
1021
1023
  collection-type="Product"
1022
1024
  collection-fields="name,qty"
1023
1025
  collection-view="alphabeticalView"
@@ -1035,7 +1037,7 @@ A modal component to prompt the user for confirmation before performing sensitiv
1035
1037
 
1036
1038
  <!-- The confirm-modal element must be specified here with slot="modal" to prompt the user for confirmation -->
1037
1039
  <confirm-modal slot="modal" title="Delete confirmation" message="" confirm-button-label="Delete"></confirm-modal>
1038
- </collection-browser>
1040
+ </collection-viewer>
1039
1041
  ```
1040
1042
 
1041
1043
  **Attributes**
@@ -4,7 +4,7 @@ import { renderTemplate, convertStringToFieldParams } from './utils.js';
4
4
 
5
5
  const DEFAULT_RELOAD_DELAY = 0;
6
6
 
7
- class CollectionBrowser extends SocketConsumer {
7
+ class CollectionViewer extends SocketConsumer {
8
8
  constructor() {
9
9
  super();
10
10
  this.isReady = false;
@@ -351,7 +351,7 @@ class CollectionBrowser extends SocketConsumer {
351
351
  (async () => {
352
352
  for await (let { error } of this.collection.listener('error')) {
353
353
  console.error(
354
- `Collection browser encountered an error: ${error.message}`
354
+ `Collection viewer encountered an error: ${error.message}`
355
355
  );
356
356
  }
357
357
  })();
@@ -359,4 +359,4 @@ class CollectionBrowser extends SocketConsumer {
359
359
  }
360
360
  }
361
361
 
362
- window.customElements.define('collection-browser', CollectionBrowser);
362
+ window.customElements.define('collection-viewer', CollectionViewer);
package/input-provider.js CHANGED
@@ -222,6 +222,7 @@ class InputProvider extends HTMLElement {
222
222
  if (inputElement.type !== 'checkbox') {
223
223
  onInputKeyUp = async (event) => {
224
224
  this.updateInputClassList(event.target.value);
225
+ if (this.hasAttribute('disable-instant-flush')) return;
225
226
  debounce(async () => {
226
227
  let elementName = this.getAttribute('name');
227
228
  let providerTemplate = this.getAttribute('provider-template');
package/model-input.js CHANGED
@@ -395,6 +395,7 @@ class ModelInput extends SocketConsumer {
395
395
  if (inputType !== 'checkbox' && inputType !== 'select' && inputType !== 'file') {
396
396
  onInputKeyUp = async (event) => {
397
397
  inputElement.classList.remove(successStyleClass);
398
+ if (this.hasAttribute('disable-instant-flush')) return;
398
399
  debounce(async () => {
399
400
  if (event.target.value === String(model.value[fieldName] || '')) return;
400
401
  let providerTemplate = this.getAttribute('provider-template');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "4.4.0",
3
+ "version": "5.0.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/styles.css CHANGED
@@ -167,14 +167,14 @@ collection-adder .collection-adder-form-content > label {
167
167
  align-self: flex-start;
168
168
  }
169
169
 
170
- collection-browser {
170
+ collection-viewer {
171
171
  display: flex;
172
172
  flex-wrap: wrap;
173
173
  justify-content: center;
174
174
  gap: 10px;
175
175
  }
176
176
 
177
- collection-browser > [slot=viewport] {
177
+ collection-viewer > [slot=viewport] {
178
178
  display: flex;
179
179
  flex-direction: column;
180
180
  }
@@ -183,20 +183,20 @@ collection-browser > [slot=viewport] {
183
183
  cursor: pointer;
184
184
  }
185
185
 
186
- collection-browser > [slot=previous-page] {
186
+ collection-viewer > [slot=previous-page] {
187
187
  cursor: pointer;
188
188
  flex-shrink: 1;
189
189
  width: auto;
190
190
  }
191
191
 
192
- collection-browser > [slot=page-number] {
192
+ collection-viewer > [slot=page-number] {
193
193
  display: flex;
194
194
  justify-content: center;
195
195
  align-items: center;
196
196
  min-width: 30px;
197
197
  }
198
198
 
199
- collection-browser > [slot=next-page] {
199
+ collection-viewer > [slot=next-page] {
200
200
  cursor: pointer;
201
201
  flex-shrink: 1;
202
202
  width: auto;