<!--
  Copyright 2023 Comcast Cable Communications Management, LLC

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  SPDX-License-Identifier: Apache-2.0
-->

import { Canvas, Story } from '@storybook/addon-docs';
import { DocsLink } from '../../docs/utils';
import Column from '.';

# Column

Focus management and smooth scrolling for a list of vertically-oriented items.

## Source

https://github.com/rdkcentral/Lightning-UI-Components/blob/develop/packages/@lightningjs/ui-components/src/components/Column/Column.js

## Usage

`Column` expects a list of `items` which will be laid out top-to-bottom. By default, the width of `Column`'s Item container is the same as the width of the widest item, and the height is the height passed in, the height of the wrapping FocusManager if available, or the height of the stage by default. You can change the `w` and `h` attributes to manage `width` and `height`, respectively.

```js
import { Column } from '@lightningjs/ui-components';

class BasicExample extends lng.Component {
  static _template() {
    return {
      Column: {
        type: Column,
        items: [
          // list of items
        ]
      }
    };
  }

  _getFocused() {
    return this.tag('Column');
  }
}
```

The `Column` component extends `FocusManager`[fm-docs], so you need to make sure that `_getFocused` returns the `Column` component.

### Column items with changing height

If the heights of various items in the column can change (ex. an item which expands), the layout
of the items in the Column must be recalculated. To trigger this update, fire an event, called `$itemChanged`, from the
item when its height changes.
ex.

```js
class ExpandingButton extends Button {
  _focus() {
    super._focus();
    this.patch({ h: 100 });
    // triggers recalculation of Column items layout
    this.fireAncestors('$itemChanged');
  }

  _unfocus() {
    super._unfocus();
    this.patch({ h: 40 });
  }
}

class ExpandableHeightItems extends lng.Component {
  static _template() {
    return {
      Column: {
        type: Column,
        h: 500,
        items: Array.apply(null, { length: 15 }).map((_, i) => ({
          type: ExpandingButton,
          h: 40,
          w: 150,
          buttonText: `Button ${i}`
        }))
      }
    };
  }

  _getFocused() {
    return this.tag('Column');
  }
}
```

<Canvas>
  <Story id="navigation-column--column" />
</Canvas>

### Scrolling

Scroll behavior is determined based on what is passed to the `alwaysScroll` and `neverScroll` properties.
If both of those properties are enabled, `alwaysScroll` will take precedence over `neverScroll`.

## API

### Properties

| name          | type                                                    | required | default   | description                                                                                                                                  |
| ------------- | ------------------------------------------------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| items         | <DocsLink id="lng.Component">lng.Component[]</DocsLink> | false    | undefined | components to be listed in the column.                                                                                                       |
| onScreenItems | array                                                   | true     | undefined | list of items that are currently on screen within the bounds of the container.                                                               |
| plinko        | boolean                                                 | false    | false     | when `true`, the component will determine the nearest horizontal index of the next focused item's children. if `Column` has `Row`s as items. |
| selected      | <DocsLink id="lng.Component" />                         | true     | undefined | component that is currently in focus                                                                                                         |
| selectedIndex | number                                                  | false    | 0         | index of currently selected item                                                                                                             |

### Style Properties

| name           | type                             | description                            |
| -------------- | -------------------------------- | -------------------------------------- |
| itemSpacing    | number                           | spacing between items                  |
| scrollIndex    | number                           | index of currently selected item       |
| itemTransition | <DocsLink id="lng.Transition" /> | transition to apply to items on render |

### Child Item Properties

The `Column` component will utilize the following properties on each individual element in its `items` array:

| name             | type    | required | default | description                                                                                             |
| ---------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------- |
| centerInParent   | boolean | false    | false   | when true, it places the child component in center of the parent                                        |
| extraItemSpacing | number  | false    | 0       | additional space to be added between items                                                              |
| skipPlinko       | boolean | false    | false   | when true, plinko will use the previous item to determine the horizontal index of the next focused item |

### Methods

#### onScreenEffect(): void

A callback that can be overridden to do something with the items that are currently on screen. This will be called on every new render.

#### appendItems(items: lng.Component[]): void

Adds items to the end of the child list

#### appendItemsAt(items: lng.Component[], index: number): void

Adds items at a specified index of the child list

#### prependItems(items: lng.Component[]): void

Adds items to the start of the child list

#### removeItemAt(index: number): void

Removes an item (by index) from the row

##### Arguments

| name  | type                                                    | required | default | description             |
| ----- | ------------------------------------------------------- | -------- | ------- | ----------------------- |
| items | <DocsLink id="lng.Component">lng.Component[]</DocsLink> | false    | []      | list of items to append |

#### render(selected: lng.Component, previous: lng.Component): void

Called when item focus changes. Render is responsible for the scroll behavior

##### Arguments

| name     | type                                                    | required | default | description                                                          |
| -------- | ------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------- |
| selected | <DocsLink id="lng.Component">lng.Component[]</DocsLink> | false    | -       | The items to be selected. Required only if this.plinko is true.      |
| previous | <DocsLink id="lng.Component">lng.Component[]</DocsLink> | false    | -       | The previously selected items. Required only if this.plinko is true. |

### Events

#### $columnEmpty(): void

Fires when all of the items have been removed from a `Column`

[fm-docs]: ?path=/docs/focusmanager--rows
[fm-api]: ?path=/docs/focusmanager--rows#api
