UNPKG

@trimble-oss/moduswebcomponents

Version:

Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust

364 lines (359 loc) 17.3 kB
import { r as registerInstance, h, e as Host, g as getElement } from './index-C1-Dpkp7.js'; import { h as handleShadowDOMStyles } from './base-component-tWS3Egrk.js'; import { i as inheritAriaAttributes } from './utils-DN2BGzSQ.js'; const convertPropsToClasses = ({ orientation, size, type, density, }) => { let classes = ''; if (orientation) { switch (orientation) { case 'horizontal': classes = `${classes} modus-wc-handle-vertical`; break; case 'vertical': classes = `${classes} modus-wc-handle-horizontal`; break; } } if (size) { switch (size) { case 'default': classes = `${classes} modus-wc-handle-default`; break; case 'lg': classes = `${classes} modus-wc-handle-lg`; break; case 'xl': classes = `${classes} modus-wc-handle-xl`; break; case '2xl': classes = `${classes} modus-wc-handle-2xl`; break; } } if (type) { switch (type) { case 'bar': classes = `${classes} modus-wc-handle-type-bar`; break; case 'button': classes = `${classes} modus-wc-handle-type-button`; break; } } if (density) { switch (density) { case 'compact': classes = `${classes} modus-wc-handle-compact`; break; case 'comfortable': classes = `${classes} modus-wc-handle-comfortable`; break; case 'relaxed': classes = `${classes} modus-wc-handle-relaxed`; break; } } return classes.trim(); }; const modusWcHandleCss = ".modus-wc-handle-container{align-items:center;background-color:transparent;display:flex;justify-content:center;outline:none;padding:0;position:relative}.modus-wc-handle-container:focus{outline:var(--modus-wc-border-width-sm) solid var(--modus-wc-color-primary);outline-offset:var(--modus-wc-border-width-sm)}.modus-wc-handle-container:hover{cursor:row-resize}.modus-wc-handle-container:hover.modus-wc-handle-vertical{cursor:col-resize}.modus-wc-handle-horizontal{flex-direction:column}.modus-wc-handle-horizontal.modus-wc-handle-compact{height:var(--modus-wc-spacing-sm)}.modus-wc-handle-horizontal.modus-wc-handle-comfortable{height:var(--modus-wc-spacing-md)}.modus-wc-handle-horizontal.modus-wc-handle-relaxed{height:var(--modus-wc-spacing-lg)}.modus-wc-handle-horizontal.modus-wc-handle-type-button{height:var(--modus-wc-spacing-xs)}.modus-wc-handle-vertical{flex-direction:row;height:100%}.modus-wc-handle-vertical.modus-wc-handle-compact{width:var(--modus-wc-spacing-sm)}.modus-wc-handle-vertical.modus-wc-handle-comfortable{width:var(--modus-wc-spacing-md)}.modus-wc-handle-vertical.modus-wc-handle-relaxed{width:var(--modus-wc-spacing-lg)}.modus-wc-handle-vertical.modus-wc-handle-type-button{width:var(--modus-wc-spacing-xs)}.modus-wc-handle-bar{background-color:var(--modus-wc-color-base-200);border-radius:var(--modus-wc-border-radius-md);left:50%;position:absolute;top:50%;transform:translate(-50%, -50%)}.modus-wc-handle-horizontal .modus-wc-handle-bar{height:var(--modus-wc-spacing-xs)}.modus-wc-handle-vertical .modus-wc-handle-bar{width:var(--modus-wc-spacing-xs)}.modus-wc-handle-horizontal.modus-wc-handle-default .modus-wc-handle-bar{width:44px}.modus-wc-handle-horizontal.modus-wc-handle-lg .modus-wc-handle-bar{width:80px}.modus-wc-handle-horizontal.modus-wc-handle-xl .modus-wc-handle-bar{width:144px}.modus-wc-handle-horizontal.modus-wc-handle-2xl .modus-wc-handle-bar{width:224px}.modus-wc-handle-vertical.modus-wc-handle-default .modus-wc-handle-bar{height:44px}.modus-wc-handle-vertical.modus-wc-handle-lg .modus-wc-handle-bar{height:80px}.modus-wc-handle-vertical.modus-wc-handle-xl .modus-wc-handle-bar{height:144px}.modus-wc-handle-vertical.modus-wc-handle-2xl .modus-wc-handle-bar{height:224px}.modus-wc-handle-type-button .modus-wc-handle-button-wrapper{left:50%;position:absolute;top:50%;transform:translate(-50%, -50%)}"; const ModusWcHandle = class { constructor(hostRef) { registerInstance(this, hostRef); this.inheritedAttributes = {}; this.startPos = 0; this.startLeftSize = 0; this.startRightSize = 0; this.previousBodyCursor = null; this.previousBodyUserSelect = null; this.nonPassiveListenerOptions = { passive: false, }; /** Custom CSS class to apply to the handle element. */ this.customClass = ''; /** The orientation of the handle. */ this.orientation = 'horizontal'; /** The size of the handle. */ this.size = 'default'; /** The density/spacing of the handle container (compact: 8px, comfortable: 12px, relaxed: 16px). */ this.density = 'comfortable'; /** The initial split percentage for the left/top panel (1-100). The right/bottom panel gets the remaining percentage. */ this.defaultSplit = 50; /** The size of the button. */ this.buttonSize = 'md'; /** The color of the button. */ this.buttonColor = 'tertiary'; /** The variant of the button. */ this.buttonVariant = 'filled'; /** The type of handle to display. */ this.type = 'bar'; /** Internal state for dragging */ this.isDragging = false; this.handleMouseDown = (e) => { if (this.type === 'bar') { e.preventDefault(); } this.startDrag(e.clientX, e.clientY); }; this.handleTouchStart = (e) => { if (this.type === 'bar') { e.preventDefault(); } const touch = e.touches[0]; this.startDrag(touch.clientX, touch.clientY); }; this.handleMouseMove = (e) => { if (!this.isDragging) return; e.preventDefault(); this.drag(e.clientX, e.clientY); }; this.handleTouchMove = (e) => { if (!this.isDragging) return; e.preventDefault(); const touch = e.touches[0]; this.drag(touch.clientX, touch.clientY); }; this.handleMouseUp = () => { this.endDrag(); }; this.handleTouchEnd = () => { this.endDrag(); }; this.handleKeyDown = (e) => { // Use larger increment when Shift key is held const moveAmount = e.shiftKey ? 15 : 5; // pixels to move per key press let delta = 0; // Vertical orientation: up/down arrows if (this.orientation === 'vertical') { if (e.key === 'ArrowUp') { delta = -moveAmount; e.preventDefault(); } else if (e.key === 'ArrowDown') { delta = moveAmount; e.preventDefault(); } else { return; // Ignore other keys } } // Horizontal orientation: left/right arrows else if (this.orientation === 'horizontal') { if (e.key === 'ArrowLeft') { delta = -moveAmount; e.preventDefault(); } else if (e.key === 'ArrowRight') { delta = moveAmount; e.preventDefault(); } else { return; // Ignore other keys } } // Apply the resize const leftEl = this.getTargetElement(this.leftTarget); const rightEl = this.getTargetElement(this.rightTarget); const leftSize = leftEl ? this.orientation === 'horizontal' ? leftEl.offsetWidth : leftEl.offsetHeight : 0; const rightSize = rightEl ? this.orientation === 'horizontal' ? rightEl.offsetWidth : rightEl.offsetHeight : 0; const clampedDelta = this.clampDelta(delta, leftEl, rightEl, leftSize, rightSize); if (leftEl && this.orientation === 'vertical') { const newHeight = leftSize + clampedDelta; leftEl.style.height = `${newHeight}px`; } else if (leftEl && this.orientation === 'horizontal') { const newWidth = leftSize + clampedDelta; leftEl.style.width = `${newWidth}px`; } if (rightEl && this.orientation === 'vertical') { const newHeight = rightSize - clampedDelta; rightEl.style.height = `${newHeight}px`; } else if (rightEl && this.orientation === 'horizontal') { const newWidth = rightSize - clampedDelta; rightEl.style.width = `${newWidth}px`; } }; } componentWillLoad() { handleShadowDOMStyles(this.el); this.inheritedAttributes = inheritAriaAttributes(this.el); } componentDidLoad() { this.setupDragHandlers(); this.applyInitialSplit(); } applyInitialSplit() { if (!this.defaultSplit || !this.leftTarget || !this.rightTarget) return; const leftEl = this.getTargetElement(this.leftTarget); const rightEl = this.getTargetElement(this.rightTarget); // istanbul ignore next (unreachable code) if (!leftEl || !rightEl) return; // Clamp split value between 1 and 100 const splitValue = Math.max(1, Math.min(100, this.defaultSplit)); const leftPercent = splitValue; const rightPercent = 100 - splitValue; if (this.orientation === 'horizontal') { // Set width percentages for horizontal layout leftEl.style.width = `${leftPercent}%`; rightEl.style.width = `${rightPercent}%`; // Prevent panels from shrinking below their set percentage leftEl.style.flexShrink = '0'; rightEl.style.flexShrink = '0'; } else { // Set height percentages for vertical layout leftEl.style.height = `${leftPercent}%`; rightEl.style.height = `${rightPercent}%`; // Prevent panels from shrinking below their set percentage leftEl.style.flexShrink = '0'; rightEl.style.flexShrink = '0'; } } setupDragHandlers() { // Mouse and touch events this.el.addEventListener('mousedown', this.handleMouseDown); document.addEventListener('mousemove', this.handleMouseMove); document.addEventListener('mouseup', this.handleMouseUp); // Touch events for mobile support this.el.addEventListener('touchstart', this.handleTouchStart, this.nonPassiveListenerOptions); document.addEventListener('touchmove', this.handleTouchMove, this.nonPassiveListenerOptions); document.addEventListener('touchend', this.handleTouchEnd); // Keyboard navigation this.el.addEventListener('keydown', this.handleKeyDown); } getTargetElement(target) { if (!target) return null; if (typeof target === 'string') { return document.querySelector(target); } return target; } startDrag(clientX, clientY) { this.isDragging = true; this.startPos = this.orientation === 'horizontal' ? clientX : clientY; const leftEl = this.getTargetElement(this.leftTarget); const rightEl = this.getTargetElement(this.rightTarget); if (leftEl) { this.startLeftSize = this.orientation === 'horizontal' ? leftEl.offsetWidth : leftEl.offsetHeight; } else { this.startLeftSize = 0; } if (rightEl) { this.startRightSize = this.orientation === 'horizontal' ? rightEl.offsetWidth : rightEl.offsetHeight; } else { this.startRightSize = 0; } this.previousBodyCursor = document.body.style.cursor; this.previousBodyUserSelect = document.body.style.userSelect; document.body.style.cursor = this.orientation === 'horizontal' ? 'col-resize' : 'row-resize'; document.body.style.userSelect = 'none'; } drag(clientX, clientY) { const currentPos = this.orientation === 'horizontal' ? clientX : clientY; const delta = currentPos - this.startPos; const leftEl = this.getTargetElement(this.leftTarget); const rightEl = this.getTargetElement(this.rightTarget); const clampedDelta = this.clampDelta(delta, leftEl, rightEl, this.startLeftSize, this.startRightSize); if (leftEl && this.orientation === 'horizontal') { leftEl.style.width = `${this.startLeftSize + clampedDelta}px`; } else if (leftEl && this.orientation === 'vertical') { leftEl.style.height = `${this.startLeftSize + clampedDelta}px`; } if (rightEl && this.orientation === 'horizontal') { rightEl.style.width = `${this.startRightSize - clampedDelta}px`; } else if (rightEl && this.orientation === 'vertical') { rightEl.style.height = `${this.startRightSize - clampedDelta}px`; } } endDrag() { var _a, _b; this.isDragging = false; document.body.style.cursor = (_a = this.previousBodyCursor) !== null && _a !== void 0 ? _a : ''; document.body.style.userSelect = (_b = this.previousBodyUserSelect) !== null && _b !== void 0 ? _b : ''; this.previousBodyCursor = null; this.previousBodyUserSelect = null; } getMinSize(target, axis) { const computedStyle = getComputedStyle(target); const value = axis === 'horizontal' ? computedStyle.minWidth : computedStyle.minHeight; const parsed = parseFloat(value); // istanbul ignore next (unreachable code) return Number.isNaN(parsed) ? 0 : parsed; } clampDelta(delta, leftEl, rightEl, leftSize, rightSize) { let minDelta = Number.NEGATIVE_INFINITY; let maxDelta = Number.POSITIVE_INFINITY; const axis = this.orientation === 'vertical' ? 'vertical' : 'horizontal'; if (leftEl) { const minLeft = this.getMinSize(leftEl, axis); minDelta = Math.max(minDelta, minLeft - leftSize); } if (rightEl) { const minRight = this.getMinSize(rightEl, axis); maxDelta = Math.min(maxDelta, rightSize - minRight); } return Math.min(Math.max(delta, minDelta), maxDelta); } disconnectedCallback() { // Determine the target element for removing event listeners this.el.removeEventListener('mousedown', this.handleMouseDown); this.el.removeEventListener('touchstart', this.handleTouchStart, this.nonPassiveListenerOptions); this.el.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); document.removeEventListener('touchmove', this.handleTouchMove, this.nonPassiveListenerOptions); document.removeEventListener('touchend', this.handleTouchEnd); } getClasses() { const classList = ['modus-wc-handle-container']; const propClasses = convertPropsToClasses({ orientation: this.orientation, size: this.size, type: this.type, density: this.density, }); if (propClasses) classList.push(propClasses); if (this.customClass) classList.push(this.customClass); return classList.join(' '); } renderBarHandle() { return h("div", { class: "modus-wc-handle-bar" }); } renderButtonHandle() { const iconName = this.orientation === 'vertical' ? 'drag_vertical' : 'drag_horizontal'; return (h("div", { class: "modus-wc-handle-button-wrapper" }, h("modus-wc-button", { size: this.buttonSize, color: this.buttonColor, variant: this.buttonVariant, shape: "circle", "aria-orientation": this.orientation, role: "separator" }, h("modus-wc-icon", { name: iconName, size: "xs" })))); } render() { return (h(Host, Object.assign({ key: '944adbf88619577d1d10058e336a995b6ff52a18', class: this.getClasses(), role: this.type === 'bar' ? 'separator' : undefined, tabIndex: 0, "aria-orientation": this.type === 'bar' ? this.orientation : undefined }, this.inheritedAttributes), this.type === 'bar' ? this.renderBarHandle() : this.renderButtonHandle())); } get el() { return getElement(this); } }; ModusWcHandle.style = modusWcHandleCss; export { ModusWcHandle as modus_wc_handle };