@nectary/components 2.1.2 → 2.1.3

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/dialog/index.js CHANGED
@@ -2,9 +2,9 @@ import '../icon-button';
2
2
  import '../icon';
3
3
  import '../stop-events';
4
4
  import '../title';
5
- import { disableScroll, enableScroll } from '../pop/utils';
6
5
  import { defineCustomElement, getAttribute, getBooleanAttribute, getRect, isAttrTrue, updateAttribute, getReactEventHandler, NectaryElement, updateBooleanAttribute, setClass, isTargetEqual } from '../utils';
7
6
  const templateHTML = '<style>:host{display:contents;--sinch-comp-dialog-max-width:512px;--sinch-comp-dialog-max-height:50vh}#dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;max-width:var(--sinch-comp-dialog-max-width);max-height:unset;border-radius:var(--sinch-comp-dialog-shape-radius);box-sizing:border-box;contain:content;background-color:var(--sinch-comp-dialog-color-default-background-initial);border:none;box-shadow:var(--sinch-comp-dialog-shadow);outline:0}#dialog:not([open]){display:none}dialog::backdrop{background-color:#000;opacity:.55}#header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;margin-bottom:12px;padding:0 24px}#caption{--sinch-global-color-text:var(--sinch-comp-dialog-color-default-title-initial);--sinch-comp-title-font:var(--sinch-comp-dialog-font-title)}#content{min-height:0;overflow:auto;max-height:var(--sinch-comp-dialog-max-height);padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#action.empty{display:none}#close{position:relative;left:4px;top:-4px;--sinch-global-color-icon:var(--sinch-comp-dialog-color-default-icon-initial)}</style><dialog id="dialog"><div id="header"><sinch-title id="caption" type="m" level="3" ellipsis></sinch-title><sinch-icon-button id="close" size="s"><sinch-icon id="icon-close" slot="icon" name="close"></sinch-icon></sinch-icon-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="action"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
7
+ import { disableScroll, enableScroll } from './utils';
8
8
  const template = document.createElement('template');
9
9
  template.innerHTML = templateHTML;
10
10
  defineCustomElement('sinch-dialog', class extends NectaryElement {
@@ -0,0 +1,2 @@
1
+ export declare const disableScroll: () => void;
2
+ export declare const enableScroll: () => void;
@@ -0,0 +1,18 @@
1
+ const bodyEl = document.body;
2
+ export const disableScroll = () => {
3
+ bodyEl.__dialog_counter__ = (bodyEl.__dialog_counter__ ?? 0) + 1;
4
+ if (bodyEl.__dialog_counter__ === 1) {
5
+ const scrollWidth = window.innerWidth - document.documentElement.clientWidth;
6
+ bodyEl.style.overflow = 'hidden';
7
+ if (scrollWidth > 0) {
8
+ bodyEl.style.setProperty('padding-right', `${scrollWidth}px`);
9
+ }
10
+ }
11
+ };
12
+ export const enableScroll = () => {
13
+ bodyEl.__dialog_counter__ = Math.max(0, (bodyEl.__dialog_counter__ ?? 0) - 1);
14
+ if (bodyEl.__dialog_counter__ === 0) {
15
+ bodyEl.style.overflow = '';
16
+ bodyEl.style.removeProperty('padding-right');
17
+ }
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
package/pop/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, getRect, isAttrTrue, updateLiteralAttribute, getReactEventHandler, updateBooleanAttribute, NectaryElement, throttleAnimationFrame, isElementFocused, updateIntegerAttribute, getIntegerAttribute, getFirstFocusableElement, getFirstSlotElement, Context, subscribeContext, isTargetEqual } from '../utils';
2
2
  const templateHTML = '<style>:host{display:contents;position:relative}dialog{position:fixed;left:0;top:0;margin:0;outline:0;padding:0;border:none;box-sizing:border-box;max-width:unset;max-height:unset;z-index:1;background:0 0;overflow:visible}dialog:not([open]){display:none}dialog::backdrop{background-color:transparent}#content{position:relative;z-index:1}#target-open{display:flex;flex-direction:column;position:absolute;left:0;top:0}#focus{display:none;position:absolute;width:0;height:0}</style><slot id="target" name="target" aria-haspopup="dialog" aria-expanded="false"></slot><div id="focus"></div><dialog id="dialog"><div id="target-open"><slot name="target-open"></slot></div><div id="content"><slot name="content"></slot></div></dialog>';
3
- import { disableScroll, enableScroll, orientationValues } from './utils';
3
+ import { disableOverscroll, enableOverscroll, orientationValues } from './utils';
4
4
  const template = document.createElement('template');
5
5
  template.innerHTML = templateHTML;
6
6
  defineCustomElement('sinch-pop', class extends NectaryElement {
@@ -17,6 +17,8 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
17
17
  #keydownContext;
18
18
  #visibilityContext;
19
19
  #targetStyleValue = null;
20
+ #modalWidth = 0;
21
+ #modalHeight = 0;
20
22
  constructor() {
21
23
  super();
22
24
  const shadowRoot = this.attachShadow();
@@ -201,7 +203,10 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
201
203
  }
202
204
  }
203
205
  }
204
- disableScroll();
206
+ disableOverscroll();
207
+ window.addEventListener('scroll', this.#updatePosition, {
208
+ passive: false
209
+ });
205
210
  window.addEventListener('resize', this.#onResize);
206
211
  requestAnimationFrame(() => {
207
212
  if (this.isConnected && this.#$dialog.open) {
@@ -242,14 +247,18 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
242
247
  if (this.#targetActiveElement !== null) {
243
248
  if (!isElementFocused(this.#targetActiveElement)) {
244
249
  this.#$targetSlot.addEventListener('focus', this.#stopEventPropagation, true);
245
- this.#targetActiveElement.focus();
250
+ this.#targetActiveElement.focus({
251
+ preventScroll: true
252
+ });
246
253
  this.#$targetSlot.removeEventListener('focus', this.#stopEventPropagation, true);
247
254
  if (!isElementFocused(this.#targetActiveElement)) {
248
255
  const $targetEl = this.#targetActiveElement;
249
256
  requestAnimationFrame(() => {
250
257
  if (this.isConnected && !this.#$dialog.open) {
251
258
  this.#$targetSlot.addEventListener('focus', this.#stopEventPropagation, true);
252
- $targetEl.focus();
259
+ $targetEl.focus({
260
+ preventScroll: true
261
+ });
253
262
  this.#$targetSlot.removeEventListener('focus', this.#stopEventPropagation, true);
254
263
  }
255
264
  });
@@ -257,14 +266,58 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
257
266
  this.#targetActiveElement = null;
258
267
  }
259
268
  }
260
- enableScroll();
269
+ enableOverscroll();
261
270
  this.#resizeThrottle.cancel();
262
271
  window.removeEventListener('resize', this.#onResize);
272
+ window.removeEventListener('scroll', this.#updatePosition);
263
273
  this.#$contentSlot.removeEventListener('slotchange', this.#onContentSlotChange);
264
274
  }
265
275
  #onResize = () => {
266
276
  this.#resizeThrottle.fn();
267
277
  };
278
+ #updatePosition = () => {
279
+ const targetRect = this.modal ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
280
+ const orient = this.orientation;
281
+ const modalWidth = this.#modalWidth;
282
+ const modalHeight = this.#modalHeight;
283
+ const inset = this.inset;
284
+ let xPos = 0;
285
+ let yPos = 0;
286
+ if (orient === 'bottom-right' || orient === 'top-right' || orient === 'top-stretch' || orient === 'bottom-stretch') {
287
+ xPos = targetRect.x;
288
+ }
289
+ if (orient === 'bottom-left' || orient === 'top-left') {
290
+ xPos = targetRect.x + targetRect.width - modalWidth;
291
+ }
292
+ if (orient === 'bottom-center' || orient === 'top-center') {
293
+ xPos = targetRect.x + targetRect.width / 2 - modalWidth / 2;
294
+ }
295
+ if (orient === 'center-right') {
296
+ xPos = targetRect.x + targetRect.width;
297
+ }
298
+ if (orient === 'center-left') {
299
+ xPos = targetRect.x - modalWidth;
300
+ }
301
+ if (orient === 'bottom-left' || orient === 'bottom-right' || orient === 'bottom-stretch' || orient === 'bottom-center') {
302
+ yPos = targetRect.y + targetRect.height;
303
+ }
304
+ if (orient === 'top-left' || orient === 'top-right' || orient === 'top-stretch' || orient === 'top-center') {
305
+ yPos = targetRect.y - modalHeight;
306
+ }
307
+ if (orient === 'center-left' || orient === 'center-right') {
308
+ yPos = targetRect.y + targetRect.height / 2 - modalHeight / 2;
309
+ }
310
+ const clampedXPos = Math.max(inset, Math.min(xPos, window.innerWidth - modalWidth - inset));
311
+ const clampedYPos = Math.max(inset, Math.min(yPos, window.innerHeight - modalHeight - inset));
312
+ this.#$dialog.style.setProperty('left', `${clampedXPos}px`);
313
+ this.#$dialog.style.setProperty('top', `${clampedYPos}px`);
314
+ if (!this.modal) {
315
+ const targetLeftPos = targetRect.x - clampedXPos;
316
+ const targetTopPos = targetRect.y - clampedYPos;
317
+ this.#$targetOpenWrapper.style.setProperty('left', `${targetLeftPos}px`);
318
+ this.#$targetOpenWrapper.style.setProperty('top', `${targetTopPos}px`);
319
+ }
320
+ };
268
321
  #updateOrientation = () => {
269
322
  this.#$dialog.style.setProperty('width', 'max-content');
270
323
  const targetRect = this.#getTargetRect();
@@ -276,6 +329,8 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
276
329
  const inset = this.inset;
277
330
  let xPos = 0;
278
331
  let yPos = 0;
332
+ this.#modalHeight = modalHeight;
333
+ this.#modalWidth = modalWidth;
279
334
  if (orient === 'bottom-right' || orient === 'top-right' || orient === 'top-stretch' || orient === 'bottom-stretch') {
280
335
  xPos = targetRect.x;
281
336
  }
package/pop/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { TSinchPopOrientation } from './types';
2
2
  export declare const orientationValues: readonly TSinchPopOrientation[];
3
- export declare const disableScroll: () => void;
4
- export declare const enableScroll: () => void;
3
+ export declare const disableOverscroll: () => void;
4
+ export declare const enableOverscroll: () => void;
package/pop/utils.js CHANGED
@@ -1,12 +1,16 @@
1
1
  export const orientationValues = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'bottom-stretch', 'bottom-center', 'top-stretch', 'top-center', 'center-left', 'center-right'];
2
2
  const bodyEl = document.body;
3
- export const disableScroll = () => {
4
- bodyEl.style.overflow = 'hidden';
3
+ export const disableOverscroll = () => {
5
4
  bodyEl.__pop_counter__ = (bodyEl.__pop_counter__ ?? 0) + 1;
5
+ if (bodyEl.__pop_counter__ === 1) {
6
+ bodyEl.style.setProperty('overscroll-behavior', 'none');
7
+ document.documentElement.style.setProperty('overscroll-behavior', 'none');
8
+ }
6
9
  };
7
- export const enableScroll = () => {
10
+ export const enableOverscroll = () => {
8
11
  bodyEl.__pop_counter__ = Math.max(0, (bodyEl.__pop_counter__ ?? 0) - 1);
9
12
  if (bodyEl.__pop_counter__ === 0) {
10
- document.body.style.overflow = '';
13
+ bodyEl.style.removeProperty('overscroll-behavior');
14
+ document.documentElement.style.removeProperty('overscroll-behavior');
11
15
  }
12
16
  };