import { Meta } from '@storybook/blocks';

<Meta title="API/Modals/Intro" />

# Modals

A modal is a popup that darkens the background and has focus lock. There are two types of modals: "dialog" and "sidebar".

**Dialog modals** appears in the middle of the screen.
| option | values |
|:------:|:--------------------------:|
| No options yet | |

**Sidebar modals** slides in from the right.
| option | values |
|:------:|:--------------------------:|
| size | small, medium, large, full |

## Basic Usage

### Consume UmbModalManagerContext from an element

The modal manager context can be used to open modals.

```ts
import { LitElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/element';
import { UmbModalManagerContext, UMB_MODAL_CONTEXT_ALIAS } from '@umbraco-cms/modal';

class MyElement extends UmbElementMixin(LitElement) {
	#modalManagerContext?: UmbModalManagerContext;

	constructor() {
		super();
		this.consumeContext(UMB_MODAL_CONTEXT_ALIAS, (instance) => {
			this.#modalManagerContext = instance;
			// modalManagerContext is now ready to be used.
		});
	}
}
```

### Open a modal

A modal can be opned in two ways. Either you registrer the modal with a route or at runtime open the modal. Notice the first one will enable users to deep link to the modal, this might be preferable in some cases if not go for the last.

#### Simple Modal Route Registration

A modal can be registred via the UmbModalRouteRegistrationController. The registration will accept a modal token (or extension alias).

Notice we are using a Controller here, this means your element has to be a Controller Host (TODO: Insert link to story about Controller Host also available through the UmbElementMixin)

```ts
this.myModalRegistration = new UmbModalRouteRegistrationController(this, UMB_LINK_PICKER_MODAL)
	.onSubmit((submitData) => {
		console.log('Modal submitted with data'.submitData);
	})
	.observeRouteBuilder((routeBuilder) => {
		this._modalRouteBuilder = routeBuilder;
	});
```

The registration holds an instance of its UmbModalHandler when the modal it active.
The modal registration accepts 4 different callbacks:

- onSetup - called when the modal is opened
- onSubmit - called when the modal is submitted
- onReject - called when the modal is rejected
- observeRouteBuilder - called when the modal route changes, use the given route builder to build a route to open the modal.

#### TODOS:

descripe the addional features of the route Registration:

##### Hints:

- Add unique parts to the path. (How is this cone properly as part of a Property Editor)
- A modal registred in a dashboard can be relatively simple.
- A modal registred in a property editor, needs to become specific for the property and the variant of that property.

- Build some data for the setup.
- Reject a modal by returning false in setup.
- Use a param as part of setup to determin the data going to the modal.

#### Modal registration for UI as part of a Property Editor

```ts


	@property()
	public set alias(value: string | undefined) {
		this.myModalRegistration.setUniquePathValue('propertyAlias', value);
	}

	@property()
	public set variantId(value: string | UmbVariantId | undefined) {
		this.myModalRegistration.setUniquePathValue('variantId', value?.toString());
	}

	private _items = [
		{ name: 'Item 1' },
		{ name: 'Item 2' },
		{ name: 'Item 3' },
	]


	constructor() {
		super();

		this.myModalRegistration = new UmbModalRouteRegistrationController(
			this,
			MY_MODAL_TOKEN
		)
		.addAdditionalPath(`:index`)
		.addUniquePaths(['propertyAlias', 'variantId'])
		.onSetup((params) => {
			// Get item index:
			const indexParam = params.index;
			if (!indexParam) return false;
			let index: number | null = parseInt(params.index);
			if (Number.isNaN(index)) return false;

			// Use the index to find data:
			let data = null;
			if (index >= 0 && index < this._items.length) {
				data = this._items[index];
			} else {
				// If not then make a new pick:
				index = null;
			}

			return { data: {
				index: index,
				itemData: {
					name: data?.name
				},
			}};
		})
		.onSubmit((submitData) => {
			if (!submitData) return;
			this._items[submitData.index] = submitData.itemData;
		})
		.observeRouteBuilder((routeBuilder) => {
			this._modalRouteBuilder = routeBuilder;
		});
	}

	render() {
		return html`
			${this._items?.map((item, index) =>
				html`<uui-button look="placeholder" label="Edit item ${index}" .href=${this._modalRouteBuilder?.({ index })}>Add</uui-button>`
			)}
		`;
	}
```

#### Generate the URL to a Modal Route Registration

The Modal registration has an option to retrive a URL Builder, this is a function that can be used to generate a URL to a modal.

```ts
const modalLink = _myUrlBuilder?.({ alias: 'my-input-alias' });
```

The modalLink from above could look like: /umbraco/backoffice/my/location/modal/Our.Modal.SomethingPicker/my-input-alias

Notice the Property Editor registration will add the property alias and variant id to the URL, so it becomes:

/umbraco/backoffice/my/location/modal/Our.Modal.SomethingPicker/my-property-alias/en-us/my-input-alias

#### Open A Modal at own initiative

A modal can be opened by calling the open method on the UmbModalManagerContext. The methods will accept a modal token (or extension alias), an optional dataset, and optional modal options. It returns an instance of UmbModalHandler.

```ts
import { html, LitElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/element';
import { UmbModalManagerContext, UMB_MODAL_CONTEXT_ALIAS } from '@umbraco-cms/modal';
class MyElement extends UmbElementMixin(LitElement) {
	#modalManagerContext?: UmbmodalManagerContext;

	constructor() {
		super();
		this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => {
			this.#modalManagerContext = instance;
			// modalManagerContext is now ready to be used
		});
	}

	#onClick() {
		const data = {'data goes here'};
		const value = {'initial value go here'};
		const modalContext = this.#modalManagerContext?.open(MY_MODAL_TOKEN), {data: data, value: value});

		modalContext?.onSubmit().then((value) => {
			// if modal submitted, then data is supplied here.
		});
	}

	render() {
		return html`<button @click=${this.#onClick}>Open modal</button>`;
	}
}
```

## Create a custom modal

### Register in the extension registry

The manifest

```json
{
	"type": "modal",
	"alias": "My.Modal",
	"name": "My Modal",
	"js": "../path/to/my-modal.element.js"
}
```

### Create a modal token

A modal token is a string that identifies a modal. It should be the modal extension alias. It is used to open a modal and is also to set default options for the modal.

```ts
interface MyModalData = {
	headline: string;
	content: string;
}

interface MyModalValue = {
	myData: string;
}

const MY_MODAL_TOKEN = new ModalToken<MyModalData, MyModalValue>('My.Modal',
	config: {
		type: 'sidebar',
		size: 'small'
	}
);
```

The Modal element

```ts
import { html, LitElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/element';
import type { UmbModalHandler } from '@umbraco-cms/modal';

class MyDialog extends UmbElementMixin(LitElement) {
	// the modal handler will be injected into the element when the modal is opened.
	@property({ attribute: false })
	modalContext?: UmbModalHandler<MyModalData, MyModalValue>;

	private _handleCancel() {
		this.modalContext?.close();
	}

	private _handleSubmit() {
		/* Value is set on modal context. and then parsed on to the consumer of the submit promise. */
		this.modalContext?.setValue({ myData: 'hello world' });
		this.modalContext?.submit();
	}

	render() {
		return html`
			<div>
				<h1>My Modal</h1>
				<button @click=${this._handleCancel}>Cancel</button>
				<button @click=${this._handleSubmit}>Submit</button>
			</div>
		`;
	}
}
```
