Merge branch 'main' into feature/various-clean-up-and-corrections
This commit is contained in:
6
src/Umbraco.Web.UI.Client/package-lock.json
generated
6
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -17874,9 +17874,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.27",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
||||
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ export * from './input-checkbox-list/index.js';
|
||||
export * from './input-color/index.js';
|
||||
export * from './input-eye-dropper/index.js';
|
||||
export * from './input-list-base/index.js';
|
||||
export * from './input-markdown-editor/index.js';
|
||||
export * from './input-multi-url/index.js';
|
||||
export * from './input-tiny-mce/index.js';
|
||||
export * from './input-number-range/index.js';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './input-markdown.element.js';
|
||||
@@ -0,0 +1,81 @@
|
||||
import { UmbCodeEditorElement, loadCodeEditor } from '@umbraco-cms/backoffice/code-editor';
|
||||
import { css, html, customElement, query, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
/**
|
||||
* @element umb-input-markdown
|
||||
* @fires change - when the value of the input changes
|
||||
*/
|
||||
|
||||
@customElement('umb-input-markdown')
|
||||
export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
preview?: boolean;
|
||||
|
||||
#isCodeEditorReady = new UmbBooleanState(false);
|
||||
isCodeEditorReady = this.#isCodeEditorReady.asObservable();
|
||||
|
||||
@query('umb-code-editor')
|
||||
_codeEditor?: UmbCodeEditorElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#loadCodeEditor();
|
||||
}
|
||||
|
||||
async #loadCodeEditor() {
|
||||
try {
|
||||
await loadCodeEditor();
|
||||
this._codeEditor?.editor?.updateOptions({
|
||||
lineNumbers: false,
|
||||
minimap: false,
|
||||
folding: false,
|
||||
});
|
||||
this.#isCodeEditorReady.next(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <div id="actions"></div>
|
||||
<umb-code-editor language="markdown" .code=${this.value as string}></umb-code-editor>
|
||||
${this.renderPreview()}`;
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
if (!this.preview) return;
|
||||
return html`<div>TODO Preview</div>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#actions {
|
||||
background-color: var(--uui-color-background-alt);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
umb-code-editor {
|
||||
height: 200px;
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px solid var(--uui-color-divider-emphasis);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-markdown': UmbInputMarkdownElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Meta, StoryObj } from '@storybook/web-components';
|
||||
import './input-markdown.element.js';
|
||||
import type { UmbInputMarkdownElement } from './input-markdown.element.js';
|
||||
|
||||
const meta: Meta<UmbInputMarkdownElement> = {
|
||||
title: 'Components/Inputs/Markdown',
|
||||
component: 'umb-input-markdown',
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<UmbInputMarkdownElement>;
|
||||
|
||||
export const Overview: Story = {};
|
||||
@@ -3,6 +3,7 @@ import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbInputMarkdownElement } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-markdown-editor
|
||||
@@ -15,11 +16,24 @@ export class UmbPropertyEditorUIMarkdownEditorElement
|
||||
@property()
|
||||
value = '';
|
||||
|
||||
@state()
|
||||
private _preview?: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: UmbPropertyEditorConfigCollection;
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
this._preview = config?.getValueByAlias('preview');
|
||||
}
|
||||
|
||||
#onChange(e: Event) {
|
||||
this.value = (e.target as UmbInputMarkdownElement).value as string;
|
||||
this.dispatchEvent(new CustomEvent('property-value-change'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div>umb-property-editor-ui-markdown-editor</div>`;
|
||||
return html`<umb-input-markdown
|
||||
?preview=${this._preview}
|
||||
@change=${this.#onChange}
|
||||
.value=${this.value}></umb-input-markdown>`;
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import { Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
import * as LocalizeStories from './sorter.stories';
|
||||
|
||||
<Meta title="API/Drag and Drop/Intro" />
|
||||
|
||||
# Drag and Drop
|
||||
|
||||
Drag and Drop can be done by using the `UmbSorterController`
|
||||
|
||||
To get started using drag and drop, finish the following steps:
|
||||
|
||||
- Preparing the model
|
||||
- Setting the configuration
|
||||
- Registering the controller
|
||||
|
||||
#### Preparing the model
|
||||
|
||||
The SorterController needs a model to know what item it is we are dealing with.
|
||||
|
||||
```typescript
|
||||
type MySortEntryType = {
|
||||
id: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const awesomeModel: Array<MySortEntryType> = [
|
||||
{
|
||||
id: '0',
|
||||
value: 'Entry 0',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
value: 'Entry 1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'Entry 2',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
#### Setting the configuration
|
||||
|
||||
When you know the model of which that is being sorted, you can set up the configuration.
|
||||
The configuration has a lot of optional options, but the required ones are:
|
||||
|
||||
- compareElementToModel()
|
||||
- querySelectModelToElement()
|
||||
- identifier
|
||||
- itemSelector
|
||||
- containerSelector
|
||||
|
||||
It can be set up as following:
|
||||
|
||||
```typescript
|
||||
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
type MySortEntryType = {...}
|
||||
const awesomeModel: Array<MySortEntryType> = [...]
|
||||
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
compareElementToModel: (element: HTMLElement, model: MySortEntryType) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.id;
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: MySortEntryType) => {
|
||||
return container.querySelector('data-sort-entry-id=[' + modelEntry.id + ']');
|
||||
},
|
||||
identifier: 'test-sorter',
|
||||
itemSelector: 'li',
|
||||
containerSelector: 'ul',
|
||||
};
|
||||
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${awesomeModel.map(
|
||||
(entry) =>
|
||||
html`<li data-sort-entry-id="${entry.id}">
|
||||
<span>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Registering the controller
|
||||
|
||||
When the model and configuration are available we can register the controller and tell the controller what model we are using.
|
||||
|
||||
```typescript
|
||||
import { UmbSorterController, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
type MySortEntryType = {...}
|
||||
const awesomeModel: Array<MySortEntryType> = [...]
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {...}
|
||||
|
||||
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
#sorter = new UmbSorterController(this, MY_SORTER_CONFIG);
|
||||
|
||||
constructor() {
|
||||
this.#sorter.setModel(awesomeModel);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${awesomeModel.map(
|
||||
(entry) =>
|
||||
html`<li data-sort-entry-id="${entry.id}">
|
||||
<span>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Placeholder
|
||||
|
||||
While dragging an entry, the entry will get an additional class that can be styled.
|
||||
The class is by default `--umb-sorter-placeholder` but can be changed via the configuration to a different value.
|
||||
|
||||
```typescript
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
...
|
||||
placeholderClass: 'dragging-now',
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
static styles = [
|
||||
css`
|
||||
li {
|
||||
display:relative;
|
||||
}
|
||||
|
||||
li.dragging-now span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
li.dragging-now::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border: 1px dashed grey;
|
||||
}
|
||||
`,
|
||||
];
|
||||
```
|
||||
|
||||
### Horizontal sorting
|
||||
|
||||
By default, the sorter controller will sort vertically. You can sort your model horizontally by setting the `resolveVerticalDirection` to return false.
|
||||
|
||||
```typescript
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
...
|
||||
resolveVerticalDirection: () => return false,
|
||||
};
|
||||
```
|
||||
|
||||
### Performing logic when using the controller (TODO: Better title)
|
||||
|
||||
Let's say your model has a property sortOrder that you would like to update when the entry is being sorted.
|
||||
You can add your code logic in the configuration option `performItemInsert` and `performItemRemove`
|
||||
|
||||
```typescript
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
#sorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
performItemInsert: ({ item, newIndex }) => {
|
||||
console.log(item, newIndex);
|
||||
// Perform some logic here to calculate the new sortOrder & save it.
|
||||
return true;
|
||||
},
|
||||
performItemRemove: () => true,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1,24 +1,23 @@
|
||||
import { UmbSorterConfig, UmbSorterController } from '../sorter.controller.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
type SortEntryType = {
|
||||
id: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const sorterConfig: UmbSorterConfig<SortEntryType> = {
|
||||
const SORTER_CONFIG: UmbSorterConfig<SortEntryType> = {
|
||||
compareElementToModel: (element: HTMLElement, model: SortEntryType) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.id;
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: SortEntryType) => {
|
||||
return container.querySelector('data-sort-entry-id[' + modelEntry.id + ']');
|
||||
return container.querySelector('data-sort-entry-id=[' + modelEntry.id + ']');
|
||||
},
|
||||
identifier: 'test-sorter',
|
||||
itemSelector: 'li',
|
||||
containerSelector: 'ul',
|
||||
};
|
||||
|
||||
const model: Array<SortEntryType> = [
|
||||
{
|
||||
id: '0',
|
||||
@@ -38,18 +37,35 @@ const model: Array<SortEntryType> = [
|
||||
export default class UmbTestSorterControllerElement extends UmbLitElement {
|
||||
public sorter;
|
||||
|
||||
@state()
|
||||
private vertical = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.sorter = new UmbSorterController(this, sorterConfig);
|
||||
this.sorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
resolveVerticalDirection: () => {
|
||||
this.vertical ? true : false;
|
||||
},
|
||||
});
|
||||
this.sorter.setModel(model);
|
||||
}
|
||||
|
||||
#toggle() {
|
||||
this.vertical = !this.vertical;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
<uui-button label="Change direction" look="outline" color="positive" @click=${this.#toggle}>
|
||||
Horizontal/Vertical
|
||||
</uui-button>
|
||||
<ul class="${this.vertical ? 'vertical' : 'horizontal'}">
|
||||
${model.map(
|
||||
(entry) => html`<li id="${'sort' + entry.id}" data-sort-entry-id="${entry.id}">${entry.value}</li>`
|
||||
(entry) =>
|
||||
html`<li class="item" data-sort-entry-id="${entry.id}">
|
||||
<span><uui-icon name="umb:wand"></uui-icon>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
@@ -59,39 +75,47 @@ export default class UmbTestSorterControllerElement extends UmbLitElement {
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
ul.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: grab;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
|
||||
li span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
background: #eee;
|
||||
background-color: rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background: #ddd !important;
|
||||
cursor: move;
|
||||
li.--umb-sorter-placeholder span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
li:active {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
#sort0 {
|
||||
background: #f00;
|
||||
}
|
||||
|
||||
#sort1 {
|
||||
background: #0f0;
|
||||
}
|
||||
|
||||
#sort2 {
|
||||
background: #c9da10;
|
||||
li.--umb-sorter-placeholder::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px dashed var(--uui-color-divider-emphasis);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user