Revert "Merge pull request #1174 from umbraco/feature/split-panel"
This reverts commit214f857034, reversing changes made to0915f33cdd.
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
<style>
|
||||
/* Make stories able to use 100% height */
|
||||
html {
|
||||
height: 100vh;
|
||||
}
|
||||
/* Make stories able to use 100% height */
|
||||
body,
|
||||
#storybook-root,
|
||||
#root-inner {
|
||||
height: 100%;
|
||||
}
|
||||
html {
|
||||
height: 100vh;
|
||||
}
|
||||
/* Make stories able to use 100% height */
|
||||
body,
|
||||
#root,
|
||||
#root-inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px !important;
|
||||
@@ -40,3 +40,4 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -29,6 +29,5 @@ export * from './input-upload-field/index.js';
|
||||
export * from './multiple-color-picker-input/index.js';
|
||||
export * from './multiple-text-string-input/index.js';
|
||||
export * from './popover-layout/index.js';
|
||||
export * from './split-panel/index.js';
|
||||
export * from './table/index.js';
|
||||
export * from './tooltip-menu/index.js';
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import './split-panel.element.js';
|
||||
|
||||
export * from './split-panel.element.js';
|
||||
@@ -1,312 +0,0 @@
|
||||
import {
|
||||
type PropertyValueMap,
|
||||
LitElement,
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
/**
|
||||
* Custom element for a split panel with adjustable divider.
|
||||
* @element umb-split-panel
|
||||
* @slot start - Content for the start panel.
|
||||
* @slot end - Content for the end panel.
|
||||
* @cssprop --umb-split-panel-initial-position - Initial position of the divider.
|
||||
* @cssprop --umb-split-panel-start-min-width - Minimum width of the start panel.
|
||||
* @cssprop --umb-split-panel-end-min-width - Minimum width of the end panel.
|
||||
* @cssprop --umb-split-panel-divider-touch-area-width - Width of the divider touch area.
|
||||
* @cssprop --umb-split-panel-divider-width - Width of the divider.
|
||||
* @cssprop --umb-split-panel-divider-color - Color of the divider.
|
||||
*/
|
||||
@customElement('umb-split-panel')
|
||||
export class UmbSplitPanelElement extends LitElement {
|
||||
@query('#main') mainElement!: HTMLElement;
|
||||
@query('#divider-touch-area') dividerTouchAreaElement!: HTMLElement;
|
||||
@query('#divider') dividerElement!: HTMLElement;
|
||||
|
||||
/**
|
||||
* Snap points for the divider position.
|
||||
* Pixel or percent space-separated values: e.g., "100px 50% -75% -200px".
|
||||
* Negative values are relative to the end of the container.
|
||||
*/
|
||||
@property({ type: String }) snap?: string; //TODO: Consider using css variables for snap points.
|
||||
|
||||
/**
|
||||
* Locking mode for the split panel.
|
||||
* Possible values: "start", "end", "none" (default).
|
||||
*/
|
||||
@property({ type: String }) lock: 'start' | 'end' | 'none' = 'none';
|
||||
|
||||
/**
|
||||
* Initial position of the divider.
|
||||
* Pixel or percent value: e.g., "100px" or "25%".
|
||||
* Defaults to a CSS variable if not set: "var(--umb-split-panel-initial-position) which defaults to 50%".
|
||||
*/
|
||||
@property({ type: String, reflect: true }) position = 'var(--umb-split-panel-initial-position)';
|
||||
//TODO: Add support for negative values (relative to end of container) similar to snap points.
|
||||
|
||||
/** Width of the locked panel when in "start" or "end" lock mode */
|
||||
#lockedPanelWidth: number = 0;
|
||||
/** Resize observer for tracking container size changes. */
|
||||
#resizeObserver?: ResizeObserver;
|
||||
/** Pixel value for the snap threshold. Determines how close the divider needs to be to a snap point to snap to it. */
|
||||
readonly #SNAP_THRESHOLD = 25 as const;
|
||||
|
||||
@state() _hasStartPanel = false;
|
||||
@state() _hasEndPanel = false;
|
||||
get #hasBothPanels() {
|
||||
return this._hasStartPanel && this._hasEndPanel;
|
||||
}
|
||||
|
||||
#hasInitialized = false;
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.#disconnect();
|
||||
}
|
||||
|
||||
protected updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
super.updated(_changedProperties);
|
||||
|
||||
if (!this.#hasInitialized) return;
|
||||
|
||||
if (_changedProperties.has('position')) {
|
||||
if (this.lock !== 'none') {
|
||||
const { width } = this.mainElement.getBoundingClientRect();
|
||||
|
||||
let pos = parseFloat(this.position);
|
||||
|
||||
if (this.position.endsWith('%')) {
|
||||
pos = (pos / 100) * width;
|
||||
}
|
||||
|
||||
const lockedPanelWidth = this.lock === 'start' ? pos : width - pos;
|
||||
this.#lockedPanelWidth = lockedPanelWidth;
|
||||
}
|
||||
|
||||
this.#updateSplit();
|
||||
}
|
||||
}
|
||||
|
||||
#clamp(value: number, min: number, max: number) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
#onResize(entries: ResizeObserverEntry[]) {
|
||||
const mainContainerWidth = entries[0].contentRect.width;
|
||||
|
||||
if (this.lock === 'start') {
|
||||
this.#setPosition(this.#lockedPanelWidth);
|
||||
}
|
||||
if (this.lock === 'end') {
|
||||
this.#setPosition(mainContainerWidth - this.#lockedPanelWidth);
|
||||
}
|
||||
}
|
||||
|
||||
#setPosition(pos: number) {
|
||||
const { width } = this.mainElement.getBoundingClientRect();
|
||||
const localPos = this.#clamp(pos, 0, width);
|
||||
const percentagePos = (localPos / width) * 100;
|
||||
this.position = percentagePos + '%';
|
||||
}
|
||||
|
||||
#updateSplit() {
|
||||
// If lock is none
|
||||
let maxStartWidth = this.position;
|
||||
let maxEndWidth = '1fr';
|
||||
|
||||
if (this.lock === 'start') {
|
||||
maxStartWidth = this.#lockedPanelWidth + 'px';
|
||||
maxEndWidth = `1fr`;
|
||||
}
|
||||
if (this.lock === 'end') {
|
||||
maxStartWidth = `1fr`;
|
||||
maxEndWidth = this.#lockedPanelWidth + 'px';
|
||||
}
|
||||
|
||||
this.mainElement.style.gridTemplateColumns = `
|
||||
minmax(var(--umb-split-panel-start-min-width), ${maxStartWidth})
|
||||
0px
|
||||
minmax(var(--umb-split-panel-end-min-width), ${maxEndWidth})
|
||||
`;
|
||||
}
|
||||
|
||||
#onDragStart = (event: PointerEvent | TouchEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const move = (event: PointerEvent) => {
|
||||
const { clientX } = event;
|
||||
const { left, width } = this.mainElement.getBoundingClientRect();
|
||||
const localPos = this.#clamp(clientX - left, 0, width);
|
||||
const mappedPos = mapXAxisToSnap(localPos, width);
|
||||
|
||||
this.#lockedPanelWidth = this.lock === 'start' ? mappedPos : width - mappedPos;
|
||||
this.#setPosition(mappedPos);
|
||||
};
|
||||
|
||||
function stop() {
|
||||
document.removeEventListener('pointermove', move);
|
||||
document.removeEventListener('pointerup', stop);
|
||||
}
|
||||
|
||||
const mapXAxisToSnap = (xPos: number, containerWidth: number) => {
|
||||
const snaps = this.snap?.split(' ');
|
||||
if (!snaps) return xPos;
|
||||
|
||||
const snapsInPixels = snaps.map((snap) => {
|
||||
let snapPx = parseFloat(snap);
|
||||
|
||||
if (snap.endsWith('%')) {
|
||||
snapPx = (snapPx / 100) * containerWidth;
|
||||
}
|
||||
|
||||
if (snap.startsWith('-')) {
|
||||
snapPx = containerWidth + snapPx;
|
||||
}
|
||||
|
||||
return snapPx;
|
||||
});
|
||||
|
||||
const closestSnap = snapsInPixels.reduce((prev, curr) => {
|
||||
return Math.abs(curr - xPos) < Math.abs(prev - xPos) ? curr : prev;
|
||||
});
|
||||
|
||||
if (closestSnap < xPos + this.#SNAP_THRESHOLD && closestSnap > xPos - this.#SNAP_THRESHOLD) {
|
||||
xPos = closestSnap;
|
||||
}
|
||||
|
||||
return xPos;
|
||||
};
|
||||
|
||||
document.addEventListener('pointermove', move, { passive: true });
|
||||
document.addEventListener('pointerup', stop);
|
||||
};
|
||||
|
||||
#disconnect() {
|
||||
this.#resizeObserver?.unobserve(this);
|
||||
this.dividerTouchAreaElement.removeEventListener('pointerdown', this.#onDragStart);
|
||||
this.dividerTouchAreaElement.removeEventListener('touchstart', this.#onDragStart);
|
||||
this.dividerElement.style.display = 'none';
|
||||
this.mainElement.style.display = 'flex';
|
||||
this.#hasInitialized = false;
|
||||
}
|
||||
|
||||
async #connect() {
|
||||
this.#hasInitialized = true;
|
||||
|
||||
this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));
|
||||
this.mainElement.style.display = 'grid';
|
||||
this.mainElement.style.gridTemplateColumns = `${this.position} 0px 1fr`;
|
||||
this.dividerElement.style.display = 'unset';
|
||||
|
||||
this.dividerTouchAreaElement.addEventListener('pointerdown', this.#onDragStart);
|
||||
this.dividerTouchAreaElement.addEventListener('touchstart', this.#onDragStart, { passive: false });
|
||||
|
||||
// Wait for the next frame to get the correct position of the divider.
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
const { left: dividerLeft } = this.shadowRoot!.querySelector('#divider')!.getBoundingClientRect();
|
||||
const { left: mainLeft, width: mainWidth } = this.mainElement.getBoundingClientRect();
|
||||
const percentagePos = ((dividerLeft - mainLeft) / mainWidth) * 100;
|
||||
this.position = `${percentagePos}%`;
|
||||
|
||||
this.#resizeObserver?.observe(this);
|
||||
}
|
||||
|
||||
#onSlotChanged(event: Event) {
|
||||
const slot = event.target as HTMLSlotElement;
|
||||
const name = slot.name;
|
||||
|
||||
if (name === 'start') {
|
||||
this._hasStartPanel = slot.assignedElements().length > 0;
|
||||
}
|
||||
if (name === 'end') {
|
||||
this._hasEndPanel = slot.assignedElements().length > 0;
|
||||
}
|
||||
|
||||
if (!this.#hasBothPanels) {
|
||||
if (this.#hasInitialized) {
|
||||
this.#disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.#connect();
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('render', this._hasStartPanel, this._hasEndPanel);
|
||||
return html`
|
||||
<div id="main">
|
||||
<slot
|
||||
name="start"
|
||||
@slotchange=${this.#onSlotChanged}
|
||||
style="width: ${this._hasStartPanel ? '100%' : '0'}"></slot>
|
||||
<div id="divider">
|
||||
<div id="divider-touch-area" tabindex="0"></div>
|
||||
</div>
|
||||
<slot name="end" @slotchange=${this.#onSlotChanged} style="width: ${this._hasEndPanel ? '100%' : '0'}"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
static styles = css`
|
||||
:host {
|
||||
display: contents;
|
||||
--umb-split-panel-initial-position: 50%;
|
||||
--umb-split-panel-start-min-width: 0;
|
||||
--umb-split-panel-end-min-width: 0;
|
||||
|
||||
--umb-split-panel-divider-touch-area-width: 20px;
|
||||
--umb-split-panel-divider-width: 1px;
|
||||
--umb-split-panel-divider-color: transparent;
|
||||
}
|
||||
slot {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#divider {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 999999;
|
||||
display: none;
|
||||
}
|
||||
#divider-touch-area {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: var(--umb-split-panel-divider-touch-area-width);
|
||||
transform: translateX(-50%);
|
||||
cursor: col-resize;
|
||||
}
|
||||
/* Do we want a line that shows the divider? */
|
||||
#divider::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: var(--umb-split-panel-divider-width);
|
||||
height: 100%;
|
||||
transform: round(translateX(-50%));
|
||||
background-color: var(--umb-split-panel-divider-color);
|
||||
z-index: -1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-split-panel': UmbSplitPanelElement;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/web-components';
|
||||
import './split-panel.element.js';
|
||||
import type { UmbSplitPanelElement } from './split-panel.element.js';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
const meta: Meta<UmbSplitPanelElement> = {
|
||||
title: 'Components/Split Panel',
|
||||
component: 'umb-split-panel',
|
||||
argTypes: {
|
||||
lock: { options: ['none', 'start', 'end'] },
|
||||
snap: { control: 'text' },
|
||||
position: { control: 'text' },
|
||||
},
|
||||
args: {
|
||||
lock: 'start',
|
||||
snap: '',
|
||||
position: '50%',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<UmbSplitPanelElement>;
|
||||
|
||||
export const Overview: Story = {
|
||||
render: (props) => html`
|
||||
<umb-split-panel .lock=${props.lock} .snap=${props.snap} .position=${props.position}>
|
||||
<div id="start" slot="start">Start</div>
|
||||
<div id="end" slot="end">End</div>
|
||||
</umb-split-panel>
|
||||
<style>
|
||||
#start,
|
||||
#end {
|
||||
background-color: #ffffff;
|
||||
color: #383838;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
#start {
|
||||
border-right: 2px solid #e5e5e5;
|
||||
min-height: 300px;
|
||||
}
|
||||
#end {
|
||||
border-left: 2px solid #e5e5e5;
|
||||
}
|
||||
</style>
|
||||
`,
|
||||
};
|
||||
@@ -76,26 +76,24 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-split-panel lock="start" snap="300px">
|
||||
${this._sidebarApps && this._sidebarApps.length > 0
|
||||
? html`
|
||||
<!-- TODO: these extensions should be combined into one type: sectionSidebarApp with a "subtype" -->
|
||||
<umb-section-sidebar slot="start">
|
||||
${repeat(
|
||||
this._sidebarApps,
|
||||
(app) => app.alias,
|
||||
(app) => app.component,
|
||||
)}
|
||||
</umb-section-sidebar>
|
||||
`
|
||||
${this._sidebarApps && this._sidebarApps.length > 0
|
||||
? html`
|
||||
<!-- TODO: these extensions should be combined into one type: sectionSidebarApp with a "subtype" -->
|
||||
<umb-section-sidebar>
|
||||
${repeat(
|
||||
this._sidebarApps,
|
||||
(app) => app.alias,
|
||||
(app) => app.component,
|
||||
)}
|
||||
</umb-section-sidebar>
|
||||
`
|
||||
: nothing}
|
||||
<umb-section-main>
|
||||
${this._routes && this._routes.length > 0
|
||||
? html`<umb-router-slot id="router-slot" .routes="${this._routes}"></umb-router-slot>`
|
||||
: nothing}
|
||||
<umb-section-main slot="end">
|
||||
${this._routes && this._routes.length > 0
|
||||
? html`<umb-router-slot id="router-slot" .routes="${this._routes}"></umb-router-slot>`
|
||||
: nothing}
|
||||
<slot></slot>
|
||||
</umb-section-main>
|
||||
</umb-split-panel>
|
||||
<slot></slot>
|
||||
</umb-section-main>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -111,19 +109,6 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
|
||||
h3 {
|
||||
padding: var(--uui-size-4) var(--uui-size-8);
|
||||
}
|
||||
|
||||
umb-split-panel {
|
||||
--umb-split-panel-initial-position: 200px;
|
||||
--umb-split-panel-start-min-width: 200px;
|
||||
--umb-split-panel-start-max-width: 400px;
|
||||
--umb-split-panel-end-min-width: 600px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 800px) {
|
||||
umb-split-panel {
|
||||
--umb-split-panel-initial-position: 300px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -34,26 +34,23 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._variants) return nothing;
|
||||
return this._variants
|
||||
? html`<div id="splitViews">
|
||||
${repeat(
|
||||
this._variants,
|
||||
(view) =>
|
||||
view.index + '_' + (view.culture ?? '') + '_' + (view.segment ?? '') + '_' + this._variants!.length,
|
||||
(view) => html`
|
||||
<umb-workspace-split-view
|
||||
alias="Umb.Workspace.Document"
|
||||
.splitViewIndex=${view.index}
|
||||
.displayNavigation=${view.index === this._variants!.length - 1}></umb-workspace-split-view>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
return html`<div id="splitViews">
|
||||
<umb-split-panel snap="50%">
|
||||
${repeat(
|
||||
this._variants,
|
||||
(view) =>
|
||||
view.index + '_' + (view.culture ?? '') + '_' + (view.segment ?? '') + '_' + this._variants!.length,
|
||||
(view, index) => html`
|
||||
<umb-workspace-split-view
|
||||
slot=${['start', 'end'][index] || ''}
|
||||
alias="Umb.Workspace.Document"
|
||||
.splitViewIndex=${view.index}
|
||||
.displayNavigation=${view.index === this._variants!.length - 1}></umb-workspace-split-view>
|
||||
`,
|
||||
)}
|
||||
</umb-split-panel>
|
||||
</div>
|
||||
|
||||
<umb-workspace-footer alias="Umb.Workspace.Document"></umb-workspace-footer>`;
|
||||
<umb-workspace-footer alias="Umb.Workspace.Document"></umb-workspace-footer>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -69,16 +66,11 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#splitViews {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - var(--umb-footer-layout-height));
|
||||
}
|
||||
|
||||
umb-split-panel {
|
||||
--umb-split-panel-start-min-width: 25%;
|
||||
--umb-split-panel-end-min-width: 25%;
|
||||
--umb-split-panel-divider-color: var(--uui-color-border);
|
||||
}
|
||||
|
||||
#breadcrumbs {
|
||||
margin: 0 var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#toggle {
|
||||
width: 100%;
|
||||
width: var(--umb-section-sidebar-width);
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
Reference in New Issue
Block a user