# Displaying Custom Windows

Canonical page: https://www.adaptabletools.com/docs/ui-popup-windows

- AdapTable allows developers to provide custom windows
- These are custom, movable, resizable with bespoke content

AdapTable allows developers to create and display bespoke Windows.

These windows display bespoke content supplied by the developer.

- Custom Windows are fully movable and resizable
- If a user moves or resizes a window, it re-opens with those dimensions and position

Open and close Custom Windows via [User Interface API](https://www.adaptabletools.com/docs/technical-reference-ui/index.md#user-interface-api):

- `openCustomWindowPopup` — displays a Custom Window using the supplied [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md)
- `closeCustomWindowPopup` — closes the open window with the given `id`

### The Custom Window object

The [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md) object is defined as follows:

| Property | Type | Description |
| --- | --- | --- |
| [frameworkComponent](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#frameworkcomponent) | [`AdaptableFrameworkComponent`](https://www.adaptabletools.com/docs/reference/adaptableframeworkcomponent.md) | React, Angular or Vue Framework component to use for Custom Window content |
| [icon](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#icon) | [`AdaptableSystemIconName`](https://www.adaptabletools.com/docs/reference/adaptablesystemiconname.md) | Icon to display in Custom Window header |
| [id](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#id) | `string` | Unique identifier for Custom Window |
| [onFrameworkComponentDestroyed](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#onframeworkcomponentdestroyed) | `() => void` | Callback function invoked when framework component is destroyed |
| [position](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#position) | [`WindowPosition`](https://www.adaptabletools.com/docs/reference/windowposition.md) | Position of window in pixels (provided as x, y) |
| [render](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#render) | `(customRenderContext: `[`CustomRenderContext`](https://www.adaptabletools.com/docs/reference/customrendercontext.md)`) => string \| null` | Render function to display content in Custom Window (if not using a framework component) |
| [size](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#size) | [`WindowSize`](https://www.adaptabletools.com/docs/reference/windowsize.md) | Size of window in pixels (provided as width, height) |
| [title](https://www.adaptabletools.com/docs/reference/customwindowconfig.md#title) | `string` | Title of Custom Window |

As can be seen content is provided in one of 2 ways:

- in Framework versions of AdapTable, by using the `frameworkComponent` property
- in the vanilla (i.e. non-Framework) AdapTable version, by using the `render` property

<!-- framework: vanilla -->
### Vanilla

### Providing a Custom Window

Build a [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md):

- `id` — required unique identifier; pass the same value to `closeCustomWindowPopup`
- `title` — label shown in the window header
- `icon` — optional [`AdaptableSystemIconName`](https://www.adaptabletools.com/docs/reference/adaptablesystemiconname.md) for the header (e.g. `'laptop'`, `'search'`)
- `position` — optional initial placement in pixels (`x`, `y`) — [`WindowPosition`](https://www.adaptabletools.com/docs/reference/windowposition.md)
- `size` — optional initial dimensions (`width`, `height`) — [`WindowSize`](https://www.adaptabletools.com/docs/reference/windowsize.md)
- `render` — function returning HTML; receives [`CustomRenderContext`](https://www.adaptabletools.com/docs/reference/customrendercontext.md) (`phase`, `element`, `adaptableApi`). Return markup on `'onMount'`; return `null` on `'onDestroy'`.

Users can move and resize the window at run time; AdapTable restores the last position and size when the window opens again.

```tsx [[1, 2, "id"], [1, 3, "title"], [1, 4, "icon"], [1, 5, "position"], [1, 6, "size"], [1, 7, "render"]]
const config: CustomWindowConfig = {
  id: 'demo_window',
  title: 'Demo Window',
  icon: 'laptop',
  position: {x: 100, y: 100},
  size: {width: 400, height: 300},
  render: context => {
    if (context.phase === 'onMount') {
      return '<div style="padding:10px"><p>Custom window content</p></div>';
    }
    return null;
  },
};
```

Call `openCustomWindowPopup` wherever you have `adaptableApi` — commonly in a [Custom Toolbar Button](https://www.adaptabletools.com/docs/ui-dashboard-custom-toolbars/index.md#providing-adaptable-buttons) or [Custom Dashboard Button](https://www.adaptabletools.com/docs/ui-dashboard-buttons/index.md#custom-buttons) `onClick` handler.

```tsx
onClick: (button, context) => {
  context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
},
```

A close button is always shown in the header (not configurable). To close from code, call `closeCustomWindowPopup` with the config `id` (for example after a timeout).

```tsx
setTimeout(() => {
  context.adaptableApi.userInterfaceApi.closeCustomWindowPopup('demo_window');
}, 10000);
```

**Example: Creating a Custom Window**

Displaying a Custom Window

- This example contains a Custom Dashboard Button - `Show Custom Window` - which opens a Custom Window popup that displays the rendered content that was provided
- The Custom Window is given an initial Size and Position but is also fully movable and resizable by the run-time user
- The Custom Window contains a Close button for manual close, but is also set programmatically to close after 10 seconds (via `closeCustomWindowPopup`)

- Click **Show Custom Window** — the window opens at the configured position and size; move and resize it as needed
- Wait 10 seconds — the window closes automatically via `closeCustomWindowPopup`
- Open it again — it restores the position and size you last set

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts        [template]
  gridOptions.ts       [template]
  index.ts             [template]
  onAdaptableReady.ts  [template]
  rowData.ts           [template]
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {
  CustomRenderContext,
  CustomWindowConfig,
} from '@adaptabletools/adaptable';
import {
  AdaptableButton,
  AdaptableOptions,
  CustomToolbarButtonContext,
} from '@adaptabletools/adaptable';
import {WebFramework} from './rowData';

export const adaptableOptions: AdaptableOptions<WebFramework> = {
  primaryKey: 'id',
  adaptableId: 'Custom Window Popup',
  dashboardOptions: {
    customToolbars: [
      {
        name: 'Buttons',
        title: 'Buttons',
        toolbarButtons: [
          {
            label: 'Show Custom Window',
            buttonStyle: {
              variant: 'text',
              tone: 'info',
            },
            onClick: (
              button: AdaptableButton<CustomToolbarButtonContext>,
              context: CustomToolbarButtonContext
            ) => {
              const config: CustomWindowConfig = {
                id: 'demo_window',
                title: 'Demo Window',
                icon: 'laptop',
                position: {
                  x: 100,
                  y: 100,
                },
                size: {
                  width: 400,
                  height: 300,
                },
                render: (customRenderContext: CustomRenderContext) => {
                  if (customRenderContext.phase === 'onMount') {
                    return '<div style="background-color:lightyellow; color:brown; padding:10px; margin:10px"><p>This is a custom, movable and resizable window displaying bespoke content</p><p>The window has a close button but we have also programmatically set it to close after 10 seconds</p></div>';
                  }
                  return null;
                },
              };
              context.adaptableApi.userInterfaceApi.openCustomWindowPopup(
                config
              );
              setTimeout(() => {
                context.adaptableApi.userInterfaceApi.closeCustomWindowPopup(
                  'demo_window'
                );
              }, 10000);
            },
          },
        ],
      },
    ],
  },

  initialState: {
    Dashboard: {
      Tabs: [
        {
          Name: 'Messages',
          Toolbars: ['Buttons'],
        },
      ],
    },
    Theme: {CurrentTheme: 'dark'},
    Layout: {
      CurrentLayout: 'Standard Layout',
      Layouts: [
        {
          TableColumns: [
            'name',
            'language',
            'github_stars',
            'license',
            'has_projects',
            'has_pages',
            'created_at',
            'has_wiki',
            'updated_at',
            'pushed_at',
            'github_watchers',
            'week_issue_change',
          ],
          Name: 'Standard Layout',
          AutoSizeColumns: true,
        },
      ],
    },
  },
};
```

<!-- /framework -->

<!-- framework: angular -->
### Angular

### Providing a Custom Window

This is a standard Angular Component. Developers will typically inject the [Adaptable API](https://www.adaptabletools.com/docs/technical-reference-adaptable-api/index.md).

```ts
@Component({
  selector: 'quick-search',
  template: `
    <div class="customQuickSearch">
      <p class="customQuickSearch__intro">
        Type below to highlight cells that contain your text.
      </p>
      <label class="customQuickSearch__label" for="custom-quick-search-input"
        >Search text</label
      >
      <div class="customQuickSearch__row">
        <input
          id="custom-quick-search-input"
          class="customQuickSearch__input"
          type="text"
          [value]="searchTerm"
          (input)="onSearchChange($event)" />
        <button
          type="button"
          class="customQuickSearch__button"
          (click)="resetQuickSearch()">
          Clear
        </button>
      </div>
    </div>
  `,
})
export class QuickSearchComponent {
  searchTerm = '';

  constructor(@Inject(ADAPTABLE_API) private adaptableApi: AdaptableApi) {}

  onSearchChange(event: Event) {
    this.searchTerm = (event.target as HTMLInputElement)?.value ?? '';
    this.adaptableApi.quickSearchApi.runQuickSearch(this.searchTerm);
  }

  resetQuickSearch() {
    this.searchTerm = '';
    this.adaptableApi.quickSearchApi.clearQuickSearch();
  }
}
```

Build a [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md):

- `id` — required unique identifier; pass the same value to `closeCustomWindowPopup`
- `title` — label shown in the window header
- `icon` — optional [`AdaptableSystemIconName`](https://www.adaptabletools.com/docs/reference/adaptablesystemiconname.md) for the header
- `position` — optional initial placement in pixels (`x`, `y`) — [`WindowPosition`](https://www.adaptabletools.com/docs/reference/windowposition.md)
- `size` — optional initial dimensions (`width`, `height`) — [`WindowSize`](https://www.adaptabletools.com/docs/reference/windowsize.md)
- `frameworkComponent` — Angular component to render (`{ type: YourComponent }`)

Users can move and resize the window at run time; AdapTable restores the last position and size when the window opens again.

```tsx [[1, 2, "id"], [1, 3, "title"], [1, 4, "icon"], [1, 5, "position"], [1, 6, "size"], [1, 7, "frameworkComponent"]]
const config: CustomWindowConfig = {
  id: 'quick_search_window',
  title: 'Custom Quick Search',
  icon: 'search',
  position: {x: 100, y: 100},
  size: {width: 420, height: 280},
  frameworkComponent: {
    type: QuickSearchComponent,
  },
};
```

Call `openCustomWindowPopup` in a [Custom Dashboard Button](https://www.adaptabletools.com/docs/ui-dashboard-buttons/index.md#custom-buttons) `onClick` handler (or anywhere you have `adaptableApi`).

```tsx
onClick: (button, context) => {
  context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
},
```

A close button is always shown in the header (not configurable). To close from code, call `closeCustomWindowPopup` with the config `id`.

```tsx
context.adaptableApi.userInterfaceApi.closeCustomWindowPopup(
  'quick_search_window'
);
```

**Example: Angular Components as Custom Windows**

Providing Angular Components as Custom Windows

- Custom Window with a bespoke Angular Quick Search component, opened from a Dashboard Button

Demo project (Angular); the files without a marker are shown below:

```text
src/
  app/
    agGridModules.ts           [template]
    app.component.ts
    app.module.ts
    columnDefs.ts              [template]
    gridOptions.ts             [template]
    onAdaptableReady.ts        [template]
    quick-search.component.ts
    rowData.ts                 [template]
    styles.css                 [template]
  index.html                   [template]
  main.ts                      [template]
  polyfills.ts                 [template]
package.json                   [template]
tsconfig.json                  [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#angular).

**`src/app/quick-search.component.ts`**

```ts
import {
  ADAPTABLE_API,
  AdaptableApi,
} from '@adaptabletools/adaptable-angular-aggrid';
import {Component, Inject} from '@angular/core';

@Component({
  standalone: false,
  selector: 'quick-search',
  template: `
    <div class="customQuickSearch">
      <p class="customQuickSearch__intro">
        Type below to highlight cells that contain your text. This Custom Window calls
        <code>quickSearchApi.runQuickSearch</code> as you type.
      </p>
      <label class="customQuickSearch__label" for="custom-quick-search-input"
        >Search text</label
      >
      <div class="customQuickSearch__row">
        <input
          id="custom-quick-search-input"
          class="customQuickSearch__input"
          type="text"
          [value]="searchTerm"
          placeholder="e.g. TypeScript"
          autocomplete="off"
          (input)="onSearchChange($event)" />
        <button
          type="button"
          class="customQuickSearch__button"
          (click)="resetQuickSearch()">
          Clear
        </button>
      </div>
    </div>
  `,
  styles: [
    `
      :host {
        display: block;
        height: 100%;
        box-sizing: border-box;
        background: var(--ab-color-background);
        color: var(--ab-color-foreground);
      }

      .customQuickSearch {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: calc(var(--ab-base-space) * 2);
        padding: calc(var(--ab-base-space) * 4);
        box-sizing: border-box;
        height: 100%;
        min-height: 160px;
      }

      .customQuickSearch__intro {
        margin: 0;
        font-size: var(--ab-font-size-3, 0.875rem);
        line-height: 1.45;
      }

      .customQuickSearch__intro code {
        font-size: 0.9em;
      }

      .customQuickSearch__label {
        margin: calc(var(--ab-base-space) * 1) 0 0;
        font-size: var(--ab-font-size-3, 0.875rem);
        font-weight: 600;
      }

      .customQuickSearch__row {
        display: flex;
        align-items: center;
        gap: calc(var(--ab-base-space) * 2);
      }

      .customQuickSearch__input {
        flex: 1;
        min-width: 0;
        padding: calc(var(--ab-base-space) * 1.5);
        border: 1px solid var(--ab-color-input-border);
        border-radius: var(--ab-border-radius-input);
        background: var(--ab-color-input-background);
        color: var(--ab-color-input-foreground);
        font: inherit;
      }

      .customQuickSearch__button {
        flex-shrink: 0;
        padding: calc(var(--ab-base-space) * 1.5) calc(var(--ab-base-space) * 3);
        border: 1px solid var(--ab-color-input-border);
        border-radius: var(--ab-border-radius-button);
        background: var(--ab-color-primary);
        color: var(--ab-color-primary-foreground);
        cursor: pointer;
        font: inherit;
      }

      .customQuickSearch__button:hover {
        filter: brightness(0.97);
      }
    `,
  ],
})
export class QuickSearchComponent {
  searchTerm = '';

  constructor(@Inject(ADAPTABLE_API) private adaptableApi: AdaptableApi) {}

  onSearchChange(event: Event) {
    this.searchTerm = (event.target as HTMLInputElement)?.value ?? '';
    this.adaptableApi.quickSearchApi.runQuickSearch(this.searchTerm);
  }

  resetQuickSearch() {
    this.searchTerm = '';
    this.adaptableApi.quickSearchApi.clearQuickSearch();
  }
}
```

**`src/app/app.module.ts`**

```ts
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

import {AgGridModule} from 'ag-grid-angular';
import {AdaptableAngularAgGridModule} from '@adaptabletools/adaptable-angular-aggrid';
import {AppComponent} from './app.component';
import {QuickSearchComponent} from './quick-search.component';

@NgModule({
  declarations: [AppComponent, QuickSearchComponent],
  imports: [BrowserModule, AgGridModule, AdaptableAngularAgGridModule],
  providers: [],
  bootstrap: [AppComponent],
  // @ts-ignore FIXME #AFL required only for Codesandbox (which uses an old rendering engine), since Ivy (Angular >9) 'entryComponents' is no longer required in standalone apps
  entryComponents: [QuickSearchComponent],
})
export class AppModule {}
```

**`src/app/app.component.ts`**

```ts
import {Component} from '@angular/core';

import {AdaptableReadyInfo, AdaptableOptions} from '@adaptabletools/adaptable-angular-aggrid';
import {GridOptions} from 'ag-grid-enterprise';

// Adaptable styles
import '@adaptabletools/adaptable-angular-aggrid/index.css';

// AG Grid styles
import './styles.css';

// grid configuration
import {gridOptions} from './gridOptions';
import {agGridModules} from './agGridModules';
import {onAdaptableReady} from './onAdaptableReady';
import {WebFramework} from './rowData';
import {QuickSearchComponent} from './quick-search.component';

@Component({
  standalone: false,
  selector: 'app-root',
  template: `
    <div id="demoOutputAbove"></div>
    <adaptable-provider
      [adaptableOptions]="adaptableOpts"
      [gridOptions]="agGridOptions"
      [modules]="modules"
      (adaptableReady)="adaptableReady($event)"
      style="height: 100vh; width: 100vw;">
      <adaptable-ui style="flex: none"></adaptable-ui>
      <ag-grid-angular
        *adaptable="let adaptable"
        [gridOptions]="adaptable.gridOptions"
        [modules]="adaptable.modules"
        style="flex: 1">
      </ag-grid-angular>
    </adaptable-provider>
    <div id="demoOutputBelow"></div>
  `,
  styles: [
    `
      :host {
        height: 100vh;
        display: flex;
        flex-flow: column;
      }
    `,
  ],
})
export class AppComponent {
  adaptableOpts: AdaptableOptions<WebFramework> = {
    primaryKey: 'id',
    adaptableId: 'Angular Custom Window',
    stateOptions: {loadState: () => Promise.resolve({})},
    dashboardOptions: {
      showQuickSearchInHeader: false,
      customDashboardButtons: [
        {
          label: 'Run Custom Quick Search',
          buttonStyle: {
            variant: 'outlined',
            tone: 'accent',
          },
          onClick: (
            button: any, // AdaptableButton<DashboardButtonContext>,
            context: any // DashboardButtonContext
          ) => {
            context.adaptableApi.userInterfaceApi.openCustomWindowPopup({
              id: 'quick_search_window',
              title: 'Custom Quick Search',
              icon: 'search',
              position: {
                x: 100,
                y: 100,
              },
              size: {
                width: 420,
                height: 280,
              },
              frameworkComponent: {
                type: QuickSearchComponent,
              },
            });
          },
        },
      ],
    },
    initialState: {
      Theme: {CurrentTheme: 'dark'},
      Layout: {
        CurrentLayout: 'Standard Layout',
        Layouts: [
          {
            Name: 'Standard Layout',
            TableColumns: [
              'name',
              'language',
              'github_stars',
              'license',
              'created_at',
              'has_wiki',
              'updated_at',
              'pushed_at',
              'github_watchers',
              'description',
              'open_issues_count',
              'closed_issues_count',
              'open_pr_count',
              'closed_pr_count',
              'has_projects',
              'has_pages',
              'week_issue_change',
            ],
            AutoSizeColumns: true,
          },
        ],
      },

      QuickSearch: {
        CellMatchStyle: {
          BackColor: '#a1c0d4',
        },
      },
    },
  };
  agGridOptions: GridOptions<WebFramework> = gridOptions;
  modules = agGridModules;

  adaptableReady($event: AdaptableReadyInfo) {
    onAdaptableReady($event);
  }
}
```



<!-- /framework -->

<!-- framework: react -->
### React

### Providing a Custom Window

Create a React component that receives `adaptableApi` (typically via props from `frameworkComponent`).

```tsx
export const CustomQuickSearch = ({
  adaptableApi,
}: {
  adaptableApi: AdaptableApi;
}) => {
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    adaptableApi.quickSearchApi.runQuickSearch(searchTerm);
  }, [searchTerm, adaptableApi]);

  const clearSearch = () => {
    setSearchTerm('');
    adaptableApi.quickSearchApi.clearQuickSearch();
  };

  return (
    <div className="customQuickSearch">
      <p className="customQuickSearch__intro">
        Type below to highlight cells that contain your text.
      </p>
      <label
        className="customQuickSearch__label"
        htmlFor="custom-quick-search-input">
        Search text
      </label>
      <div className="customQuickSearch__row">
        <input
          id="custom-quick-search-input"
          className="customQuickSearch__input"
          type="text"
          value={searchTerm}
          onChange={e => setSearchTerm(e.target.value)}
        />
        <button
          type="button"
          className="customQuickSearch__button"
          onClick={clearSearch}>
          Clear
        </button>
      </div>
    </div>
  );
};
```

Build a [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md):

- `id` — required unique identifier; pass the same value to `closeCustomWindowPopup`
- `title` — label shown in the window header
- `icon` — optional [`AdaptableSystemIconName`](https://www.adaptabletools.com/docs/reference/adaptablesystemiconname.md) for the header
- `position` — optional initial placement in pixels (`x`, `y`) — [`WindowPosition`](https://www.adaptabletools.com/docs/reference/windowposition.md)
- `size` — optional initial dimensions (`width`, `height`) — [`WindowSize`](https://www.adaptabletools.com/docs/reference/windowsize.md)
- `frameworkComponent` — function receiving `{ adaptableApi }` and returning JSX

Users can move and resize the window at run time; AdapTable restores the last position and size when the window opens again.

```tsx [[1, 2, "id"], [1, 3, "title"], [1, 4, "icon"], [1, 5, "position"], [1, 6, "size"], [1, 7, "frameworkComponent"]]
const config: CustomWindowConfig = {
  id: 'quick_search_window',
  title: 'Custom Quick Search',
  icon: 'search',
  position: {x: 100, y: 100},
  size: {width: 420, height: 280},
  frameworkComponent: ({adaptableApi}) => {
    return <CustomQuickSearch adaptableApi={adaptableApi} />;
  },
};
```

Call `openCustomWindowPopup` in a [Custom Dashboard Button](https://www.adaptabletools.com/docs/ui-dashboard-buttons/index.md#custom-buttons) `onClick` handler (or anywhere you have `adaptableApi`).

```tsx
onClick: (_button, context) => {
  context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
},
```

A close button is always shown in the header (not configurable). To close from code, call `closeCustomWindowPopup` with the config `id`.

```tsx
context.adaptableApi.userInterfaceApi.closeCustomWindowPopup(
  'quick_search_window'
);
```

**Example: React Components as Custom Windows**

Providing React Components as Custom Windows

- Custom Window with a React Quick Search component, opened from a Dashboard Button

Demo project (React); the files without a marker are shown below:

```text
src/
  agGridModules.ts       [template]
  App.tsx
  columnDefs.ts          [template]
  CustomQuickSearch.tsx
  gridOptions.ts         [template]
  index.tsx              [template]
  onAdaptableReady.ts    [template]
  rowData.ts             [template]
  styles.css             [template + shown below]
package.json             [template]
tsconfig.json            [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#react).

**`src/App.tsx`**

```ts
import * as React from 'react';
import {useState} from 'react';
import {
  Adaptable,
  AdaptableApi,
  AdaptableButton,
  AdaptableOptions,
  AdaptableReadyInfo,
  CustomWindowConfig,
  DashboardButtonContext,
} from '@adaptabletools/adaptable-react-aggrid';

import {GridOptions} from 'ag-grid-enterprise';

// import adaptable css
import '@adaptabletools/adaptable-react-aggrid/index.css';
// import aggrid themes (using Balham theme)

import './styles.css';

import {onAdaptableReady} from './onAdaptableReady';
import {gridOptions} from './gridOptions';
import {agGridModules} from './agGridModules';
import {CustomQuickSearch} from './CustomQuickSearch';
import {WebFramework} from './rowData';

const App: React.FunctionComponent = () => {
  const adaptableApiRef = React.useRef<AdaptableApi>(null);

  const [adaptOptions, _setAdaptOptions] = useState<
    AdaptableOptions<WebFramework>
  >({
    primaryKey: 'id',
    adaptableId: 'React Custom Window',
    stateOptions: {loadState: () => Promise.resolve({})},
    dashboardOptions: {
      showQuickSearchInHeader: false,
      customDashboardButtons: [
        {
          label: 'Run Custom Quick Search',
          buttonStyle: {
            variant: 'outlined',
            tone: 'accent',
          },
          onClick: (
            _button: AdaptableButton<DashboardButtonContext>,
            context: DashboardButtonContext
          ) => {
            const config: CustomWindowConfig = {
              id: 'quick_search_window',
              title: 'Custom Quick Search',
              icon: 'search',
              position: {
                x: 100,
                y: 100,
              },
              size: {
                width: 420,
                height: 280,
              },
              frameworkComponent: ({adaptableApi}) => {
                return <CustomQuickSearch adaptableApi={adaptableApi} />;
              },
            };
            context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
          },
        },
      ],
    },
    initialState: {
      Theme: {CurrentTheme: 'dark'},
      Layout: {
        CurrentLayout: 'Standard Layout',
        Layouts: [
          {
            Name: 'Standard Layout',
            TableColumns: [
              'name',
              'language',
              'github_stars',
              'topics',
              'license',
              'created_at',
              'has_wiki',
              'updated_at',
              'pushed_at',
              'github_watchers',
              'description',
              'open_issues_count',
              'closed_issues_count',
              'open_pr_count',
              'closed_pr_count',
              'has_projects',
              'has_pages',
              'week_issue_change',
            ],
            AutoSizeColumns: true,
          },
        ],
      },

      QuickSearch: {
        CellMatchStyle: {
          BackColor: '#a1c0d4',
        },
      },
    },
  });
  const [agGridOptions, setAgGridOptions] = useState<GridOptions>(gridOptions);

  return (
    <div style={{display: 'flex', flexFlow: 'column', height: '100vh'}}>
      <div id="demoOutputAbove"></div>
      <Adaptable.Provider
        gridOptions={agGridOptions}
        adaptableOptions={adaptOptions}
        modules={[...agGridModules]}
        onAdaptableReady={(adaptableReadyInfo: AdaptableReadyInfo) => {
          // save a reference to adaptable api
          adaptableApiRef.current = adaptableReadyInfo.adaptableApi;
          onAdaptableReady(adaptableReadyInfo);
        }}>
        <Adaptable.UI style={{flex: 'none'}} />
        <Adaptable.AgGridReact />
      </Adaptable.Provider>
      <div id="demoOutputBelow"></div>
    </div>
  );
};
export default App;
```

**`src/CustomQuickSearch.tsx`**

```ts
import * as React from 'react';
import {useEffect, useState} from 'react';
import {AdaptableApi} from '@adaptabletools/adaptable-react-aggrid';

export const CustomQuickSearch = ({
  adaptableApi,
}: {
  adaptableApi: AdaptableApi;
}) => {
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    adaptableApi.quickSearchApi.runQuickSearch(searchTerm);
  }, [searchTerm, adaptableApi]);

  const onSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(event.target.value);
  };

  const clearSearch = () => {
    setSearchTerm('');
    adaptableApi.quickSearchApi.clearQuickSearch();
  };

  return (
    <div className="customQuickSearch">
      <p className="customQuickSearch__intro">
        Type below to highlight cells that contain your text. This Custom Window calls{' '}
        <code>quickSearchApi.runQuickSearch</code> as you type.
      </p>
      <label className="customQuickSearch__label" htmlFor="custom-quick-search-input">
        Search text
      </label>
      <div className="customQuickSearch__row">
        <input
          id="custom-quick-search-input"
          className="customQuickSearch__input ab-Input ab-Input--type-text"
          type="text"
          value={searchTerm}
          onChange={onSearchChange}
          placeholder="e.g. TypeScript"
          autoComplete="off"
        />
        <button type="button" className="customQuickSearch__button" onClick={clearSearch}>
          Clear
        </button>
      </div>
    </div>
  );
};
```

**`src/styles.css`**

```css
.customQuickSearch {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: calc(var(--ab-base-space) * 2);
  padding: calc(var(--ab-base-space) * 4);
  box-sizing: border-box;
  height: 100%;
  min-height: 160px;
  color: var(--ab-color-foreground, #333);
  background: var(--ab-color-background, #fff);
}

.customQuickSearch__intro {
  margin: 0;
  font-size: var(--ab-font-size-3, 0.875rem);
  line-height: 1.45;
}

.customQuickSearch__intro code {
  font-size: 0.9em;
}

.customQuickSearch__label {
  margin: calc(var(--ab-base-space) * 1) 0 0;
  font-size: var(--ab-font-size-3, 0.875rem);
  font-weight: 600;
}

.customQuickSearch__row {
  display: flex;
  align-items: center;
  gap: calc(var(--ab-base-space) * 2);
}

.customQuickSearch__input {
  flex: 1;
  min-width: 0;
}

.customQuickSearch__button {
  flex-shrink: 0;
  padding: calc(var(--ab-base-space) * 1.5) calc(var(--ab-base-space) * 3);
  border: 1px solid var(--ab-color-input-border, #ccc);
  border-radius: var(--ab-border-radius-button, 4px);
  background: var(--ab-color-primary, #e9e9e9);
  color: var(--ab-color-primary-foreground, #333);
  cursor: pointer;
  font: inherit;
}

.customQuickSearch__button:hover {
  filter: brightness(0.97);
}
```



<!-- /framework -->

<!-- framework: vue -->
### Vue

### Providing a Custom Window

Create a Vue component that receives `adaptableApi` via props from `frameworkComponent`.

```vue
<script setup>
import {ref, watch} from 'vue';

const props = defineProps({adaptableApi: {type: Object, required: true}});
const searchTerm = ref('');

watch(searchTerm, value => {
  props.adaptableApi.quickSearchApi.runQuickSearch(value);
});
</script>

<template>
  <div class="customQuickSearch">
    <input v-model="searchTerm" type="text" />
    <button
      @click="
        searchTerm = '';
        adaptableApi.quickSearchApi.clearQuickSearch();
      ">
      Clear
    </button>
  </div>
</template>
```

Build a [`CustomWindowConfig`](https://www.adaptabletools.com/docs/reference/customwindowconfig.md):

- `id` — required unique identifier; pass the same value to `closeCustomWindowPopup`
- `title` — label shown in the window header
- `icon` — optional [`AdaptableSystemIconName`](https://www.adaptabletools.com/docs/reference/adaptablesystemiconname.md) for the header
- `position` — optional initial placement in pixels (`x`, `y`) — [`WindowPosition`](https://www.adaptabletools.com/docs/reference/windowposition.md)
- `size` — optional initial dimensions (`width`, `height`) — [`WindowSize`](https://www.adaptabletools.com/docs/reference/windowsize.md)
- `frameworkComponent` — factory returning `h(YourComponent, { adaptableApi })`

Users can move and resize the window at run time; AdapTable restores the last position and size when the window opens again.

```tsx [[1, 2, "id"], [1, 3, "title"], [1, 4, "icon"], [1, 5, "position"], [1, 6, "size"], [1, 7, "frameworkComponent"]]
const config: CustomWindowConfig = {
  id: 'quick_search_window',
  title: 'Custom Quick Search',
  icon: 'search',
  position: {x: 100, y: 100},
  size: {width: 420, height: 280},
  frameworkComponent: ({adaptableApi}) => h(CustomQuickSearch, {adaptableApi}),
};
```

Call `openCustomWindowPopup` in a [Custom Dashboard Button](https://www.adaptabletools.com/docs/ui-dashboard-buttons/index.md#custom-buttons) `onClick` handler (or anywhere you have `adaptableApi`).

```tsx
onClick: (button, context) => {
  context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
},
```

A close button is always shown in the header (not configurable). To close from code, call `closeCustomWindowPopup` with the config `id`.

```tsx
context.adaptableApi.userInterfaceApi.closeCustomWindowPopup(
  'quick_search_window'
);
```

**Example: Vue Components as Custom Windows**

Providing Vue Components as Custom Windows

- Custom Window with a Vue Quick Search component, opened from a Dashboard Button

Demo project (Vue); the files without a marker are shown below:

```text
public/
  index.html             [template]
src/
  agGridModules.ts       [template]
  App.vue
  columnDefs.ts          [template]
  CustomQuickSearch.vue
  gridOptions.ts         [template]
  main.ts                [template]
  onAdaptableReady.ts    [template]
  rowData.ts             [template]
  shims-vue.d.ts         [template]
  styles.css             [template]
.babelrc                 [template]
package.json             [template]
tsconfig.json            [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vue).

**`src/App.vue`**

```ts
<script setup lang="ts">
import {h} from 'vue';
import {
  AdaptableProvider,
  AdaptableAgGridVue,
  AdaptableUI,
  AdaptableOptions,
  AdaptableButton,
  CustomWindowConfig,
  DashboardButtonContext,
} from '@adaptabletools/adaptable-vue3-aggrid';

import {onAdaptableReady} from './onAdaptableReady';
import {gridOptions} from './gridOptions';
import {agGridModules} from './agGridModules';
import {WebFramework} from './rowData';
import CustomQuickSearch from './CustomQuickSearch.vue';

const adaptableOpts: AdaptableOptions<WebFramework> = {
  primaryKey: 'id',
  adaptableId: 'Vue Custom Window',
  stateOptions: {loadState: () => Promise.resolve({})},
  dashboardOptions: {
    showQuickSearchInHeader: false,
    customDashboardButtons: [
      {
        label: 'Run Custom Quick Search',
        buttonStyle: {
          variant: 'outlined',
          tone: 'accent',
        },
        onClick: (
          button: AdaptableButton<DashboardButtonContext>,
          context: DashboardButtonContext
        ) => {
          const config: CustomWindowConfig = {
            id: 'quick_search_window',
            title: 'Custom Quick Search',
            icon: 'search',
            position: {x: 100, y: 100},
            size: {width: 420, height: 280},
            frameworkComponent: ({adaptableApi}) =>
              h(CustomQuickSearch, {adaptableApi}),
          };
          context.adaptableApi.userInterfaceApi.openCustomWindowPopup(config);
        },
      },
    ],
  },
  initialState: {
    Theme: {CurrentTheme: 'dark'},
    Layout: {
      CurrentLayout: 'Standard Layout',
      Layouts: [
        {
          Name: 'Standard Layout',
          TableColumns: [
            'name',
            'language',
            'github_stars',
            'license',
            'created_at',
            'has_wiki',
            'updated_at',
            'pushed_at',
            'github_watchers',
            'description',
            'open_issues_count',
            'closed_issues_count',
            'open_pr_count',
            'closed_pr_count',
            'has_projects',
            'has_pages',
            'week_issue_change',
          ],
          AutoSizeColumns: true,
        },
      ],
    },

    QuickSearch: {
      Style: {
        BackColor: '#a1c0d4',
      },
    },
  },
};
</script>

<template>
  <AdaptableProvider
    :gridOptions="gridOptions"
    :adaptableOptions="adaptableOpts"
    :modules="agGridModules"
    @onAdaptableReady="onAdaptableReady">
    <div style="display: flex; flex-direction: column; height: 100vh">
      <AdaptableUI />
      <AdaptableAgGridVue style="flex: 1" />
    </div>
  </AdaptableProvider>
</template>
```

**`src/CustomQuickSearch.vue`**

```ts
<script setup lang="ts">
import {ref, watch} from 'vue';

const props = defineProps({
  adaptableApi: {
    type: Object,
    required: true,
  },
});

const searchTerm = ref('');

watch(searchTerm, newSearchTerm => {
  props.adaptableApi.quickSearchApi.runQuickSearch(newSearchTerm);
});

const clearSearch = () => {
  searchTerm.value = '';
  props.adaptableApi.quickSearchApi.clearQuickSearch();
};
</script>

<template>
  <div class="customQuickSearch">
    <p class="customQuickSearch__intro">
      Type below to highlight cells that contain your text. This Custom Window calls
      <code>quickSearchApi.runQuickSearch</code> as you type.
    </p>
    <label class="customQuickSearch__label" for="custom-quick-search-input">Search text</label>
    <div class="customQuickSearch__row">
      <input
        id="custom-quick-search-input"
        type="text"
        class="customQuickSearch__input ab-Input ab-Input--type-text"
        v-model="searchTerm"
        placeholder="e.g. TypeScript"
        autocomplete="off" />
      <button type="button" class="customQuickSearch__button" @click="clearSearch">
        Clear
      </button>
    </div>
  </div>
</template>

<style scoped>
.customQuickSearch {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: calc(var(--ab-base-space) * 2);
  padding: calc(var(--ab-base-space) * 4);
  box-sizing: border-box;
  height: 100%;
  min-height: 160px;
  color: var(--ab-color-foreground, #333);
  background: var(--ab-color-background, #fff);
}

.customQuickSearch__intro {
  margin: 0;
  font-size: var(--ab-font-size-3, 0.875rem);
  line-height: 1.45;
}

.customQuickSearch__intro code {
  font-size: 0.9em;
}

.customQuickSearch__label {
  margin: calc(var(--ab-base-space) * 1) 0 0;
  font-size: var(--ab-font-size-3, 0.875rem);
  font-weight: 600;
}

.customQuickSearch__row {
  display: flex;
  align-items: center;
  gap: calc(var(--ab-base-space) * 2);
}

.customQuickSearch__input {
  flex: 1;
  min-width: 0;
}

.customQuickSearch__button {
  flex-shrink: 0;
  padding: calc(var(--ab-base-space) * 1.5) calc(var(--ab-base-space) * 3);
  border: 1px solid var(--ab-color-input-border, #ccc);
  border-radius: var(--ab-border-radius-button, 4px);
  background: var(--ab-color-primary, #e9e9e9);
  color: var(--ab-color-primary-foreground, #333);
  cursor: pointer;
  font: inherit;
}

.customQuickSearch__button:hover {
  filter: brightness(0.97);
}
</style>
```



<!-- /framework -->

It is also possible to display [Progress Indicators](https://www.adaptabletools.com/docs/ui-progress-indicator/index.md) when running a long task
