@tylertech/forge 3.10.4 → 3.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +2 -2
- package/dist/lib.js +9 -9
- package/dist/lib.js.map +3 -3
- package/esm/core/mixins/interactions/moveable/with-moveable.js +5 -7
- package/esm/core/utils/utils.d.ts +16 -0
- package/esm/core/utils/utils.js +67 -0
- package/esm/dialog/dialog-adapter.d.ts +6 -0
- package/esm/dialog/dialog-adapter.js +16 -0
- package/esm/dialog/dialog-core.js +4 -0
- package/esm/expansion-panel/expansion-panel-adapter.js +1 -1
- package/esm/expansion-panel/expansion-panel-core.js +3 -7
- package/esm/menu/menu-core.js +2 -2
- package/esm/split-view/split-view-panel/split-view-panel.js +1 -1
- package/esm/toast/toast-core.js +1 -0
- package/package.json +1 -1
|
@@ -50,15 +50,13 @@ export function WithMoveable(base = class {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
// Ensure that the surface position stays within the bounds of the screen
|
|
54
|
-
const newPosition = this._clampPosition(position, this._moveContext);
|
|
55
53
|
// Only update the position if it actually changed
|
|
56
|
-
if (!this._lastPosition ||
|
|
57
|
-
const defaultPrevented = this._onMove(
|
|
54
|
+
if (!this._lastPosition || position.x !== this._lastPosition.x || position.y !== this._lastPosition.y) {
|
|
55
|
+
const defaultPrevented = this._onMove(position);
|
|
58
56
|
if (!defaultPrevented) {
|
|
59
|
-
this._lastPosition = { ...
|
|
60
|
-
const newX = this._normalizePositionValue(
|
|
61
|
-
const newY = this._normalizePositionValue(
|
|
57
|
+
this._lastPosition = { ...position };
|
|
58
|
+
const newX = this._normalizePositionValue(position.x);
|
|
59
|
+
const newY = this._normalizePositionValue(position.y);
|
|
62
60
|
this._updatePosition(newX, newY);
|
|
63
61
|
}
|
|
64
62
|
}
|
|
@@ -168,3 +168,19 @@ export declare function checkVisibility(element: HTMLElement): boolean;
|
|
|
168
168
|
* @param value - Whether to add or remove the state.
|
|
169
169
|
*/
|
|
170
170
|
export declare function toggleState(internals: ElementInternals, state: string, value: boolean): void;
|
|
171
|
+
/**
|
|
172
|
+
* Determines if an element is clipped by the viewport bounds
|
|
173
|
+
* @param element The element to check.
|
|
174
|
+
* @returns `true` if the element is clipped by the viewport, otherwise `false`.
|
|
175
|
+
*/
|
|
176
|
+
export declare function isElementClipped(element: HTMLElement | null): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Moves an element into the viewport by adjusting its position to ensure it's fully visible.
|
|
179
|
+
* @param element The element to move into view.
|
|
180
|
+
* @param options Configuration options for the viewport adjustment.
|
|
181
|
+
* @param options.padding The minimum distance from viewport edges (defaults to 8px).
|
|
182
|
+
* @returns `true` if the position was adjusted, otherwise `false`.
|
|
183
|
+
*/
|
|
184
|
+
export declare function moveElementIntoViewport(element: HTMLElement | null, { padding }?: {
|
|
185
|
+
padding?: number | undefined;
|
|
186
|
+
}): boolean;
|
package/esm/core/utils/utils.js
CHANGED
|
@@ -348,3 +348,70 @@ export function toggleState(internals, state, value) {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Determines if an element is clipped by the viewport bounds
|
|
353
|
+
* @param element The element to check.
|
|
354
|
+
* @returns `true` if the element is clipped by the viewport, otherwise `false`.
|
|
355
|
+
*/
|
|
356
|
+
export function isElementClipped(element) {
|
|
357
|
+
if (!element) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const rect = element.getBoundingClientRect();
|
|
361
|
+
const viewportWidth = window.innerWidth;
|
|
362
|
+
const viewportHeight = window.innerHeight;
|
|
363
|
+
return rect.top < 0 || rect.left < 0 || rect.bottom > viewportHeight || rect.right > viewportWidth;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Moves an element into the viewport by adjusting its position to ensure it's fully visible.
|
|
367
|
+
* @param element The element to move into view.
|
|
368
|
+
* @param options Configuration options for the viewport adjustment.
|
|
369
|
+
* @param options.padding The minimum distance from viewport edges (defaults to 8px).
|
|
370
|
+
* @returns `true` if the position was adjusted, otherwise `false`.
|
|
371
|
+
*/
|
|
372
|
+
export function moveElementIntoViewport(element, { padding = 8 } = {}) {
|
|
373
|
+
if (!element) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const rect = element.getBoundingClientRect();
|
|
377
|
+
const viewportWidth = window.innerWidth;
|
|
378
|
+
const viewportHeight = window.innerHeight;
|
|
379
|
+
// Get current computed position values
|
|
380
|
+
const computedStyle = window.getComputedStyle(element);
|
|
381
|
+
const currentLeft = parseFloat(computedStyle.left) || 0;
|
|
382
|
+
const currentTop = parseFloat(computedStyle.top) || 0;
|
|
383
|
+
let newLeft = currentLeft;
|
|
384
|
+
let newTop = currentTop;
|
|
385
|
+
// Calculate the adjustments needed to bring the element into view
|
|
386
|
+
// Handle horizontal positioning
|
|
387
|
+
if (rect.left < 0) {
|
|
388
|
+
// Element extends beyond left edge - move it right
|
|
389
|
+
newLeft = currentLeft - rect.left + padding;
|
|
390
|
+
}
|
|
391
|
+
else if (rect.right > viewportWidth) {
|
|
392
|
+
// Element extends beyond right edge - move it left
|
|
393
|
+
newLeft = currentLeft - (rect.right - viewportWidth) - padding;
|
|
394
|
+
}
|
|
395
|
+
// Handle vertical positioning
|
|
396
|
+
if (rect.top < 0) {
|
|
397
|
+
// Element extends beyond top edge - move it down
|
|
398
|
+
newTop = currentTop - rect.top + padding;
|
|
399
|
+
}
|
|
400
|
+
else if (rect.bottom > viewportHeight) {
|
|
401
|
+
// Element extends beyond bottom edge - move it up
|
|
402
|
+
newTop = currentTop - (rect.bottom - viewportHeight) - padding;
|
|
403
|
+
}
|
|
404
|
+
// Ensure the element doesn't exceed viewport bounds after adjustment
|
|
405
|
+
// This prevents the element from being too large for the viewport
|
|
406
|
+
const maxLeft = viewportWidth - rect.width - padding;
|
|
407
|
+
const maxTop = viewportHeight - rect.height - padding;
|
|
408
|
+
newLeft = Math.max(padding, Math.min(newLeft, maxLeft));
|
|
409
|
+
newTop = Math.max(padding, Math.min(newTop, maxTop));
|
|
410
|
+
// Only apply position changes if they're different from current values
|
|
411
|
+
if (newLeft !== currentLeft || newTop !== currentTop) {
|
|
412
|
+
element.style.left = `${newLeft}px`;
|
|
413
|
+
element.style.top = `${newTop}px`;
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
@@ -25,10 +25,13 @@ export interface IDialogAdapter extends IBaseAdapter<IDialogComponent> {
|
|
|
25
25
|
showBackdrop(): void;
|
|
26
26
|
addSurfaceClass(className: string): void;
|
|
27
27
|
removeSurfaceClass(className: string): void;
|
|
28
|
+
moveSurfaceIntoView(): void;
|
|
28
29
|
addFullscreenListener(breakpoint: number, listener: (value: boolean) => void): void;
|
|
29
30
|
removeFullscreenListener(listener: (value: boolean) => void): void;
|
|
30
31
|
setAccessibleLabel(label: string): void;
|
|
31
32
|
setAccessibleDescription(description: string): void;
|
|
33
|
+
isDialogCompletelyOffScreen(): boolean;
|
|
34
|
+
isSurfaceClipped(): boolean;
|
|
32
35
|
}
|
|
33
36
|
export declare class DialogAdapter extends BaseAdapter<IDialogComponent> implements IDialogAdapter {
|
|
34
37
|
private _dialogElement;
|
|
@@ -59,9 +62,12 @@ export declare class DialogAdapter extends BaseAdapter<IDialogComponent> impleme
|
|
|
59
62
|
showBackdrop(): void;
|
|
60
63
|
addSurfaceClass(className: string): void;
|
|
61
64
|
removeSurfaceClass(className: string): void;
|
|
65
|
+
moveSurfaceIntoView(): void;
|
|
62
66
|
addFullscreenListener(breakpoint: number, listener: (value: boolean) => void): void;
|
|
63
67
|
removeFullscreenListener(listener: (value: boolean) => void): void;
|
|
64
68
|
setAccessibleLabel(label: string): void;
|
|
65
69
|
setAccessibleDescription(description: string): void;
|
|
70
|
+
isDialogCompletelyOffScreen(): boolean;
|
|
71
|
+
isSurfaceClipped(): boolean;
|
|
66
72
|
private _forceClose;
|
|
67
73
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { getShadowElement, playKeyframeAnimation } from '@tylertech/forge-core';
|
|
7
7
|
import { BACKDROP_CONSTANTS } from '../backdrop';
|
|
8
8
|
import { BaseAdapter } from '../core/base/base-adapter';
|
|
9
|
+
import { isElementClipped, moveElementIntoViewport } from '../core/utils/utils';
|
|
9
10
|
import { DialogComponent } from './dialog';
|
|
10
11
|
import { DIALOG_CONSTANTS, dialogStack, hideBackdrop, showBackdrop } from './dialog-constants';
|
|
11
12
|
export class DialogAdapter extends BaseAdapter {
|
|
@@ -133,6 +134,9 @@ export class DialogAdapter extends BaseAdapter {
|
|
|
133
134
|
removeSurfaceClass(className) {
|
|
134
135
|
this._surfaceElement.classList.remove(className);
|
|
135
136
|
}
|
|
137
|
+
moveSurfaceIntoView() {
|
|
138
|
+
moveElementIntoViewport(this._surfaceElement);
|
|
139
|
+
}
|
|
136
140
|
addFullscreenListener(breakpoint, listener) {
|
|
137
141
|
this._fullscreenMediaQuery = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
138
142
|
this._fullscreenMediaQuery.addEventListener('change', event => listener(event.matches));
|
|
@@ -150,6 +154,18 @@ export class DialogAdapter extends BaseAdapter {
|
|
|
150
154
|
setAccessibleDescription(description) {
|
|
151
155
|
this._accessibleDescriptionElement.textContent = description;
|
|
152
156
|
}
|
|
157
|
+
isDialogCompletelyOffScreen() {
|
|
158
|
+
if (!this._surfaceElement) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const rect = this._surfaceElement.getBoundingClientRect();
|
|
162
|
+
const viewportWidth = window.innerWidth;
|
|
163
|
+
const viewportHeight = window.innerHeight;
|
|
164
|
+
return rect.right <= 0 || rect.left >= viewportWidth || rect.bottom <= 0 || rect.top >= viewportHeight;
|
|
165
|
+
}
|
|
166
|
+
isSurfaceClipped() {
|
|
167
|
+
return isElementClipped(this._surfaceElement);
|
|
168
|
+
}
|
|
153
169
|
_forceClose() {
|
|
154
170
|
this._surfaceElement.classList.remove(BACKDROP_CONSTANTS.classes.EXITING);
|
|
155
171
|
this._dialogElement.close();
|
|
@@ -173,6 +173,10 @@ export class DialogCore {
|
|
|
173
173
|
return event.defaultPrevented;
|
|
174
174
|
};
|
|
175
175
|
const onMoveEnd = () => {
|
|
176
|
+
// Move dialog back into view if the surface is clipped
|
|
177
|
+
if (this._adapter.isSurfaceClipped()) {
|
|
178
|
+
this._adapter.moveSurfaceIntoView();
|
|
179
|
+
}
|
|
176
180
|
const event = new CustomEvent(DIALOG_CONSTANTS.events.MOVE_END);
|
|
177
181
|
this._adapter.removeSurfaceClass(DIALOG_CONSTANTS.classes.MOVING);
|
|
178
182
|
this._adapter.dispatchHostEvent(event);
|
|
@@ -100,7 +100,7 @@ export class ExpansionPanelAdapter extends BaseAdapter {
|
|
|
100
100
|
this._triggerElement?.removeAttribute('aria-expanded');
|
|
101
101
|
}
|
|
102
102
|
_getTriggerElementById(id) {
|
|
103
|
-
if (id) {
|
|
103
|
+
if (id && this.isConnected) {
|
|
104
104
|
const rootNode = this._component.getRootNode();
|
|
105
105
|
return rootNode.getElementById(id);
|
|
106
106
|
}
|
|
@@ -148,10 +148,8 @@ export class ExpansionPanelCore {
|
|
|
148
148
|
if (this._trigger !== value) {
|
|
149
149
|
this._clearTrigger();
|
|
150
150
|
this._trigger = value;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this._syncTrigger();
|
|
154
|
-
}
|
|
151
|
+
this._adapter.setTriggerElementById(this._trigger);
|
|
152
|
+
this._syncTrigger();
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
get triggerElement() {
|
|
@@ -161,9 +159,7 @@ export class ExpansionPanelCore {
|
|
|
161
159
|
if (this._adapter.triggerElement !== el) {
|
|
162
160
|
this._clearTrigger();
|
|
163
161
|
this._adapter.setTriggerElement(el);
|
|
164
|
-
|
|
165
|
-
this._syncTrigger();
|
|
166
|
-
}
|
|
162
|
+
this._syncTrigger();
|
|
167
163
|
}
|
|
168
164
|
}
|
|
169
165
|
}
|
package/esm/menu/menu-core.js
CHANGED
|
@@ -378,10 +378,10 @@ export class MenuCore extends CascadingListDropdownAwareCore {
|
|
|
378
378
|
_createCascadingElement({ index, options, parentValue }) {
|
|
379
379
|
const menu = this._adapter.createChildMenu(index, parentValue, this._onCascadingChildOpen.bind(this), this._onCascadingChildClose.bind(this), this._onCascadingOptionSelected.bind(this));
|
|
380
380
|
menu.mode = 'cascade';
|
|
381
|
-
menu.popupOffset = {
|
|
381
|
+
menu.popupOffset = { alignmentAxis: -8 };
|
|
382
382
|
menu.dense = this._dense;
|
|
383
383
|
menu.placement = 'right-start';
|
|
384
|
-
menu.fallbackPlacements = ['left-start', 'right-start', 'bottom', 'top'];
|
|
384
|
+
menu.fallbackPlacements = ['left-start', 'left-end', 'right-start', 'right-end', 'bottom', 'top'];
|
|
385
385
|
menu.persistSelection = this._persistSelection;
|
|
386
386
|
if (this._persistSelection) {
|
|
387
387
|
menu.selectedValue = this._selectedValue;
|
|
@@ -14,7 +14,7 @@ import { IconComponent, IconRegistry } from '../../icon';
|
|
|
14
14
|
import { StateLayerComponent } from '../../state-layer';
|
|
15
15
|
import { FocusIndicatorComponent } from '../../focus-indicator';
|
|
16
16
|
const template = '<template><div class=\"forge-split-view-panel\" id=\"root\" part=\"root\"><div class=\"forge-split-view-panel__handle\" id=\"handle\" part=\"handle\" role=\"separator\" aria-controls=\"content\" aria-grabbed=\"false\" tabindex=\"0\"><forge-icon class=\"forge-split-view-panel__icon\" id=\"icon\" part=\"icon\"></forge-icon><forge-state-layer target=\"handle\" id=\"state-layer\" exportparts=\"surface:state-layer\"></forge-state-layer><forge-focus-indicator inward target=\"handle\" part=\"focus-indicator\"></forge-focus-indicator></div><div class=\"forge-split-view-panel__content\" id=\"content\" part=\"content\" role=\"group\"><slot></slot></div></div></template>';
|
|
17
|
-
const styles = '.forge-split-view-panel{display:flex;width:100%;height:100%;overflow:hidden}.forge-split-view-panel__handle{color:var(--forge-theme-text-medium,rgba(0,0,0,.6));background-color:var(--forge-theme-outline,#e0e0e0);position:relative;display:flex;flex-shrink:0;justify-content:center;align-items:center;outline:0}.forge-split-view-panel__content{flex:1;overflow:hidden}.forge-split-view-panel--closed{display:none}.forge-split-view-panel--disabled #handle{pointer-events:none}.forge-split-view-panel--disabled .forge-split-view-panel__icon{display:none}.forge-split-view-panel[orientation=horizontal]{min-width:var(--forge-split-view-handle-width,8px);width:calc(var(--forge-split-view-panel-size,unset) + var(--forge-split-view-handle-width,8px));flex-direction:row}.forge-split-view-panel[orientation=horizontal] .forge-split-view-panel__handle{width:var(--forge-split-view-handle-width,8px);cursor:var(--forge-split-view-panel-cursor)}.forge-split-view-panel[orientation=horizontal].forge-split-view-panel--closing[resizable=end]{position:absolute;top:0;left:0;animation-name:
|
|
17
|
+
const styles = '.forge-split-view-panel{display:flex;width:100%;height:100%;overflow:hidden}.forge-split-view-panel__handle{color:var(--forge-theme-text-medium,rgba(0,0,0,.6));background-color:var(--forge-theme-outline,#e0e0e0);position:relative;display:flex;flex-shrink:0;justify-content:center;align-items:center;outline:0}.forge-split-view-panel__content{flex:1;overflow:hidden}.forge-split-view-panel--closed{display:none}.forge-split-view-panel--disabled #handle{pointer-events:none}.forge-split-view-panel--disabled .forge-split-view-panel__icon{display:none}.forge-split-view-panel[orientation=horizontal]{min-width:var(--forge-split-view-handle-width,8px);width:calc(var(--forge-split-view-panel-size,unset) + var(--forge-split-view-handle-width,8px));flex-direction:row}.forge-split-view-panel[orientation=horizontal] .forge-split-view-panel__handle{width:var(--forge-split-view-handle-width,8px);cursor:var(--forge-split-view-panel-cursor)}.forge-split-view-panel[orientation=horizontal].forge-split-view-panel--closing[resizable=end]{position:absolute;top:0;left:0;animation-name:uhqbsid;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1))}@keyframes uhqbsid{from{transform:none}to{transform:translateX(-100%)}}.forge-split-view-panel[orientation=horizontal].forge-split-view-panel--closing[resizable=start]{position:absolute;top:0;right:0;animation-name:uhqbsj9;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1))}@keyframes uhqbsj9{from{transform:none}to{transform:translateX(100%)}}.forge-split-view-panel[orientation=horizontal].forge-split-view-panel--opening[resizable=end]{position:absolute;top:0;left:0;animation-name:uhqbsjp;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1));animation-direction:reverse}@keyframes uhqbsjp{from{transform:none}to{transform:translateX(-100%)}}.forge-split-view-panel[orientation=horizontal].forge-split-view-panel--opening[resizable=start]{position:absolute;top:0;right:0;animation-name:uhqbskm;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1));animation-direction:reverse}@keyframes uhqbskm{from{transform:none}to{transform:translateX(100%)}}.forge-split-view-panel[orientation=vertical]{min-height:var(--forge-split-view-handle-width,8px);height:calc(var(--forge-split-view-panel-size,unset) + var(--forge-split-view-handle-width,8px));flex-direction:column}.forge-split-view-panel[orientation=vertical] .forge-split-view-panel__handle{height:var(--forge-split-view-handle-width,8px);cursor:var(--forge-split-view-panel-cursor)}.forge-split-view-panel[orientation=vertical].forge-split-view-panel--closing[resizable=end]{position:absolute;top:0;left:0;animation-name:uhqbslj;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1))}@keyframes uhqbslj{from{transform:none}to{transform:translateY(-100%)}}.forge-split-view-panel[orientation=vertical].forge-split-view-panel--closing[resizable=start]{position:absolute;bottom:0;left:0;animation-name:uhqbslt;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1))}@keyframes uhqbslt{from{transform:none}to{transform:translateY(100%)}}.forge-split-view-panel[orientation=vertical].forge-split-view-panel--opening[resizable=end]{position:absolute;top:0;left:0;animation-name:uhqbsm4;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1));animation-direction:reverse}@keyframes uhqbsm4{from{transform:none}to{transform:translateY(-100%)}}.forge-split-view-panel[orientation=vertical].forge-split-view-panel--opening[resizable=start]{position:absolute;bottom:0;left:0;animation-name:uhqbsmg;animation-duration:var(--forge-animation-duration-medium2, 300ms);animation-timing-function:var(--forge-animation-easing-standard,cubic-bezier(0.2,0,0,1));animation-direction:reverse}@keyframes uhqbsmg{from{transform:none}to{transform:translateY(100%)}}:host{z-index:var(--forge-split-view-animating-layer)!important;display:block;position:relative;height:100%;width:100%;flex:0}:host([hidden]){display:none}:host(:not([resizable=start],[resizable=end])){flex:1}:host(:not([resizable=start],[resizable=end])) .forge-split-view-panel{width:100%;height:100%;min-width:0;min-height:0}:host(:not([resizable=start],[resizable=end])) .forge-split-view-panel__handle{display:none}forge-focus-indicator{--forge-focus-indicator-active-width:2px}';
|
|
18
18
|
/**
|
|
19
19
|
* @tag forge-split-view-panel
|
|
20
20
|
*
|
package/esm/toast/toast-core.js
CHANGED