UNPKG

igniteui-webcomponents

Version:

Ignite UI for Web Components is a complete library of UI components, giving you the ability to build modern web applications using encapsulation and the concept of reusable components in a dependency-free approach.

608 lines 23.8 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { html, LitElement } from 'lit'; import { eventOptions, property, query, state } from 'lit/decorators.js'; import { createRef, ref } from 'lit/directives/ref.js'; import { styleMap } from 'lit/directives/style-map.js'; import { addThemingController } from '../../theming/theming-controller.js'; import { addInternalsController } from '../common/controllers/internals.js'; import { addKeybindings, arrowDown, arrowLeft, arrowRight, arrowUp, ctrlKey, endKey, homeKey, } from '../common/controllers/key-bindings.js'; import { createResizeObserverController } from '../common/controllers/resize-observer.js'; import { addSlotController, setSlots } from '../common/controllers/slot.js'; import { registerComponent } from '../common/definitions/register.js'; import { EventEmitterMixin } from '../common/mixins/event-emitter.js'; import { partMap } from '../common/part-map.js'; import { asNumber, asPercent, bindIf, clamp, isLTR, roundPrecise, } from '../common/util.js'; import IgcVisuallyHiddenComponent from '../visually-hidden/visually-hidden.js'; import { styles as shared } from './themes/shared/splitter.common.css.js'; import { styles } from './themes/splitter.base.css.js'; import { all } from './themes/themes.js'; const KEYBOARD_RESIZE_STEP = 10; const DEFAULT_RESIZE_STATE = { startPane: null, endPane: null, isDragging: false, dragStartPosition: { x: 0, y: 0 }, dragPointerId: -1, }; export default class IgcSplitterComponent extends EventEmitterMixin(LitElement) { static { this.tagName = 'igc-splitter'; } static { this.styles = [styles, shared]; } static register() { registerComponent(IgcSplitterComponent, IgcVisuallyHiddenComponent); } get _separator() { return this._separatorRef.value; } get _resizeDisallowed() { return this.disableResize || this._collapsedPane != null; } get _isHorizontal() { return this.orientation === 'horizontal'; } get _separatorCursor() { if (this._resizeDisallowed) { return 'default'; } return this._isHorizontal ? 'col-resize' : 'row-resize'; } set startMinSize(value) { this._startPaneState.minSize = this._normalizeValue(value); } get startMinSize() { return this._startPaneState.minSize; } set endMinSize(value) { this._endPaneState.minSize = this._normalizeValue(value); } get endMinSize() { return this._endPaneState.minSize; } set startMaxSize(value) { this._startPaneState.maxSize = this._normalizeValue(value); } get startMaxSize() { return this._startPaneState.maxSize; } set endMaxSize(value) { this._endPaneState.maxSize = this._normalizeValue(value); } get endMaxSize() { return this._endPaneState.maxSize; } set startSize(value) { this._startPaneState.size = this._normalizeValue(value, 'auto'); } get startSize() { return this._startPaneState.size; } set endSize(value) { this._endPaneState.size = this._normalizeValue(value, 'auto'); } get endSize() { return this._endPaneState.size; } constructor() { super(); this._internals = addInternalsController(this); this._separatorRef = createRef(); this._startPaneState = { size: 'auto', styles: {} }; this._endPaneState = { size: 'auto', styles: {} }; this._collapsedPane = null; this._resizeState = { ...DEFAULT_RESIZE_STATE }; this.orientation = 'horizontal'; this.disableCollapse = false; this.disableResize = false; this.hideCollapseButtons = false; this.hideDragHandle = false; addThemingController(this, all); addSlotController(this, { slots: setSlots('start', 'end') }); createResizeObserverController(this, { callback: () => this.requestUpdate(), }); addKeybindings(this, { ref: this._separatorRef, }) .set(arrowUp, () => this._handleResizePanes(-1, 'vertical')) .set(arrowDown, () => this._handleResizePanes(1, 'vertical')) .set(arrowLeft, () => this._handleResizePanes(-1, 'horizontal')) .set(arrowRight, () => this._handleResizePanes(1, 'horizontal')) .set(homeKey, () => this._handleMinMaxResize('min')) .set(endKey, () => this._handleMinMaxResize('max')) .set([ctrlKey, arrowUp], () => this._handleArrowsExpandCollapse('start', 'vertical')) .set([ctrlKey, arrowDown], () => this._handleArrowsExpandCollapse('end', 'vertical')) .set([ctrlKey, arrowLeft], () => this._handleArrowsExpandCollapse('start', 'horizontal')) .set([ctrlKey, arrowRight], () => this._handleArrowsExpandCollapse('end', 'horizontal')); } update(changed) { if (changed.get('orientation') != null) { for (const pane of ['start', 'end']) { const state = this._getPaneState(pane); state.size = 'auto'; state.minSize = undefined; state.maxSize = undefined; } } if (this.hasUpdated) { this._updatePanes(); } super.update(changed); } updated() { this._updateBarAria(); } _handleBarPointerDown(e) { if (e.button !== 0) { return; } e.preventDefault(); this._resizeState = { ...this._resizeState, isDragging: true, dragPointerId: e.pointerId, dragStartPosition: { x: e.clientX, y: e.clientY }, }; this._resizeStart(); this._separator?.setPointerCapture(this._resizeState.dragPointerId); } _getDragDelta(e) { const deltaX = e.clientX - this._resizeState.dragStartPosition.x; const deltaY = e.clientY - this._resizeState.dragStartPosition.y; return this._resolveDelta(deltaX, deltaY); } _handleBarPointerMove(e) { if (e.pointerId !== this._resizeState.dragPointerId) { return; } const delta = this._getDragDelta(e); if (delta !== 0) { this._resizing(delta); } } _handleEndDrag(e) { if (e.pointerId !== this._resizeState.dragPointerId) { return; } const delta = this._getDragDelta(e); if (delta !== 0) { this._resizeEnd(delta); } this._endDrag(); } _endDrag() { if (this._resizeState.dragPointerId !== -1) { this._separator?.releasePointerCapture(this._resizeState.dragPointerId); } this._resizeState = { ...DEFAULT_RESIZE_STATE }; } toggle(position) { if (this._collapsedPane === null) { this._savePaneSizes(); } this._collapsedPane = this._collapsedPane === position ? null : position; this._internals.setState('start-collapsed', this._isCollapsed('start')); this._internals.setState('end-collapsed', this._isCollapsed('end')); this._restoreSizesOnExpandCollapse(); } _savePaneSizes() { this._startPaneState.savedSize = `${this._paneRectAsPercent(0)}%`; this._endPaneState.savedSize = `${this._paneRectAsPercent(1)}%`; } _restoreSizesOnExpandCollapse() { if (this._collapsedPane !== null) { this._startPaneState.size = 'auto'; this._endPaneState.size = 'auto'; } else { this._startPaneState.size = this._startPaneState.savedSize ?? this.startSize; this._endPaneState.size = this._endPaneState.savedSize ?? this.endSize; } } _paneRectAsPercent(paneIndex) { const totalSize = this._getTotalSize(); if (totalSize === 0) { return 0; } return roundPrecise(asPercent(this._rectSize()[paneIndex], totalSize), 0); } _sizeToPercent(sizeValue) { const totalSize = this._getTotalSize(); if (totalSize === 0) { return 0; } if (sizeValue.includes('%')) { return asNumber(sizeValue); } const pxValue = asNumber(sizeValue); return roundPrecise(asPercent(pxValue, totalSize), 0); } _getStartPaneSizePercent() { if (!this._startPane || this._isCollapsed('start')) { return 0; } if (this._isCollapsed('end')) { return 100; } return this._paneRectAsPercent(0); } _getMinMaxAsPercent(type) { const value = type === 'min' ? this.startMinSize : this.startMaxSize; const defaultValue = type === 'min' ? 0 : 100; return value ? this._sizeToPercent(value) : defaultValue; } _isCollapsed(which) { return this._collapsedPane === which; } _updateBarAria() { if (this._separator) { this._separator.ariaValueNow = this._getStartPaneSizePercent().toString(); this._separator.ariaValueMin = this._getMinMaxAsPercent('min').toString(); this._separator.ariaValueMax = this._getMinMaxAsPercent('max').toString(); } } _getPaneState(which) { return which === 'start' ? this._startPaneState : this._endPaneState; } _isPercentageSize(which) { const { size } = this._getPaneState(which); return !!size && size.includes('%'); } _isAutoSize(which) { return this._getPaneState(which).size === 'auto'; } _normalizeValue(value, fallback) { const trimmed = value?.trim(); if (!trimmed || trimmed === 'auto') { return fallback; } const numericValue = asNumber(trimmed, -1); if (numericValue < 0) return fallback; if (trimmed.includes('%') && numericValue > 100) return fallback; return trimmed; } _getFlex(which) { const isAuto = this._isAutoSize(which); const size = isAuto ? '0px' : this._getPaneState(which).size; return `${isAuto ? 1 : 0} 1 ${size}`; } _handleResizePanes(direction, validOrientation) { if (this._resizeDisallowed || this.orientation !== validOrientation) { return; } const delta = this._resolveDelta(KEYBOARD_RESIZE_STEP, KEYBOARD_RESIZE_STEP, direction); this._resizeStart(); this._resizing(delta); this._resizeEnd(delta); } _preventDefaultForEvent(e) { e.preventDefault(); } _resolveDelta(deltaX, deltaY, direction) { const isHorizontal = this._isHorizontal; const rtlMultiplier = isHorizontal && !isLTR(this) ? -1 : 1; const delta = isHorizontal ? deltaX : deltaY; return delta * rtlMultiplier * (direction ?? 1); } _handleMinMaxResize(type) { if (this._resizeDisallowed) { return; } const totalSize = this._getTotalSize(); const boundaryValue = type === 'min' ? this.startMinSize : this.startMaxSize; const isPercentage = boundaryValue ? boundaryValue.includes('%') : type === 'max'; const targetStartSizePx = this._setMinMaxInPx('start', type) ?? (type === 'min' ? 0 : totalSize); const targetEndSizePx = totalSize - targetStartSizePx; if (isPercentage) { this.startSize = `${roundPrecise(asPercent(targetStartSizePx, totalSize), 2)}%`; this.endSize = `${roundPrecise(asPercent(targetEndSizePx, totalSize), 2)}%`; } else { this.startSize = `${targetStartSizePx}px`; this.endSize = `${targetEndSizePx}px`; } } _handleExpanderAction(pane) { const other = pane === 'start' ? 'end' : 'start'; this.toggle(this._collapsedPane === other ? other : pane); } _handleArrowsExpandCollapse(target, validOrientation) { if (this.disableCollapse || this.orientation !== validOrientation) { return; } const effectiveTarget = validOrientation === 'horizontal' && !isLTR(this) ? target === 'start' ? 'end' : 'start' : target; this._handleExpanderAction(effectiveTarget); } _resizeStart() { const [startSize, endSize] = this._rectSize(); const totalSize = this._getTotalSize(); this._resizeState.startPane = this._createPaneState('start', startSize, totalSize); this._resizeState.endPane = this._createPaneState('end', endSize, totalSize); this.emitEvent('igcResizeStart', { detail: { startPanelSize: startSize, endPanelSize: endSize }, }); } _createPaneState(pane, size, totalSize) { return { initialSize: size, isPercentageBased: this._isPercentageSize(pane) || this._isAutoSize(pane), minSizePx: this._setMinMaxInPx(pane, 'min', totalSize), maxSizePx: this._setMinMaxInPx(pane, 'max', totalSize), }; } _setMinMaxInPx(pane, type, totalSize) { const paneState = this._getPaneState(pane); const value = type === 'max' ? paneState.maxSize : paneState.minSize; const valueAsNumber = asNumber(value); if (!value) { return undefined; } return value.includes('%') ? (valueAsNumber / 100) * (totalSize ?? this._getTotalSize()) : valueAsNumber; } _resizing(delta) { const [startPaneSize, endPaneSize] = this._calcNewSizes(delta); this.startSize = `${startPaneSize}px`; this.endSize = `${endPaneSize}px`; this.emitEvent('igcResizing', { detail: { startPanelSize: startPaneSize, endPanelSize: endPaneSize, delta, }, }); } _computeSize(pane, paneSize, totalSize) { return pane.isPercentageBased ? `${asPercent(paneSize, totalSize)}%` : `${roundPrecise(paneSize, 0)}px`; } _resizeEnd(delta) { if (!this._resizeState.startPane || !this._resizeState.endPane) { return; } const [startPaneSize, endPaneSize] = this._calcNewSizes(delta); const totalSize = this._getTotalSize(); this.startSize = this._computeSize(this._resizeState.startPane, startPaneSize, totalSize); this.endSize = this._computeSize(this._resizeState.endPane, endPaneSize, totalSize); this.emitEvent('igcResizeEnd', { detail: { startPanelSize: startPaneSize, endPanelSize: endPaneSize, delta, }, }); } _rectSize() { const axis = this._isHorizontal ? 'width' : 'height'; const startPaneRect = this._startPane.getBoundingClientRect(); const endPaneRect = this._endPane.getBoundingClientRect(); return [startPaneRect[axis], endPaneRect[axis]]; } _calcNewSizes(delta) { if (!this._resizeState.startPane || !this._resizeState.endPane) return [0, 0]; const start = this._resizeState.startPane; const end = this._resizeState.endPane; const minStart = start.minSizePx || 0; const minEnd = end.minSizePx || 0; const maxStart = start.maxSizePx || start.initialSize + end.initialSize - minEnd; const maxEnd = end.maxSizePx || start.initialSize + end.initialSize - minStart; const maxPosDelta = Math.min(maxStart - start.initialSize, end.initialSize - minEnd); const maxNegDelta = Math.min(start.initialSize - minStart, maxEnd - end.initialSize); const finalDelta = clamp(delta, -maxNegDelta, maxPosDelta); return [start.initialSize + finalDelta, end.initialSize - finalDelta]; } _getTotalSize() { if (!this._base) { return 0; } const axis = this._isHorizontal ? 'width' : 'height'; const barSize = this._separator ? roundPrecise(this._separator.getBoundingClientRect()[axis]) : 0; const rect = this._base.getBoundingClientRect(); const size = rect[axis]; return size - barSize; } _updatePanes() { const totalSize = this._getTotalSize(); const isCollapsed = this._collapsedPane !== null; for (const pane of ['start', 'end']) { const state = this._getPaneState(pane); if (isCollapsed) { state.size = 'auto'; state.minSize = undefined; state.maxSize = undefined; } this._setPaneMinMaxSizes(pane, isCollapsed ? '0' : state.minSize, isCollapsed ? '100%' : state.maxSize, totalSize); this._updatePaneStyles(pane, { flex: this._getFlex(pane) }); } } _updatePaneStyles(pane, styles) { Object.assign(this._getPaneState(pane).styles, styles); } _setPaneMinMaxSizes(pane, minSize, maxSize, totalSize) { const min = this._ensureMinConstraintIsWithinBounds(pane, minSize, totalSize) ?? 0; const max = maxSize ?? '100%'; this._updatePaneStyles(pane, this._isHorizontal ? { minWidth: min, maxWidth: max, minHeight: 0, maxHeight: '100%' } : { minWidth: 0, maxWidth: '100%', minHeight: min, maxHeight: max }); } _ensureMinConstraintIsWithinBounds(pane, minSize, totalSize) { const total = totalSize ?? this._getTotalSize(); if (minSize && total > 0) { const minPx = this._setMinMaxInPx(pane, 'min', total) ?? 0; const other = pane === 'start' ? 'end' : 'start'; const otherMinPx = this._getPaneState(other).minSize ? (this._setMinMaxInPx(other, 'min', total) ?? 0) : 0; if (minPx > total || minPx + otherMinPx > total) { return undefined; } } return minSize; } _handleExpanderClick(pane, event) { event.stopPropagation(); this._handleExpanderAction(pane); } _resolvePartNames(expander) { const other = expander === 'start' ? 'end' : 'start'; const otherIsCollapsed = this._isCollapsed(other); return { [`${other}-expand-btn`]: otherIsCollapsed, [`${expander}-collapse-btn`]: !otherIsCollapsed, }; } _renderBarControls() { const dragHandleHidden = this.hideDragHandle || this.disableResize; const hidden = this.disableCollapse || this.hideCollapseButtons; const prevButtonHidden = hidden || this._isCollapsed('start'); const nextButtonHidden = hidden || this._isCollapsed('end'); return html ` <div part="${partMap(this._resolvePartNames('start'))}" ?hidden=${prevButtonHidden} @pointerdown=${(e) => this._handleExpanderClick('start', e)} ></div> <div part="drag-handle" ?hidden=${dragHandleHidden}></div> <div part="${partMap(this._resolvePartNames('end'))}" ?hidden=${nextButtonHidden} @pointerdown=${(e) => this._handleExpanderClick('end', e)} ></div> `; } _renderAccessibleLabel() { return html ` <igc-visually-hidden id="splitter-label"> ${this._isCollapsed('start') ? 'Start pane collapsed' : 'Start pane expanded'} and ${this._isCollapsed('end') ? 'End pane collapsed' : 'End pane expanded'} </igc-visually-hidden> `; } _renderSeparator() { const isDragging = this._resizeState.isDragging; const canResize = !this._resizeDisallowed; return html ` <div ${ref(this._separatorRef)} part="splitter-bar" role="separator" tabindex=${this.disableCollapse && this.disableResize ? -1 : 0} aria-controls="start-pane end-pane" aria-labelledby="splitter-label" aria-orientation=${this.orientation} style=${styleMap({ '--cursor': this._separatorCursor })} @touchstart=${bindIf(canResize, this._preventDefaultForEvent)} @contextmenu=${bindIf(canResize, this._preventDefaultForEvent)} @pointerdown=${bindIf(canResize, this._handleBarPointerDown)} @pointermove=${bindIf(isDragging, this._handleBarPointerMove)} @pointerup=${bindIf(isDragging, this._handleEndDrag)} @lostpointercapture=${bindIf(isDragging, this._handleEndDrag)} @pointercancel=${bindIf(isDragging, this._endDrag)} > ${this._renderBarControls()} </div> `; } render() { return html ` ${this._renderAccessibleLabel()} <div part="base"> <div part="start-pane" id="start-pane" style=${styleMap(this._startPaneState.styles)} > <slot name="start"></slot> </div> ${this._renderSeparator()} <div part="end-pane" id="end-pane" style=${styleMap(this._endPaneState.styles)} > <slot name="end"></slot> </div> </div> `; } } __decorate([ state() ], IgcSplitterComponent.prototype, "_collapsedPane", void 0); __decorate([ state() ], IgcSplitterComponent.prototype, "_resizeState", void 0); __decorate([ query('[part~="base"]', true) ], IgcSplitterComponent.prototype, "_base", void 0); __decorate([ query('[part~="start-pane"]', true) ], IgcSplitterComponent.prototype, "_startPane", void 0); __decorate([ query('[part~="end-pane"]', true) ], IgcSplitterComponent.prototype, "_endPane", void 0); __decorate([ property({ reflect: true }) ], IgcSplitterComponent.prototype, "orientation", void 0); __decorate([ property({ type: Boolean, reflect: true, attribute: 'disable-collapse' }) ], IgcSplitterComponent.prototype, "disableCollapse", void 0); __decorate([ property({ type: Boolean, reflect: true, attribute: 'disable-resize' }) ], IgcSplitterComponent.prototype, "disableResize", void 0); __decorate([ property({ type: Boolean, reflect: true, attribute: 'hide-collapse-buttons', }) ], IgcSplitterComponent.prototype, "hideCollapseButtons", void 0); __decorate([ property({ type: Boolean, reflect: true, attribute: 'hide-drag-handle', }) ], IgcSplitterComponent.prototype, "hideDragHandle", void 0); __decorate([ property({ attribute: 'start-min-size' }) ], IgcSplitterComponent.prototype, "startMinSize", null); __decorate([ property({ attribute: 'end-min-size' }) ], IgcSplitterComponent.prototype, "endMinSize", null); __decorate([ property({ attribute: 'start-max-size' }) ], IgcSplitterComponent.prototype, "startMaxSize", null); __decorate([ property({ attribute: 'end-max-size' }) ], IgcSplitterComponent.prototype, "endMaxSize", null); __decorate([ property({ attribute: 'start-size' }) ], IgcSplitterComponent.prototype, "startSize", null); __decorate([ property({ attribute: 'end-size' }) ], IgcSplitterComponent.prototype, "endSize", null); __decorate([ eventOptions({ passive: false }) ], IgcSplitterComponent.prototype, "_preventDefaultForEvent", null); //# sourceMappingURL=splitter.js.map