@nectary/components 0.40.0 → 0.41.1

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.
Files changed (74) hide show
  1. package/accordion-item/index.js +4 -0
  2. package/action-menu/index.js +11 -13
  3. package/action-menu-option/index.js +2 -1
  4. package/button/index.js +5 -1
  5. package/button/types.d.ts +1 -1
  6. package/checkbox/index.js +4 -0
  7. package/chip/index.js +18 -15
  8. package/chip/types.d.ts +3 -4
  9. package/color-menu/index.d.ts +1 -0
  10. package/color-menu/index.js +37 -51
  11. package/color-menu/types.d.ts +5 -6
  12. package/color-menu/utils.d.ts +1 -0
  13. package/color-menu/utils.js +15 -0
  14. package/color-swatch/index.js +8 -7
  15. package/color-swatch/types.d.ts +3 -4
  16. package/colors.json +14 -10
  17. package/date-picker/index.js +1 -1
  18. package/field/index.js +22 -5
  19. package/file-drop/index.js +1 -1
  20. package/file-picker/index.js +1 -1
  21. package/help-tooltip/index.js +10 -27
  22. package/icon-button/index.d.ts +1 -0
  23. package/icon-button/index.js +26 -15
  24. package/icon-button/types.d.ts +16 -2
  25. package/input/index.js +4 -0
  26. package/link/index.js +5 -1
  27. package/package.json +1 -1
  28. package/pagination/index.js +4 -0
  29. package/pop/index.d.ts +11 -0
  30. package/pop/index.js +429 -0
  31. package/pop/types.d.ts +35 -0
  32. package/pop/utils.d.ts +7 -0
  33. package/pop/utils.js +18 -0
  34. package/popover/index.d.ts +1 -0
  35. package/popover/index.js +91 -230
  36. package/popover/types.d.ts +8 -1
  37. package/popover/utils.d.ts +5 -0
  38. package/popover/utils.js +17 -1
  39. package/radio-option/index.js +4 -0
  40. package/segment-collapse/index.js +4 -0
  41. package/segmented-control-option/index.js +5 -1
  42. package/segmented-icon-control-option/index.js +5 -1
  43. package/select-button/index.js +6 -1
  44. package/select-menu/index.js +12 -13
  45. package/tabs-option/index.js +4 -0
  46. package/tag/index.js +13 -15
  47. package/tag/types.d.ts +3 -4
  48. package/textarea/index.js +4 -0
  49. package/theme.css +76 -10
  50. package/tile-control-option/index.js +5 -1
  51. package/time-picker/index.js +1 -1
  52. package/toggle/index.js +5 -1
  53. package/tooltip/index.d.ts +2 -0
  54. package/tooltip/index.js +160 -17
  55. package/tooltip/types.d.ts +13 -0
  56. package/tooltip/utils.d.ts +5 -0
  57. package/tooltip/utils.js +25 -1
  58. package/types.d.ts +0 -7
  59. package/utils/animation.d.ts +17 -0
  60. package/utils/animation.js +142 -0
  61. package/utils/colors.d.ts +4 -9
  62. package/utils/colors.js +4 -120
  63. package/utils/context.d.ts +15 -0
  64. package/utils/context.js +57 -0
  65. package/utils/index.d.ts +5 -9
  66. package/utils/index.js +49 -50
  67. package/dropdown-checkbox-option/index.d.ts +0 -11
  68. package/dropdown-checkbox-option/index.js +0 -74
  69. package/dropdown-checkbox-option/types.d.ts +0 -15
  70. package/dropdown-radio-option/index.d.ts +0 -11
  71. package/dropdown-radio-option/index.js +0 -74
  72. package/dropdown-radio-option/types.d.ts +0 -15
  73. package/dropdown-radio-option/types.js +0 -1
  74. /package/{dropdown-checkbox-option → pop}/types.js +0 -0
package/popover/index.js CHANGED
@@ -1,77 +1,63 @@
1
- import dialogPolyfill from 'dialog-polyfill';
2
- import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, getRect, isAttrTrue, updateLiteralAttribute, getReactEventHandler, updateBooleanAttribute, NectaryElement, throttleAnimationFrame, Context, getFirstSlotElement, isElementFocused } from '../utils';
3
- const templateHTML = '<style>:host{display:inline-block;vertical-align:middle;outline:0}#wrapper{position:relative}dialog{position:fixed;top:0;left: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}dialog+.backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background-color:transparent}dialog.fixed{position:fixed;top:50%;transform:translate(0,-50%)}._dialog_overlay{position:fixed;top:0;right:0;bottom:0;left:0}#target{display:flex;flex-direction:column}#content{position:relative;font:var(--sinch-font-body);color:var(--sinch-color-text-default);background-color:var(--sinch-color-snow-100);box-shadow:var(--sinch-elevation-level-2);border:1px solid var(--sinch-color-snow-500);border-radius:var(--sinch-shape-radius-m);outline:0;overflow:hidden}#target-open{display:flex;flex-direction:column;position:absolute;left:0;top:0}</style><div id="wrapper"><div id="target" aria-haspopup="dialog" aria-expanded="false"><slot name="target"></slot></div><dialog id="dialog"><div id="target-open"><slot name="target-open"></slot></div><div id="content"><slot name="content"></slot></div></dialog></div>';
4
- import { orientationValues } from './utils';
1
+ import '../pop';
2
+ import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, updateLiteralAttribute, updateBooleanAttribute, NectaryElement, updateAttribute, getReactEventHandler, isAttrTrue, setClass, rectOverlap } from '../utils';
3
+ import { dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils/context';
4
+ const templateHTML = '<style>:host{display:contents}#content-wrapper{position:relative;padding-top:4px}#content{font:var(--sinch-font-body);color:var(--sinch-color-text-default);background-color:var(--sinch-color-snow-100);border:1px solid var(--sinch-color-snow-500);border-radius:var(--sinch-shape-radius-m);box-shadow:var(--sinch-elevation-level-2);overflow:hidden}#tip{position:absolute;left:50%;top:13px;transform:translateX(-50%) rotate(180deg);transform-origin:top center;fill:var(--sinch-color-snow-100);stroke:var(--sinch-color-snow-500);stroke-linecap:square;pointer-events:none;display:none}:host([orientation^=top]) #tip{transform:translateX(-50%) rotate(0);top:calc(100% - 13px)}:host([tip]) #tip:not(.hidden){display:block}:host([tip]) #content{box-shadow:none}:host([tip]) #content-wrapper{padding-top:12px;filter:drop-shadow(var(--sinch-elevation-level-2))}:host([orientation^=top]) #content-wrapper{padding-top:0;padding-bottom:4px}:host([orientation^=top][tip]) #content-wrapper{padding-top:0;padding-bottom:12px}</style><sinch-pop id="pop" inset="4"><slot name="target" slot="target"></slot><div id="content-wrapper" slot="content"><div id="content"><slot name="content"></slot></div><svg id="tip" width="16" height="9" aria-hidden="true"><path d="m0 0 8 8 8 -8"/></svg></div></sinch-pop>';
5
+ import { assertOrientation, getPopOrientation, orientationValues } from './utils';
6
+ const TIP_SIZE = 16;
5
7
  const template = document.createElement('template');
6
8
  template.innerHTML = templateHTML;
7
- const POPOVER_OFFSET = 4;
8
9
  defineCustomElement('sinch-popover', class extends NectaryElement {
9
- #$target;
10
- #$dialog;
11
- #isConnected;
12
- #resizeThrottle;
13
- #originalOverflowValue = '';
14
- #$targetSlot;
15
- #$targetOpenSlot;
16
- #$contentSlot;
17
- #$targetOpenWrapper;
18
- #targetActiveElement = null;
19
- #controller = new AbortController();
20
- #keydownContext;
21
- #visibilityContext;
10
+ #$pop;
11
+ #$content;
12
+ #$tip;
13
+ #controller = null;
22
14
 
23
15
  constructor() {
24
16
  super();
25
17
  const shadowRoot = this.attachShadow();
26
18
  shadowRoot.appendChild(template.content.cloneNode(true));
27
- this.#$target = shadowRoot.querySelector('#target');
28
- this.#$dialog = shadowRoot.querySelector('#dialog');
29
- this.#$targetSlot = shadowRoot.querySelector('slot[name="target"]');
30
- this.#$targetOpenSlot = shadowRoot.querySelector('slot[name="target-open"]');
31
- this.#$contentSlot = shadowRoot.querySelector('slot[name="content"]');
32
- this.#$targetOpenWrapper = shadowRoot.querySelector('#target-open');
33
- this.#isConnected = false;
34
- this.#resizeThrottle = throttleAnimationFrame(this.#updateOrientation);
35
- this.#keydownContext = new Context(this.#$contentSlot, 'keydown');
36
- this.#visibilityContext = new Context(this.#$contentSlot, 'visibility');
37
- dialogPolyfill.registerDialog(this.#$dialog);
38
-
39
- dialogPolyfill.dm.handleFocus_ = function () {};
19
+ this.#$pop = shadowRoot.querySelector('#pop');
20
+ this.#$content = shadowRoot.querySelector('#content');
21
+ this.#$tip = shadowRoot.querySelector('#tip');
40
22
  }
41
23
 
42
24
  connectedCallback() {
25
+ this.#controller = new AbortController();
43
26
  const {
44
27
  signal
45
28
  } = this.#controller;
46
- this.setAttribute('role', 'dialog');
47
- this.#$dialog.addEventListener('cancel', this.#onCancel, {
29
+ this.addEventListener('-close', this.#onCloseReactHandler, {
48
30
  signal
49
31
  });
50
- this.#$dialog.addEventListener('mousedown', this.#onBackdropMouseDown, {
32
+ this.#$pop.addEventListener('-close', this.#onPopClose, {
51
33
  signal
52
34
  });
53
- this.addEventListener('-close', this.#onCloseReactHandler, {
35
+ this.#$content.addEventListener('-visibility', this.#onContextVisibility, {
54
36
  signal
55
37
  });
56
- this.#isConnected = true;
57
- this.#keydownContext.subscribe();
58
- this.#visibilityContext.subscribe();
59
-
60
- if (getBooleanAttribute(this, 'open')) {
61
- this.#onExpand();
62
- }
38
+ dispatchContextConnectEvent(this.#$content, 'visibility');
39
+ updateAttribute(this.#$pop, 'orientation', getPopOrientation(this.orientation));
63
40
  }
64
41
 
65
42
  disconnectedCallback() {
43
+ dispatchContextDisconnectEvent(this.#$content, 'visibility');
66
44
  this.#controller.abort();
67
- this.#onCollapse();
68
- this.#isConnected = false;
69
- this.#keydownContext.unsubscribe();
70
- this.#visibilityContext.unsubscribe();
45
+ }
46
+
47
+ #onPopClose = () => {
48
+ this.#dispatchCloseEvent();
49
+ };
50
+ #onCloseReactHandler = e => {
51
+ getReactEventHandler(this, 'onClose')?.();
52
+ getReactEventHandler(this, 'on-close')?.(e);
53
+ };
54
+
55
+ #dispatchCloseEvent() {
56
+ this.dispatchEvent(new CustomEvent('-close'));
71
57
  }
72
58
 
73
59
  static get observedAttributes() {
74
- return ['orientation', 'open'];
60
+ return ['orientation', 'open', 'modal', 'tip'];
75
61
  }
76
62
 
77
63
  set modal(isModal) {
@@ -90,6 +76,14 @@ defineCustomElement('sinch-popover', class extends NectaryElement {
90
76
  return getBooleanAttribute(this, 'open');
91
77
  }
92
78
 
79
+ set tip(hasTip) {
80
+ updateBooleanAttribute(this, 'tip', hasTip);
81
+ }
82
+
83
+ get tip() {
84
+ return getBooleanAttribute(this, 'tip');
85
+ }
86
+
93
87
  get orientation() {
94
88
  return getLiteralAttribute(this, orientationValues, 'orientation', 'bottom-left');
95
89
  }
@@ -98,8 +92,12 @@ defineCustomElement('sinch-popover', class extends NectaryElement {
98
92
  updateLiteralAttribute(this, orientationValues, 'orientation', value);
99
93
  }
100
94
 
95
+ get footprintRect() {
96
+ return this.#$pop.footprintRect;
97
+ }
98
+
101
99
  get popoverRect() {
102
- return getRect(this.#$dialog);
100
+ return this.#$pop.popoverRect;
103
101
  }
104
102
 
105
103
  attributeChangedCallback(name, oldVal, newVal) {
@@ -108,216 +106,79 @@ defineCustomElement('sinch-popover', class extends NectaryElement {
108
106
  }
109
107
 
110
108
  switch (name) {
111
- case 'open':
109
+ case 'orientation':
112
110
  {
113
- if (isAttrTrue(newVal)) {
114
- requestAnimationFrame(() => {
115
- this.#onExpand();
116
- });
117
- } else {
118
- this.#onCollapse();
111
+ assertOrientation(newVal);
112
+ updateAttribute(this.#$pop, 'orientation', getPopOrientation(newVal));
113
+
114
+ if (this.#isOpen()) {
115
+ this.#updateTipOrientation();
119
116
  }
120
117
 
121
- updateBooleanAttribute(this, 'open', isAttrTrue(newVal));
122
118
  break;
123
119
  }
124
120
 
125
- case 'orientation':
121
+ case 'tip':
126
122
  {
127
- if (this.#isOpen()) {
128
- this.#updateOrientation();
123
+ updateBooleanAttribute(this, 'tip', isAttrTrue(newVal));
124
+
125
+ if (newVal === '' && this.#isOpen()) {
126
+ this.#updateTipOrientation();
129
127
  }
130
128
 
131
129
  break;
132
130
  }
133
- }
134
- }
135
-
136
- #onExpand() {
137
- if (!this.#isConnected || this.#isOpen()) {
138
- return;
139
- }
140
131
 
141
- const isNonModal = !this.modal;
142
- this.#$targetSlot.addEventListener('blur', this.#captureActiveElement, true);
143
-
144
- if (isNonModal) {
145
- this.#$targetOpenSlot.addEventListener('blur', this.#captureActiveElement, true);
146
- this.#$targetOpenSlot.addEventListener('focus', this.#stopPropagation, true);
147
- const targetRect = this.#$target.getBoundingClientRect();
148
- const widthPx = `${targetRect.width}px`;
149
- const heightPx = `${targetRect.height}px`;
150
- this.#$target.style.width = widthPx;
151
- this.#$target.style.height = heightPx;
152
- this.#$targetOpenWrapper.style.width = widthPx;
153
- this.#$targetOpenWrapper.style.height = heightPx;
154
- getFirstSlotElement(this.#$targetSlot)?.setAttribute('slot', 'target-open');
155
- this.#$targetOpenSlot.addEventListener('keydown', this.#onTargetKeydown);
156
- }
132
+ case 'open':
133
+ {
134
+ const isOpen = isAttrTrue(newVal);
135
+ updateBooleanAttribute(this.#$pop, name, isOpen);
136
+ updateBooleanAttribute(this, name, isOpen);
137
+ break;
138
+ }
157
139
 
158
- this.#$dialog.showModal();
159
- this.#updateOrientation();
160
- this.#$target.setAttribute('aria-expanded', 'true');
161
- this.#$targetSlot.removeEventListener('blur', this.#captureActiveElement, true);
162
-
163
- if (isNonModal) {
164
- this.#$targetOpenSlot.removeEventListener('blur', this.#captureActiveElement, true);
165
- this.#$targetOpenSlot.removeEventListener('focus', this.#stopPropagation, true);
166
-
167
- if (this.#targetActiveElement !== null) {
168
- if (!isElementFocused(this.#targetActiveElement)) {
169
- requestAnimationFrame(() => {
170
- this.#$targetOpenSlot.addEventListener('focus', this.#stopPropagation, true);
171
- this.#targetActiveElement?.focus();
172
- this.#$targetOpenSlot.removeEventListener('focus', this.#stopPropagation, true);
173
- });
140
+ default:
141
+ {
142
+ updateAttribute(this.#$pop, name, newVal);
174
143
  }
175
- }
176
- } else {
177
- if (document.activeElement === document.body) {
178
- getFirstSlotElement(this.#$contentSlot)?.focus();
179
- }
180
144
  }
181
-
182
- this.#originalOverflowValue = document.body.style.overflow;
183
- document.body.style.overflow = 'hidden';
184
- window.addEventListener('resize', this.#onResize);
185
- this.#dispatchContentVisibility(true);
186
145
  }
187
146
 
188
- #onCollapse() {
189
- if (!this.#isOpen()) {
190
- return;
191
- }
192
-
193
- this.#dispatchContentVisibility(false);
194
- this.#$targetSlot.addEventListener('focus', this.#stopPropagation, true);
195
- getFirstSlotElement(this.#$targetOpenSlot)?.setAttribute('slot', 'target');
196
- this.#$target.style.width = 'unset';
197
- this.#$target.style.height = 'unset';
198
- this.#$dialog.close();
199
- this.#$target.setAttribute('aria-expanded', 'false');
200
-
201
- if (this.#targetActiveElement !== null) {
202
- if (!isElementFocused(this.#targetActiveElement)) {
203
- requestAnimationFrame(() => {
204
- this.#$targetSlot.addEventListener('focus', this.#stopPropagation, true);
205
- this.#targetActiveElement.focus();
206
- this.#targetActiveElement = null;
207
- this.#$targetSlot.removeEventListener('focus', this.#stopPropagation, true);
208
- });
209
- } else {
210
- this.#targetActiveElement = null;
211
- }
147
+ #onContextVisibility = e => {
148
+ if (e.detail) {
149
+ this.#updateTipOrientation();
150
+ } else {
151
+ this.#resetTipOrientation();
212
152
  }
213
-
214
- this.#$targetSlot.removeEventListener('focus', this.#stopPropagation, true);
215
- this.#$targetOpenSlot.removeEventListener('keydown', this.#onTargetKeydown);
216
- document.body.style.overflow = this.#originalOverflowValue;
217
- this.#resizeThrottle.cancel();
218
- window.removeEventListener('resize', this.#onResize);
219
- }
220
-
221
- #isOpen() {
222
- return getBooleanAttribute(this.#$dialog, 'open');
223
- }
224
-
225
- #onResize = () => {
226
- this.#resizeThrottle.fn();
227
153
  };
228
- #updateOrientation = () => {
229
- this.#$dialog.style.width = 'max-content';
230
- const targetRect = this.#$target.getBoundingClientRect();
231
- const modalRect = this.#$dialog.getBoundingClientRect();
232
- let leftPos = 0;
233
- let topPos = 0;
234
- const orient = this.orientation;
235
- const shouldSetWidthToTarget = orient === 'top' || orient === 'bottom';
236
- const resultWidth = shouldSetWidthToTarget ? targetRect.width : modalRect.width;
237
-
238
- if (orient === 'bottom-right' || orient === 'top-right' || orient === 'top' || orient === 'bottom') {
239
- leftPos = Math.max(POPOVER_OFFSET, Math.min(targetRect.x, window.innerWidth - resultWidth - POPOVER_OFFSET));
240
- }
241
-
242
- if (orient === 'bottom-left' || orient === 'top-left') {
243
- leftPos = Math.max(POPOVER_OFFSET, targetRect.right - resultWidth);
244
- }
245
-
246
- if (orient === 'bottom-left' || orient === 'bottom-right' || orient === 'bottom') {
247
- topPos = Math.max(POPOVER_OFFSET, Math.min(targetRect.bottom + POPOVER_OFFSET, window.innerHeight - modalRect.height - POPOVER_OFFSET));
248
- }
249
154
 
250
- if (orient === 'top-left' || orient === 'top-right' || orient === 'top') {
251
- topPos = Math.max(POPOVER_OFFSET, targetRect.top - POPOVER_OFFSET - modalRect.height);
252
- }
155
+ #resetTipOrientation() {
156
+ this.#$tip.style.top = '';
157
+ this.#$tip.style.left = '';
158
+ }
253
159
 
254
- this.#$dialog.style.left = `${leftPos}px`;
255
- this.#$dialog.style.top = `${topPos}px`;
256
- this.#$dialog.style.width = `${resultWidth}px`;
160
+ #updateTipOrientation = () => {
161
+ const orientation = this.orientation;
162
+ const targetRect = this.#$pop.footprintRect;
163
+ const contentRect = this.#$content.getBoundingClientRect();
164
+ const diffX = targetRect.x - contentRect.x;
165
+ let desiredX = diffX + targetRect.width / 2;
257
166
 
258
- if (!this.modal) {
259
- const targetLeftPos = targetRect.left - leftPos;
260
- const targetTopPos = targetRect.top - topPos;
261
- this.#$targetOpenWrapper.style.left = `${targetLeftPos}px`;
262
- this.#$targetOpenWrapper.style.top = `${targetTopPos}px`;
263
- }
264
- };
265
- #onBackdropMouseDown = e => {
266
- if (e.target !== this.#$dialog) {
267
- return;
167
+ if (orientation === 'bottom-left' || orientation === 'top-left') {
168
+ desiredX = Math.max(desiredX, contentRect.width * 0.75);
268
169
  }
269
170
 
270
- const rect = this.popoverRect;
271
- const isInside = e.x >= rect.x && e.x < rect.x + rect.width && e.y >= rect.y && e.y < rect.y + rect.height;
272
-
273
- if (!isInside) {
274
- this.#dispatchCloseEvent();
171
+ if (orientation === 'bottom-right' || orientation === 'top-right') {
172
+ desiredX = Math.min(desiredX, contentRect.width * 0.25);
275
173
  }
276
- };
277
- #onCancel = e => {
278
- e.preventDefault();
279
- this.#dispatchCloseEvent();
280
- };
281
- #onCloseReactHandler = e => {
282
- getReactEventHandler(this, 'onClose')?.();
283
- getReactEventHandler(this, 'on-close')?.(e);
284
- };
285
-
286
- #dispatchCloseEvent() {
287
- this.dispatchEvent(new CustomEvent('-close'));
288
- }
289
174
 
290
- #captureActiveElement = e => {
291
- e.stopPropagation();
292
- this.#targetActiveElement = e.target;
293
- };
294
- #stopPropagation = e => {
295
- e.stopPropagation();
175
+ const xPos = Math.max(TIP_SIZE, Math.min(desiredX, contentRect.width - TIP_SIZE));
176
+ this.#$tip.style.left = `${xPos}px`;
177
+ setClass(this.#$tip, 'hidden', rectOverlap(targetRect, contentRect));
296
178
  };
297
179
 
298
- #dispatchContentVisibility(isVisible) {
299
- for (const $el of this.#visibilityContext.elements) {
300
- $el.dispatchEvent(new CustomEvent('-visibility', {
301
- detail: isVisible
302
- }));
303
- }
180
+ #isOpen() {
181
+ return this.#$pop.open;
304
182
  }
305
183
 
306
- #onTargetKeydown = e => {
307
- for (const $el of this.#keydownContext.elements) {
308
- let isPrevented = false;
309
- $el.dispatchEvent(new CustomEvent('-keydown', {
310
- detail: {
311
- code: e.code,
312
- preventDefault: () => {
313
- isPrevented = true;
314
- }
315
- }
316
- }));
317
-
318
- if (isPrevented) {
319
- e.preventDefault();
320
- }
321
- }
322
- };
323
184
  });
@@ -7,6 +7,9 @@ export declare type TSinchPopoverElement = HTMLElement & {
7
7
  orientation: TSinchPopoverOrientation;
8
8
  /** Modal/non-modal mode */
9
9
  modal: boolean;
10
+ /** Show tip */
11
+ tip: boolean;
12
+ readonly footprintRect: TRect;
10
13
  readonly popoverRect: TRect;
11
14
  /** Close event */
12
15
  addEventListener(type: '-close', listener: (e: CustomEvent<void>) => void): void;
@@ -15,7 +18,9 @@ export declare type TSinchPopoverElement = HTMLElement & {
15
18
  /** Orientation, where it *points to* from origin */
16
19
  setAttribute(name: 'orientation', value: TSinchPopoverOrientation): void;
17
20
  /** Modal/non-modal mode */
18
- setAttribute(name: 'modal', value: boolean): void;
21
+ setAttribute(name: 'modal', value: ''): void;
22
+ /** Show tip */
23
+ setAttribute(name: 'tip', value: ''): void;
19
24
  };
20
25
  export declare type TSinchPopoverReact = TSinchElementReact<TSinchPopoverElement> & {
21
26
  /** Open/close state */
@@ -24,6 +29,8 @@ export declare type TSinchPopoverReact = TSinchElementReact<TSinchPopoverElement
24
29
  orientation?: TSinchPopoverOrientation;
25
30
  /** Modal/non-modal mode */
26
31
  modal?: boolean;
32
+ /** Show tip */
33
+ tip?: boolean;
27
34
  /** Label that is used for a11y */
28
35
  'aria-label': string;
29
36
  /** Close event handler */
@@ -1,2 +1,7 @@
1
+ import type { TSinchPopOrientation } from '../pop/types';
1
2
  import type { TSinchPopoverOrientation } from './types';
2
3
  export declare const orientationValues: readonly TSinchPopoverOrientation[];
4
+ export declare const getPopOrientation: (orientation: TSinchPopoverOrientation) => TSinchPopOrientation;
5
+ declare type TAssertOrientation = (value: string | null) => asserts value is TSinchPopoverOrientation;
6
+ export declare const assertOrientation: TAssertOrientation;
7
+ export {};
package/popover/utils.js CHANGED
@@ -1 +1,17 @@
1
- export const orientationValues = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'bottom', 'top'];
1
+ export const orientationValues = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'bottom', 'top'];
2
+ export const getPopOrientation = orientation => {
3
+ if (orientation === 'top') {
4
+ return 'top-stretch';
5
+ }
6
+
7
+ if (orientation === 'bottom') {
8
+ return 'bottom-stretch';
9
+ }
10
+
11
+ return orientation;
12
+ };
13
+ export const assertOrientation = value => {
14
+ if (value === null || !orientationValues.includes(value)) {
15
+ throw new Error(`sinch-popover: invalid orientation attribute: ${value}`);
16
+ }
17
+ };
@@ -90,6 +90,10 @@ defineCustomElement('sinch-radio-option', class extends NectaryElement {
90
90
  }
91
91
  }
92
92
 
93
+ get focusable() {
94
+ return true;
95
+ }
96
+
93
97
  focus() {
94
98
  this.#$input.focus();
95
99
  }
@@ -56,6 +56,10 @@ defineCustomElement('sinch-segment-collapse', class extends NectaryElement {
56
56
  return getBooleanAttribute(this, 'value');
57
57
  }
58
58
 
59
+ get focusable() {
60
+ return true;
61
+ }
62
+
59
63
  focus() {
60
64
  this.#$button.focus();
61
65
  }
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;position:relative;flex-direction:row;align-items:center;gap:12px;width:100%;height:32px;padding:0 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:16px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}#content{font:var(--sinch-font-title-s);flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:0;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-3px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-aqua-400);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}</style><div id="wrapper"><slot name="icon"></slot><label for="button" id="content"></label><button id="button"></button></div>';
2
+ const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;position:relative;flex-direction:row;align-items:center;gap:12px;width:100%;height:32px;padding:0 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:16px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}#content{font:var(--sinch-font-title-s);flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:0;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-3px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}</style><div id="wrapper"><slot name="icon"></slot><label for="button" id="content"></label><button id="button"></button></div>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-segmented-control-option', class extends NectaryElement {
@@ -85,6 +85,10 @@ defineCustomElement('sinch-segmented-control-option', class extends NectaryEleme
85
85
  }
86
86
  }
87
87
 
88
+ get focusable() {
89
+ return true;
90
+ }
91
+
88
92
  focus() {
89
93
  this.#$button.focus();
90
94
  }
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:block;outline:0}#wrapper{position:relative;width:56px;height:32px;padding:3px 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:24px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:-1px;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-4px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-aqua-400);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}::slotted(*){display:block}</style><div id="wrapper"><slot name="icon"></slot><button id="button"></button></div>';
2
+ const templateHTML = '<style>:host{display:block;outline:0}#wrapper{position:relative;width:56px;height:32px;padding:3px 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:24px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:-1px;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-4px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}::slotted(*){display:block}</style><div id="wrapper"><slot name="icon"></slot><button id="button"></button></div>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-segmented-icon-control-option', class extends NectaryElement {
@@ -65,6 +65,10 @@ defineCustomElement('sinch-segmented-icon-control-option', class extends Nectary
65
65
  }
66
66
  }
67
67
 
68
+ get focusable() {
69
+ return true;
70
+ }
71
+
68
72
  focus() {
69
73
  this.#$button.focus();
70
74
  }
@@ -7,7 +7,7 @@ template.innerHTML = templateHTML;
7
7
  defineCustomElement('sinch-select-button', class extends NectaryElement {
8
8
  #$button;
9
9
  #$text;
10
- #controller = new AbortController();
10
+ #controller = null;
11
11
 
12
12
  constructor() {
13
13
  super();
@@ -18,6 +18,7 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
18
18
  }
19
19
 
20
20
  connectedCallback() {
21
+ this.#controller = new AbortController();
21
22
  const {
22
23
  signal
23
24
  } = this.#controller;
@@ -124,6 +125,10 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
124
125
  }
125
126
  }
126
127
 
128
+ get focusable() {
129
+ return true;
130
+ }
131
+
127
132
  focus() {
128
133
  this.#$button.focus();
129
134
  }
@@ -1,4 +1,5 @@
1
1
  import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, getCsvSet, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
2
+ import { dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils/context';
2
3
  const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}</style><div id="listbox" role="presentation"><slot></slot></div>';
3
4
  const ITEM_HEIGHT = 40;
4
5
  const template = document.createElement('template');
@@ -6,7 +7,7 @@ template.innerHTML = templateHTML;
6
7
  defineCustomElement('sinch-select-menu', class extends NectaryElement {
7
8
  #$optionSlot;
8
9
  #$listbox;
9
- #controller = new AbortController();
10
+ #controller = null;
10
11
 
11
12
  constructor() {
12
13
  super();
@@ -17,6 +18,7 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
17
18
  }
18
19
 
19
20
  connectedCallback() {
21
+ this.#controller = new AbortController();
20
22
  const {
21
23
  signal
22
24
  } = this.#controller;
@@ -46,22 +48,14 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
46
48
  this.addEventListener('-visibility', this.#onContextVisibility, {
47
49
  signal
48
50
  });
49
- this.dispatchEvent(new CustomEvent('-context-connect-keydown', {
50
- bubbles: true
51
- }));
52
- this.dispatchEvent(new CustomEvent('-context-connect-visibility', {
53
- bubbles: true
54
- }));
51
+ dispatchContextConnectEvent(this, 'keydown');
52
+ dispatchContextConnectEvent(this, 'visibility');
55
53
  }
56
54
 
57
55
  disconnectedCallback() {
56
+ dispatchContextDisconnectEvent(this, 'keydown');
57
+ dispatchContextDisconnectEvent(this, 'visibility');
58
58
  this.#controller.abort();
59
- this.dispatchEvent(new CustomEvent('-context-disconnect-keydown', {
60
- bubbles: true
61
- }));
62
- this.dispatchEvent(new CustomEvent('-context-disconnect-visibility', {
63
- bubbles: true
64
- }));
65
59
  }
66
60
 
67
61
  static get observedAttributes() {
@@ -343,4 +337,9 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
343
337
  #onChangeReactHandler = e => {
344
338
  getReactEventHandler(this, 'on-change')?.(e);
345
339
  };
340
+
341
+ get focusable() {
342
+ return true;
343
+ }
344
+
346
345
  });
@@ -83,6 +83,10 @@ defineCustomElement('sinch-tabs-option', class extends NectaryElement {
83
83
  }
84
84
  }
85
85
 
86
+ get focusable() {
87
+ return true;
88
+ }
89
+
86
90
  focus() {
87
91
  this.#$button.focus();
88
92
  }