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