# Overview of Feature Functionality

Headless Tree is composed by a set of individual features, that can each be included or not in the tree
configuration. This allows you to reduce the bundle size by including only what you need, while being able
to customize the tree by overriding feature behavior or expanding the tree with custom features.

Each feature is an object that can be imported from `@headless-tree/core`, and has a TypeScript type declaration
that defines the functionality that it provides as well as the configuration options that it accepts.

For instance, this is the type declaration for the `selectionFeature`, which provides the ability to
select multiple items inside the tree:

```ts
export type SelectionFeatureDef<T> = {
  state: {
    selectedItems: string[];
  };
  config: {
    setSelectedItems?: SetStateFn<string[]>;
  };
  treeInstance: {
    setSelectedItems: (selectedItems: string[]) => void;
    getSelectedItems: () => ItemInstance<T>[];
  };
  itemInstance: {
    select: () => void;
    deselect: () => void;
    toggleSelect: () => void;
    isSelected: () => boolean;
    selectUpTo: (ctrl: boolean) => void;
  };
  hotkeys:
    | "toggleSelectItem"
    | "selectUpwards"
    | "selectDownwards"
    | "selectAll";
};
```

- The feature substate interface defined with `SelectionFeatureDef<T>["state"]` gets merged with the
  interface `TreeState`,
  and allows you to manage that state in any way defined [Managing State Guide](https://headless-tree.lukasbach.com/llm/guides/state.md).
- The configuration options defined with `SelectionFeatureDef<T>["config"]` gets merged with the
  interface `TreeConfig`, and the feature recognizes any of those
  config options passed to the tree configuration.
- The `treeInstance` and `itemInstance` interfaces define the methods that the feature provides to
  the tree instances and item instances,
  respectively. These methods can be called directly on the instances to interact with the feature.
- The `hotkeys` type defines the hotkeys that the feature implements and respects as long as the
  [Hotkey Core Feature](https://headless-tree.lukasbach.com/llm/features/hotkeys.md) is included in the tree configuration.

By including the feature, you can interact with it like follows:

```ts
const tree = useTree<string>({
  // ...remaining tree config
  state: { selectedItems: ["item-1", "item-2"] },
  setSelectedItems: myCustomSetSelectedItems,
  // alternatively manageable with setState

  hotkeys: {
    // Override hotkey definitions for this feature
    selectAll: {
      hotkey: "ctrl+q",
    },
  },
  features: [
    syncDataLoaderFeature,
    selectionFeature,
    hotkeysCoreFeature,
  ],
});

// Interact with tree instance methods
tree.setSelectedItems(["item-3", "item-4"]);

// Interact with item instance methods
tree.getItemInstance("item-1").select();
```

## Implementing custom features

Apart from being merged into the Headless Tree core TypeScript interfaces, each feature is a completely
standalone object that is defined in isolation. You can implement your own features to expand the
behavior of Headless Tree, and merge the types for your feature into the global HT namespace interfaces
yourself.

```ts
declare module "@headless-tree/core" {
  export interface ItemInstance<T> {
    alertChildren: () => void;
  }
}

const customFeature: FeatureImplementation = {
  itemInstance: {
    alertChildren: ({ item }) => {
      alert(
        item
          .getChildren()
          .map((child) => child.getItemName())
          .join(", "),
      );
    },
  },
};


const tree = useTree<string>({
  // ...remaining tree config
  features: [
    // ...other features
    customFeature,
  ],
});

tree.getItemInstance("item-1").alertChildren();
```

You can find out more about this in the [Guide on writing custom plugins](https://headless-tree.lukasbach.com/llm/recipe/plugins.md).

## Overwriting feature behavior

You can also overwrite HT core features, or parts of them to customize them to your use case. In the most
extreme case, you can just completely copy the implementation for a single feature (you can find the source
for each feature, by following the "View Source" link in one of the feature docs pages) into your project
and customizing the implementation.

You can also write a feature, that wraps the original feature and modifies its behavior. When multiple features
are included in the tree configuration, that define an instance method with the same name, the last feature
in the list will be the one called when the method is invoked, and its implementation will be passed a method
called `prev` that can be used in the wrapper implementation to call the previous implementation.

As example, note the implementation of `itemInstance.getProps()` in the selection feature, that adds new DOM properties
and modifies the behavior of the `onClick` handler in cases where Shift or Ctrl is pressed, while still
respecting the behavior of other features generating props:

```ts
export const selectionFeature: FeatureImplementation = {
  // ... other feature properties

  itemInstance: {
    getProps: ({ tree, item, prev }) => ({
      ...prev?.(), // include other props generated by other features
      "aria-selected": item.isSelected() ? "true" : "false",

      onClick: (e: MouseEvent) => { // override onClick handler
        if (e.shiftKey) {
          item.selectUpTo(e.ctrlKey || e.metaKey);
        } else if (e.ctrlKey || e.metaKey) {
          item.toggleSelect();
        } else {
          tree.setSelectedItems([item.getItemMeta().itemId]);
        }

        // call the implementation of onClick of the
        // previous feature that implements this method
        prev?.()?.onClick?.(e);
      },
    }),
  }
};
```

Again, this functionality is described in more detail in the [Guide on writing custom plugins](https://headless-tree.lukasbach.com/llm/recipe/plugins.md).