Revert "Merge pull request #1174 from umbraco/feature/split-panel"

This reverts commit 214f857034, reversing
changes made to 0915f33cdd.
This commit is contained in:
leekelleher
2024-02-21 13:29:11 +00:00
parent 590821283a
commit f649c6e873
8 changed files with 45 additions and 433 deletions

View File

@@ -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>

View File

@@ -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';

View File

@@ -1,3 +0,0 @@
import './split-panel.element.js';
export * from './split-panel.element.js';

View File

@@ -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;
}
}

View File

@@ -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>
`,
};

View File

@@ -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;
}
}
`,
];
}

View File

@@ -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);
}

View File

@@ -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;