@nectary/components 2.1.1 → 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.
@@ -2,7 +2,7 @@ import '../icon';
2
2
  import '../title';
3
3
  import '../text';
4
4
  import { defineCustomElement, getAttribute, getBooleanAttribute, getLiteralAttribute, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateLiteralAttribute } from '../utils';
5
- const templateHTML = '<style>:host{display:block;outline:0;min-height:48px}#wrapper{display:flex;flex-direction:column;position:relative;width:100%;height:100%;box-sizing:border-box;overflow:hidden;border-bottom:1px solid var(--sinch-comp-accordion-color-default-border-initial)}#button{all:initial;cursor:pointer;display:flex;align-items:center;gap:8px;box-sizing:border-box;width:100%;height:48px;padding:12px 8px;--sinch-global-color-icon:var(--sinch-comp-accordion-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-accordion-size-icon)}#button>*{pointer-events:none}#button:disabled{cursor:initial;--sinch-global-color-icon:var(--sinch-comp-accordion-color-disabled-icon-initial)}#button::before{width:8px;height:8px;border-radius:50%;margin-left:2px;margin-right:8px}:host([status=success]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-success)}:host([status=warn]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-warning)}:host([status=error]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-error)}:host([status=info]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-info)}#title{flex:1;min-width:0;--sinch-comp-title-font:var(--sinch-comp-accordion-font-title);--sinch-global-color-text:var(--sinch-comp-accordion-color-default-title-initial)}#button:disabled>#title{--sinch-global-color-text:var(--sinch-comp-accordion-color-disabled-title-initial)}#content{display:none;overflow-y:auto;flex-shrink:1;min-height:0;padding:0 8px 18px}#dropdown-icon{transform:rotate(0)}#button[aria-expanded=true]>#dropdown-icon{transform:rotate(180deg)}#button[aria-expanded=true]+#content{display:block}#optional{--sinch-comp-text-font:var(--sinch-comp-accordion-font-optional-text);--sinch-global-color-text:var(--sinch-comp-accordion-color-default-optional-text-initial)}#button:disabled>#optional{--sinch-global-color-text:var(--sinch-comp-accordion-color-disabled-optional-text-initial)}</style><div id="wrapper"><button id="button" aria-controls="content" aria-expanded="false"><slot name="icon"></slot><sinch-title id="title" level="3" type="m" ellipsis></sinch-title><sinch-text id="optional" type="m"></sinch-text><sinch-icon id="dropdown-icon" name="keyboard_arrow_down"></sinch-icon></button><div id="content" role="region" aria-labelledby="button"><slot name="content"></slot></div></div>';
5
+ const templateHTML = '<style>:host{display:block;outline:0;min-height:48px}#wrapper{display:flex;flex-direction:column;position:relative;width:100%;height:100%;box-sizing:border-box;overflow:hidden;border-bottom:1px solid var(--sinch-comp-accordion-color-default-border-initial)}:host(:last-child)>#wrapper{border-bottom:none}#button{all:initial;cursor:pointer;display:flex;align-items:center;gap:8px;box-sizing:border-box;width:100%;height:48px;padding:12px 8px;--sinch-global-color-icon:var(--sinch-comp-accordion-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-accordion-size-icon)}#button>*{pointer-events:none}#button:disabled{cursor:initial;--sinch-global-color-icon:var(--sinch-comp-accordion-color-disabled-icon-initial)}#button::before{width:8px;height:8px;border-radius:50%;margin-left:2px;margin-right:8px}:host([status=success]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-success)}:host([status=warn]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-warning)}:host([status=error]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-error)}:host([status=info]) #button::before{content:"";background-color:var(--sinch-comp-accordion-color-default-status-info)}#title{flex:1;min-width:0;--sinch-comp-title-font:var(--sinch-comp-accordion-font-title);--sinch-global-color-text:var(--sinch-comp-accordion-color-default-title-initial)}#button:disabled>#title{--sinch-global-color-text:var(--sinch-comp-accordion-color-disabled-title-initial)}#content{display:none;overflow-y:auto;flex-shrink:1;min-height:0;padding:0 8px 18px}#dropdown-icon{transform:rotate(0)}#button[aria-expanded=true]>#dropdown-icon{transform:rotate(180deg)}#button[aria-expanded=true]+#content{display:block}#optional{--sinch-comp-text-font:var(--sinch-comp-accordion-font-optional-text);--sinch-global-color-text:var(--sinch-comp-accordion-color-default-optional-text-initial)}#button:disabled>#optional{--sinch-global-color-text:var(--sinch-comp-accordion-color-disabled-optional-text-initial)}</style><div id="wrapper"><button id="button" aria-controls="content" aria-expanded="false"><slot name="icon"></slot><sinch-title id="title" level="3" type="m" ellipsis></sinch-title><sinch-text id="optional" type="m"></sinch-text><sinch-icon id="dropdown-icon" name="keyboard_arrow_down"></sinch-icon></button><div id="content" role="region" aria-labelledby="button"><slot name="content"></slot></div></div>';
6
6
  import { statusValues } from './utils';
7
7
  const template = document.createElement('template');
8
8
  template.innerHTML = templateHTML;
@@ -92,9 +92,9 @@ defineCustomElement('sinch-action-menu', class extends NectaryElement {
92
92
  case 'Enter':
93
93
  case 'Space':
94
94
  {
95
+ e.preventDefault();
95
96
  const $opt = this.#findSelectedOption();
96
97
  if ($opt !== null) {
97
- e.preventDefault();
98
98
  $opt.click();
99
99
  }
100
100
  break;
package/button/index.js CHANGED
@@ -139,6 +139,7 @@ defineCustomElement('sinch-button', class extends NectaryElement {
139
139
  case 'Space':
140
140
  case 'Enter':
141
141
  {
142
+ e.preventDefault();
142
143
  this.click();
143
144
  }
144
145
  }
@@ -1,4 +1,4 @@
1
- import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, getIntegerAttribute, getReactEventHandler, getRect, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute, subscribeContext } from '../utils';
1
+ import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, getIntegerAttribute, getReactEventHandler, getRect, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute, subscribeContext, isTargetEqual } from '../utils';
2
2
  const templateHTML = '<style>:host{display:block;outline:0}#listbox{display:flex;flex-direction:row;flex-wrap:wrap;padding:4px 10px;overflow-y:auto}#listbox.empty{display:none}</style><div id="listbox" role="presentation"><slot id="options"></slot></div>';
3
3
  const NUM_COLS_DEFAULT = 5;
4
4
  const ITEM_WIDTH = 44;
@@ -132,7 +132,7 @@ defineCustomElement('sinch-color-menu', class extends NectaryElement {
132
132
  };
133
133
  #onListboxClick = e => {
134
134
  const $elem = e.target;
135
- if ($elem === this.#$listbox) {
135
+ if (isTargetEqual(e, this.#$listbox)) {
136
136
  return;
137
137
  }
138
138
  this.focus();
@@ -156,9 +156,9 @@ defineCustomElement('sinch-color-menu', class extends NectaryElement {
156
156
  case 'Space':
157
157
  case 'Enter':
158
158
  {
159
+ e.preventDefault();
159
160
  const $option = this.#findSelectedOption();
160
161
  if ($option !== null) {
161
- e.preventDefault();
162
162
  this.#dispatchChangeEvent($option);
163
163
  }
164
164
  break;
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
+ };
@@ -9,7 +9,7 @@ import '../tabs-icon-option';
9
9
  import '../emoji';
10
10
  import '../text';
11
11
  import '../icon';
12
- import { defineCustomElement, getAttribute, getBooleanAttribute, NectaryElement, updateAttribute, updateBooleanAttribute, getReactEventHandler, getRect, subscribeContext, debounceTimeout, setClass } from '../utils';
12
+ import { defineCustomElement, getAttribute, getBooleanAttribute, NectaryElement, updateAttribute, updateBooleanAttribute, getReactEventHandler, getRect, debounceTimeout, setClass } from '../utils';
13
13
  import dataJson from './data.json';
14
14
  const templateHTML = '<style>:host{display:block}#wrapper{width:384px;max-height:504px;display:flex;flex-direction:column;gap:8px;padding:12px 0}#toolbar{display:flex;gap:8px;padding:0 12px}#search{flex:1;min-width:0}#search-clear:not(.active){display:none}#list-wrapper{overflow-y:auto;overflow-x:hidden;width:384px;box-sizing:border-box;scrollbar-gutter:stable}#list{display:flex;flex-wrap:wrap;gap:8px;padding:4px 12px 0;width:384px;box-sizing:border-box}#not-found{display:none;width:100%;height:48px;align-items:center;justify-content:center;pointer-events:none;user-select:none;--sinch-global-color-text:var(--sinch-comp-emoji-picker-color-default-text-not-found);--sinch-comp-text-font:var(--sinch-comp-emoji-picker-font-not-found)}#not-found.active{display:flex}#tabs svg{pointer-events:none;height:var(--sinch-global-size-icon);fill:var(--sinch-global-color-icon)}</style><div id="wrapper"><div id="toolbar"><sinch-input id="search" size="l" aria-label="Search emojis"><sinch-icon id="icon-search" slot="icon" name="search"></sinch-icon><sinch-icon-button id="search-clear" slot="right" aria-label="Clear"><sinch-icon slot="icon" name="close"></sinch-icon></sinch-icon-button></sinch-input><sinch-popover id="skin-popover" orientation="bottom-left" aria-label="Emoji skin tone select"><sinch-icon-button id="skin-button" slot="target" size="l" aria-label="Select emoji skin tones"><sinch-color-swatch id="skin-swatch" slot="icon" name="skintone-default"></sinch-color-swatch></sinch-icon-button><sinch-color-menu id="skin-menu" slot="content" cols="1" value="skintone-default" aria-label="Emoji skin tone menu"><sinch-color-menu-option value="skintone-default"></sinch-color-menu-option><sinch-color-menu-option value="skintone-light"></sinch-color-menu-option><sinch-color-menu-option value="skintone-light-medium"></sinch-color-menu-option><sinch-color-menu-option value="skintone-medium"></sinch-color-menu-option><sinch-color-menu-option value="skintone-medium-dark"></sinch-color-menu-option><sinch-color-menu-option value="skintone-dark"></sinch-color-menu-option></sinch-color-menu></sinch-popover></div><sinch-tabs id="tabs" aria-label="Emoji groups"><sinch-tabs-icon-option id="tab-emotions"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M15.5 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-7 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2ZM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8Zm4.41-6.11a.745.745 0 0 0-1.03.24A3.98 3.98 0 0 1 12 16c-1.38 0-2.64-.7-3.38-1.88a.747.747 0 1 0-1.27.79A5.446 5.446 0 0 0 12 17.5c1.9 0 3.63-.97 4.65-2.58.22-.35.11-.81-.24-1.03Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-people"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 6a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M15.89 8.11C15.5 7.72 14.83 7 13.53 7h-2.54a5.023 5.023 0 0 1-4.92-4.15.998.998 0 0 0-.98-.85c-.61 0-1.09.54-1 1.14A7.037 7.037 0 0 0 9 8.71V21c0 .55.45 1 1 1s1-.45 1-1v-5h2v5c0 .55.45 1 1 1s1-.45 1-1V10.05l3.24 3.24a.996.996 0 1 0 1.41-1.41l-3.76-3.77Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-animals"><svg slot="icon" viewBox="0 0 24 24"><path d="M17 14c-.24-.24-.44-.49-.65-.75C17.51 11.5 19 8.56 19 5c0-1.95-.74-3-2-3-1.54 0-3.96 2.06-5 5.97C10.96 4.06 8.54 2 7 2 5.74 2 5 3.05 5 5c0 3.56 1.49 6.5 2.65 8.25-.21.26-.41.51-.65.75-.25.25-2 1.39-2 3.5C5 19.98 7.02 22 9.5 22c1.5 0 2.5-.5 2.5-.5s1 .5 2.5.5c2.48 0 4.5-2.02 4.5-4.5 0-2.11-1.75-3.25-2-3.5Zm-.12-9.97c.06.17.12.48.12.97 0 2.84-1.11 5.24-2.07 6.78-.38-.26-.83-.48-1.4-.62.24-4.52 2.44-6.83 3.35-7.13ZM7 5c0-.49.06-.8.12-.97.91.3 3.11 2.61 3.36 7.13-.58.14-1.03.35-1.4.62C8.11 10.24 7 7.84 7 5Zm7.5 15c-1 0-1.8-.33-2.22-.56.42-.18.72-.71.72-.94 0-.28-.45-.5-1-.5s-1 .22-1 .5c0 .23.3.76.72.94-.42.23-1.22.56-2.22.56A2.5 2.5 0 0 1 7 17.5c0-.7.43-1.24 1-1.73.44-.36.61-.52 1.3-1.37.76-.95 1.09-1.4 2.7-1.4s1.94.45 2.7 1.4c.69.85.86 1.01 1.3 1.37.57.49 1 1.03 1 1.73a2.5 2.5 0 0 1-2.5 2.5Zm-.5-4c0 .41-.22.75-.5.75s-.5-.34-.5-.75.22-.75.5-.75.5.34.5.75Zm-3 0c0 .41-.22.75-.5.75s-.5-.34-.5-.75.22-.75.5-.75.5.34.5.75Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-food"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M19 19H3c-.55 0-1 .45-1 1s.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1Zm1-16H9v2.4l1.81 1.45c.12.09.19.24.19.39v4.26c0 .28-.22.5-.5.5h-4c-.28 0-.5-.22-.5-.5V7.24c0-.15.07-.3.19-.39L8 5.4V3H6c-1.1 0-2 .9-2 2v8c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2Zm0 5h-2V5h2v3Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-travel"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m21.99 14.77-1.43-4.11c-.14-.4-.52-.66-.97-.66H12.4c-.46 0-.83.26-.98.66L10 14.77v5.24c0 .55.45.99 1 .99s1-.45 1-1v-1h8v1a1 1 0 0 0 2 .01l-.01-5.24Zm-10.38-1.43.69-2c.05-.2.24-.34.46-.34h6.48c.21 0 .4.14.47.34l.69 2a.5.5 0 0 1-.47.66h-7.85a.5.5 0 0 1-.47-.66Zm.38 3.66c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1Zm8 0c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1Z"/><path d="M14 4.5V9h1V4c0-.55-.45-1-1-1H8c-.55 0-1 .45-1 1v4H3c-.55 0-1 .45-1 1v12h1V9.5c0-.28.22-.5.5-.5h4c.28 0 .5-.22.5-.5v-4c0-.28.22-.5.5-.5h5c.28 0 .5.22.5.5Z"/><path d="M7 11H5v2h2v-2Zm5-6h-2v2h2V5ZM7 15H5v2h2v-2Zm0 4H5v2h2v-2Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-sports"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M19.52 2.49C17.18.15 12.9.62 9.97 3.55c-1.6 1.6-2.52 3.87-2.54 5.46-.02 1.58.26 3.89-1.35 5.5l-3.54 3.53c-.39.39-.39 1.02 0 1.42.39.39 1.02.39 1.42 0l3.53-3.54c1.61-1.61 3.92-1.33 5.5-1.35 1.58-.02 3.86-.94 5.46-2.54 2.93-2.92 3.41-7.2 1.07-9.54Zm-9.2 9.19c-1.53-1.53-1.05-4.61 1.06-6.72 2.11-2.11 5.18-2.59 6.72-1.06 1.53 1.53 1.05 4.61-1.06 6.72-2.11 2.11-5.18 2.59-6.72 1.06ZM18 17c.53 0 1.04.21 1.41.59.78.78.78 2.05 0 2.83-.37.37-.88.58-1.41.58-.53 0-1.04-.21-1.41-.59-.78-.78-.78-2.05 0-2.83.37-.37.88-.58 1.41-.58Zm0-2a3.998 3.998 0 0 0-2.83 6.83c.78.78 1.81 1.17 2.83 1.17a3.998 3.998 0 0 0 2.83-6.83A3.998 3.998 0 0 0 18 15Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-objects"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3c-.46 0-.93.04-1.4.14-2.76.53-4.96 2.76-5.48 5.52-.48 2.61.48 5.01 2.22 6.56.43.38.66.91.66 1.47V19c0 1.1.9 2 2 2h.28a1.98 1.98 0 0 0 3.44 0H14c1.1 0 2-.9 2-2v-2.31c0-.55.22-1.09.64-1.46A6.956 6.956 0 0 0 19 10c0-3.87-3.13-7-7-7Zm.5 11h-1v-2.59L9.67 9.59l.71-.71L12 10.5l1.62-1.62.71.71-1.83 1.83V14Zm1 5c-.01 0-.02-.01-.03-.01V19h-2.94v-.01c-.01 0-.02.01-.03.01-.28 0-.5-.22-.5-.5s.22-.5.5-.5c.01 0 .02.01.03.01V18h2.94v.01c.01 0 .02-.01.03-.01.28 0 .5.22.5.5s-.22.5-.5.5Zm0-2h-3c-.28 0-.5-.22-.5-.5s.22-.5.5-.5h3c.28 0 .5.22.5.5s-.22.5-.5.5Z"/></svg></sinch-tabs-icon-option><sinch-tabs-icon-option id="tab-symbols"><svg slot="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M10 5H4c-.55 0-1 .45-1 1s.45 1 1 1h2v3c0 .55.45 1 1 1s1-.45 1-1V7h2c.55 0 1-.45 1-1s-.45-1-1-1Zm0-3H4c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1Zm10.89 11.11a.996.996 0 0 0-1.41 0l-6.36 6.36a.996.996 0 1 0 1.41 1.41l6.36-6.36a.996.996 0 0 0 0-1.41ZM14.5 16a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm5 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-4-10A2.5 2.5 0 0 0 18 8.5V4h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1v3.51c-.42-.32-.93-.51-1.5-.51a2.5 2.5 0 0 0 0 5Zm-5.05 7.09a.996.996 0 1 0-1.41-1.41l-.71.71-.71-.71.35-.35a2.499 2.499 0 0 0-1.77-4.27 2.499 2.499 0 0 0-1.77 4.27l.35.35-1.06 1.06c-.98.98-.98 2.56 0 3.54.5.48 1.14.72 1.78.72.64 0 1.28-.24 1.77-.73l1.06-1.06.71.71a.996.996 0 1 0 1.41-1.41l-.71-.71.71-.71Zm-4.6-3.89a.5.5 0 0 1 .35-.15.5.5 0 0 1 .35.15c.19.2.19.51 0 .71l-.35.35-.35-.36a.5.5 0 0 1-.15-.35.5.5 0 0 1 .15-.35Zm0 5.65a.5.5 0 0 1-.35.15.5.5 0 0 1-.35-.15.5.5 0 0 1-.15-.35.5.5 0 0 1 .15-.35l1.06-1.06.71.71-1.07 1.05Z"/></svg></sinch-tabs-icon-option></sinch-tabs><div id="list-wrapper"><div id="list"></div><div id="not-found"><sinch-text type="m">No results</sinch-text></div></div></div>';
15
15
  const groupLabels = ['Emotions', 'People', 'Animals and nature', 'Food and drinks', 'Travel and places', 'Sports and activities', 'Objects', 'Symbols and flags'];
@@ -63,9 +63,6 @@ defineCustomElement('sinch-emoji-picker', class extends NectaryElement {
63
63
  this.#$searchClearButton.addEventListener('-click', this.#onSearchClearClick, {
64
64
  signal
65
65
  });
66
- this.addEventListener('keydown', this.#onListboxKeyDown, {
67
- signal
68
- });
69
66
  this.#$skinButton.addEventListener('-click', this.#onSkinButtonClick, {
70
67
  signal
71
68
  });
@@ -81,8 +78,6 @@ defineCustomElement('sinch-emoji-picker', class extends NectaryElement {
81
78
  this.addEventListener('-change', this.#onChangeReactHandler, {
82
79
  signal
83
80
  });
84
- subscribeContext(this, 'keydown', this.#onContextKeyDown, signal);
85
- subscribeContext(this, 'visibility', this.#onContextVisibility, signal);
86
81
  this.#updateTabs();
87
82
  this.#updateEmojis();
88
83
  }
@@ -118,34 +113,6 @@ defineCustomElement('sinch-emoji-picker', class extends NectaryElement {
118
113
  detail: value
119
114
  }));
120
115
  };
121
- #onContextKeyDown = e => {
122
- this.#handleKeydown(e.detail);
123
- };
124
- #onContextVisibility = e => {
125
- if (e.detail) {} else {}
126
- };
127
- #onListboxKeyDown = e => {
128
- this.#handleKeydown(e);
129
- };
130
- #handleKeydown(e) {
131
- switch (e.code) {
132
- case 'Space':
133
- case 'Enter':
134
- {
135
- break;
136
- }
137
- case 'ArrowUp':
138
- {
139
- e.preventDefault();
140
- break;
141
- }
142
- case 'ArrowDown':
143
- {
144
- e.preventDefault();
145
- break;
146
- }
147
- }
148
- }
149
116
  #onTabsChange = e => {
150
117
  const value = e.detail;
151
118
  updateAttribute(this.#$tabs, 'value', value);
@@ -146,6 +146,7 @@ defineCustomElement('sinch-icon-button', class extends NectaryElement {
146
146
  case 'Space':
147
147
  case 'Enter':
148
148
  {
149
+ e.preventDefault();
149
150
  this.click();
150
151
  }
151
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "2.1.1",
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
  };
@@ -180,6 +180,7 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
180
180
  case 'Space':
181
181
  case 'Enter':
182
182
  {
183
+ e.preventDefault();
183
184
  this.click();
184
185
  }
185
186
  }
@@ -2,7 +2,7 @@ import '../input';
2
2
  import '../icon-button';
3
3
  import '../icon';
4
4
  import '../text';
5
- import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute, debounceTimeout, setClass, subscribeContext, hasClass } from '../utils';
5
+ import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute, debounceTimeout, setClass, subscribeContext, hasClass, isTargetEqual } from '../utils';
6
6
  const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}#search{display:none;margin:10px}#search.active{display:block}#search-clear:not(.active){display:none}#not-found{display:flex;align-items:center;justify-content:center;width:100%;height:30px;margin-bottom:10px;pointer-events:none;user-select:none;--sinch-comp-text-font:var(--sinch-comp-select-menu-font-not-found-text);--sinch-global-color-text:var(--sinch-comp-select-menu-color-default-not-found-text-initial)}#not-found:not(.active){display:none}::slotted(.hidden){display:none}</style><sinch-input id="search" size="s" placeholder="Search"><sinch-icon id="icon-search" slot="icon" name="search"></sinch-icon><sinch-icon-button id="search-clear" slot="right"><sinch-icon slot="icon" name="close"></sinch-icon></sinch-icon-button></sinch-input><div id="not-found"><sinch-text type="m">No results</sinch-text></div><div id="listbox" role="presentation"><slot></slot></div>';
7
7
  const ITEM_HEIGHT = 40;
8
8
  const NUM_ITEMS_SEARCH = 7;
@@ -39,7 +39,6 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
39
39
  this.addEventListener('keydown', this.#onListboxKeyDown, options);
40
40
  this.addEventListener('focus', this.#onFocus, options);
41
41
  this.addEventListener('blur', this.#onListboxBlur, options);
42
- this.#$listbox.addEventListener('mousedown', this.#onListboxMousedown, options);
43
42
  this.#$listbox.addEventListener('click', this.#onListboxClick, options);
44
43
  this.#$search.addEventListener('-change', this.#onSearchChange, options);
45
44
  this.#$searchClear.addEventListener('-click', this.#onSearchClearClick, options);
@@ -109,20 +108,15 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
109
108
  this.#$search.focus();
110
109
  }
111
110
  };
112
- #onListboxMousedown = e => {
113
- const $elem = e.target;
114
- if (!getBooleanAttribute($elem, 'disabled')) {
115
- this.#dispatchChangeEvent($elem);
116
- }
117
- };
118
111
  #onListboxBlur = () => {
119
112
  this.#selectOption(null);
120
113
  };
121
114
  #onListboxClick = e => {
122
115
  const $elem = e.target;
123
116
  this.focus();
124
- if (!getBooleanAttribute($elem, 'disabled')) {
117
+ if (!isTargetEqual(e, this.#$listbox) && !getBooleanAttribute($elem, 'disabled')) {
125
118
  this.#selectOption($elem);
119
+ this.#dispatchChangeEvent($elem);
126
120
  }
127
121
  };
128
122
  #onSearchChange = e => {
@@ -167,9 +161,9 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
167
161
  case 'Space':
168
162
  case 'Enter':
169
163
  {
164
+ e.preventDefault();
170
165
  const $option = this.#findSelectedOption();
171
166
  if ($option !== null) {
172
- e.preventDefault();
173
167
  this.#dispatchChangeEvent($option);
174
168
  }
175
169
  break;
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, getBooleanAttribute, isAttrTrue, NectaryElement, updateBooleanAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:table-row;background-color:var(--sinch-comp-table-color-row-default-background-initial)}:host([selected]){background-color:var(--sinch-comp-table-color-row-checked-background-initial)}:host(:hover){background-color:var(--sinch-comp-table-color-row-default-background-hover)}:host([sticky]) ::slotted(sinch-table-head-cell){position:sticky;top:0;z-index:1;background-color:var(--sinch-comp-table-color-row-default-background-sticky)}</style><slot></slot>';
2
+ const templateHTML = '<style>:host{display:table-row;background-color:var(--sinch-comp-table-color-row-default-background-initial)}:host([selected]){background-color:var(--sinch-comp-table-color-row-checked-background-initial)}:host(:hover){background-color:var(--sinch-comp-table-color-row-default-background-hover)}:host([sticky]) ::slotted(sinch-table-head-cell){position:sticky;top:0;z-index:1;background-color:var(--sinch-comp-table-color-row-default-background-sticky)}:host(:last-child) ::slotted(sinch-table-cell){border-bottom:none}</style><slot></slot>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-table-row', class extends NectaryElement {