@nectary/components 0.39.0 → 0.41.0

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 (142) hide show
  1. package/accordion/index.js +47 -84
  2. package/accordion-item/index.js +30 -53
  3. package/action-menu/index.d.ts +0 -1
  4. package/action-menu/index.js +177 -223
  5. package/action-menu/types.d.ts +8 -18
  6. package/action-menu-option/index.d.ts +1 -0
  7. package/action-menu-option/index.js +52 -62
  8. package/action-menu-option/types.d.ts +9 -0
  9. package/action-menu-option/utils.d.ts +2 -0
  10. package/action-menu-option/utils.js +3 -0
  11. package/alert/index.js +6 -20
  12. package/avatar/index.js +12 -31
  13. package/avatar-badge/index.js +8 -22
  14. package/avatar-status/index.js +1 -1
  15. package/button/index.js +46 -92
  16. package/button/types.d.ts +1 -1
  17. package/card/index.js +21 -59
  18. package/chat-avatar/index.js +8 -22
  19. package/chat-block/index.js +29 -69
  20. package/chat-bubble/index.js +6 -20
  21. package/checkbox/index.js +59 -107
  22. package/chip/index.d.ts +13 -0
  23. package/chip/index.js +140 -0
  24. package/chip/types.d.ts +37 -0
  25. package/color-menu/index.d.ts +14 -0
  26. package/color-menu/index.js +450 -0
  27. package/color-menu/types.d.ts +36 -0
  28. package/color-menu/utils.d.ts +1 -0
  29. package/color-menu/utils.js +15 -0
  30. package/color-swatch/index.d.ts +13 -0
  31. package/color-swatch/index.js +60 -0
  32. package/color-swatch/types.d.ts +11 -0
  33. package/colors.json +61 -49
  34. package/date-picker/index.js +162 -293
  35. package/dialog/index.js +70 -142
  36. package/field/index.js +44 -65
  37. package/file-drop/index.js +123 -200
  38. package/file-picker/index.d.ts +0 -1
  39. package/file-picker/index.js +55 -108
  40. package/file-status/index.js +15 -39
  41. package/help-tooltip/index.js +11 -28
  42. package/horizontal-stepper/index.js +33 -59
  43. package/horizontal-stepper-item/index.js +13 -37
  44. package/icon-button/index.d.ts +1 -0
  45. package/icon-button/index.js +51 -85
  46. package/icon-button/types.d.ts +16 -2
  47. package/icons-channel/notify/index.d.ts +11 -0
  48. package/icons-channel/notify/index.js +4 -0
  49. package/illustrations/create-illustration-class.js +1 -1
  50. package/inline-alert/index.js +29 -81
  51. package/input/index.js +117 -222
  52. package/link/index.js +51 -97
  53. package/list-item/index.js +1 -1
  54. package/package.json +12 -14
  55. package/pagination/index.js +113 -163
  56. package/pop/index.d.ts +11 -0
  57. package/pop/index.js +391 -0
  58. package/pop/types.d.ts +35 -0
  59. package/pop/utils.d.ts +7 -0
  60. package/pop/utils.js +18 -0
  61. package/popover/index.d.ts +1 -0
  62. package/popover/index.js +105 -314
  63. package/popover/types.d.ts +8 -1
  64. package/popover/utils.d.ts +5 -0
  65. package/popover/utils.js +17 -1
  66. package/progress/index.js +9 -28
  67. package/radio/index.js +103 -169
  68. package/radio-option/index.js +28 -48
  69. package/segment/index.js +49 -130
  70. package/segment-collapse/index.js +28 -49
  71. package/segmented-control/index.js +36 -73
  72. package/segmented-control-option/index.js +45 -87
  73. package/segmented-icon-control/index.js +47 -84
  74. package/segmented-icon-control-option/index.js +42 -79
  75. package/select-button/index.d.ts +13 -0
  76. package/select-button/index.js +158 -0
  77. package/select-button/types.d.ts +43 -0
  78. package/select-menu/index.d.ts +11 -0
  79. package/select-menu/index.js +345 -0
  80. package/select-menu/types.d.ts +29 -0
  81. package/{dropdown-text-option → select-menu-option}/index.d.ts +5 -7
  82. package/select-menu-option/index.js +76 -0
  83. package/{select-option → select-menu-option}/types.d.ts +8 -9
  84. package/stop-events/index.js +7 -20
  85. package/table-head-cell/index.js +7 -21
  86. package/tabs/index.js +103 -165
  87. package/tabs-option/index.js +24 -44
  88. package/tag/index.d.ts +0 -1
  89. package/tag/index.js +35 -38
  90. package/tag/types.d.ts +12 -7
  91. package/textarea/index.js +96 -167
  92. package/theme.css +146 -49
  93. package/tile-control/index.js +55 -96
  94. package/tile-control-option/index.js +45 -87
  95. package/time-picker/index.js +216 -368
  96. package/title/index.js +6 -20
  97. package/toast/index.js +32 -70
  98. package/toast-manager/index.js +141 -217
  99. package/toggle/index.js +59 -107
  100. package/tooltip/index.d.ts +2 -0
  101. package/tooltip/index.js +160 -17
  102. package/tooltip/types.d.ts +13 -0
  103. package/tooltip/utils.d.ts +5 -0
  104. package/tooltip/utils.js +25 -1
  105. package/utils/animation.d.ts +17 -0
  106. package/utils/animation.js +142 -0
  107. package/utils/colors.d.ts +5 -0
  108. package/utils/colors.js +5 -0
  109. package/utils/context.d.ts +15 -0
  110. package/utils/context.js +57 -0
  111. package/{utils.d.ts → utils/index.d.ts} +15 -11
  112. package/{utils.js → utils/index.js} +104 -48
  113. package/vertical-stepper/index.js +29 -50
  114. package/vertical-stepper-item/index.js +13 -37
  115. package/dropdown/index.d.ts +0 -12
  116. package/dropdown/index.js +0 -415
  117. package/dropdown/types.d.ts +0 -32
  118. package/dropdown-checkbox-option/index.d.ts +0 -11
  119. package/dropdown-checkbox-option/index.js +0 -88
  120. package/dropdown-checkbox-option/types.d.ts +0 -15
  121. package/dropdown-radio-option/index.d.ts +0 -11
  122. package/dropdown-radio-option/index.js +0 -88
  123. package/dropdown-radio-option/types.d.ts +0 -15
  124. package/dropdown-text-option/index.js +0 -104
  125. package/dropdown-text-option/types.d.ts +0 -16
  126. package/select/index.d.ts +0 -13
  127. package/select/index.js +0 -316
  128. package/select/types.d.ts +0 -53
  129. package/select-option/index.d.ts +0 -11
  130. package/select-option/index.js +0 -8
  131. package/tag/utils.d.ts +0 -5
  132. package/tag/utils.js +0 -6
  133. package/tag-close/index.d.ts +0 -12
  134. package/tag-close/index.js +0 -42
  135. package/tag-close/types.d.ts +0 -5
  136. /package/{dropdown-checkbox-option → chip}/types.js +0 -0
  137. /package/{dropdown-radio-option → color-menu}/types.js +0 -0
  138. /package/{dropdown-text-option → color-swatch}/types.js +0 -0
  139. /package/{dropdown → pop}/types.js +0 -0
  140. /package/{select-option → select-button}/types.js +0 -0
  141. /package/{tag-close → select-menu}/types.js +0 -0
  142. /package/{select → select-menu-option}/types.js +0 -0
package/toggle/index.js CHANGED
@@ -1,115 +1,36 @@
1
- import _classPrivateFieldGet from '@babel/runtime/helpers/classPrivateFieldGet';
2
- import _classPrivateFieldSet from '@babel/runtime/helpers/classPrivateFieldSet';
3
-
4
- var _$input, _$label, _onInput, _onCheckboxFocus, _onCheckboxBlur, _onChangeReactHandler, _onFocusReactHandler, _onBlurReactHandler;
5
-
6
- function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
7
-
8
- function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
9
-
10
1
  import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
11
- const templateHTML = '<style>:host{display:inline-block;vertical-align:middle}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;box-sizing:border-box;width:100%;height:32px}:host([small]:not([small=false])) #wrapper{font:var(--sinch-font-small-text)}#input{all:initial;display:block;position:absolute;left:0;top:6px;width:40px;height:20px;cursor:pointer;pointer-events:initial}#input:disabled{cursor:initial}:host([small]:not([small=false])) #input{width:32px;height:16px;top:8px}#input:focus-visible::after{position:absolute;content:"";left:-4px;top:-4px;right:-4px;bottom:-4px;border:2px solid var(--sinch-color-aqua-400);border-radius:18px;pointer-events:none}:host([small]:not([small=false])) #input:focus-visible::after{border-radius:14px}@supports not selector(:focus-visible){#input:focus::after{position:absolute;content:"";left:-4px;top:-4px;right:-4px;bottom:-4px;border:2px solid var(--sinch-color-aqua-400);border-radius:18px;pointer-events:none}:host([small]:not([small=false])) #input:focus::after{border-radius:14px}}#knob-container{position:relative;box-sizing:border-box;width:40px;height:20px;border-radius:10px;pointer-events:none;padding:2px;background-color:var(--sinch-color-stormy-100);overflow:hidden}:host([small]:not([small=false])) #knob-container{width:32px;height:16px;border-radius:8px}#input:checked~#knob-container{background-color:var(--sinch-color-tropical-500)}#knob{position:relative;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:var(--sinch-color-snow-100);box-shadow:var(--sinch-elevation-level-1);transform:translateX(0);transition:transform .1s ease-in-out}:host([small]:not([small=false])) #knob{width:12px;height:12px}#input:checked~#knob-container>#knob{transform:translateX(20px)}:host([small]:not([small=false])) #input:checked~#knob-container>#knob{transform:translateX(16px)}#knob::after,#knob::before{font:var(--sinch-font-body);color:var(--sinch-color-snow-100);text-transform:uppercase;font-size:8px;line-height:16px;display:none;position:absolute;top:0;padding:0 3px}#knob::before{content:"on";right:100%}#knob::after{content:"off";left:100%}:host([labeled]:not([labeled=false]):is(:not([small]),[small=false])) #knob::after,:host([labeled]:not([labeled=false]):is(:not([small]),[small=false])) #knob::before{display:block}@media (prefers-reduced-motion){#knob{transition:none}}#label{flex:1;align-self:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding-left:8px;font:var(--sinch-font-body);color:var(--sinch-color-text-default)}#label:empty{display:none}#input:disabled~#label{color:var(--sinch-color-stormy-200)}#input:disabled~#knob-container{background-color:var(--sinch-color-snow-700)}#input:checked:disabled~#knob-container{background-color:var(--sinch-color-tropical-100)}</style><div id="wrapper"><input id="input" type="checkbox"><div id="knob-container"><div id="knob"></div></div><label id="label" for="input"></label></div>';
2
+ const templateHTML = '<style>:host{display:inline-block;vertical-align:middle}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;box-sizing:border-box;width:100%;height:32px}:host([small]:not([small=false])) #wrapper{font:var(--sinch-font-small-text)}#input{all:initial;display:block;position:absolute;left:0;top:6px;width:40px;height:20px;cursor:pointer;pointer-events:initial}#input:disabled{cursor:initial}:host([small]:not([small=false])) #input{width:32px;height:16px;top:8px}#input:focus-visible::after{position:absolute;content:"";left:-4px;top:-4px;right:-4px;bottom:-4px;border:2px solid var(--sinch-color-border-focus);border-radius:18px;pointer-events:none}:host([small]:not([small=false])) #input:focus-visible::after{border-radius:14px}@supports not selector(:focus-visible){#input:focus::after{position:absolute;content:"";left:-4px;top:-4px;right:-4px;bottom:-4px;border:2px solid var(--sinch-color-border-focus);border-radius:18px;pointer-events:none}:host([small]:not([small=false])) #input:focus::after{border-radius:14px}}#knob-container{position:relative;box-sizing:border-box;width:40px;height:20px;border-radius:10px;pointer-events:none;padding:2px;background-color:var(--sinch-color-stormy-100);overflow:hidden}:host([small]:not([small=false])) #knob-container{width:32px;height:16px;border-radius:8px}#input:checked~#knob-container{background-color:var(--sinch-color-tropical-500)}#knob{position:relative;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:var(--sinch-color-snow-100);box-shadow:var(--sinch-elevation-level-1);transform:translateX(0);transition:transform .1s ease-in-out}:host([small]:not([small=false])) #knob{width:12px;height:12px}#input:checked~#knob-container>#knob{transform:translateX(20px)}:host([small]:not([small=false])) #input:checked~#knob-container>#knob{transform:translateX(16px)}#knob::after,#knob::before{font:var(--sinch-font-body);color:var(--sinch-color-snow-100);text-transform:uppercase;font-size:8px;line-height:16px;display:none;position:absolute;top:0;padding:0 3px}#knob::before{content:"on";right:100%}#knob::after{content:"off";left:100%}:host([labeled]:not([labeled=false]):is(:not([small]),[small=false])) #knob::after,:host([labeled]:not([labeled=false]):is(:not([small]),[small=false])) #knob::before{display:block}@media (prefers-reduced-motion){#knob{transition:none}}#label{flex:1;align-self:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding-left:8px;font:var(--sinch-font-body);color:var(--sinch-color-text-default)}#label:empty{display:none}#input:disabled~#label{color:var(--sinch-color-stormy-200)}#input:disabled~#knob-container{background-color:var(--sinch-color-snow-700)}#input:checked:disabled~#knob-container{background-color:var(--sinch-color-tropical-100)}</style><div id="wrapper"><input id="input" type="checkbox"><div id="knob-container"><div id="knob"></div></div><label id="label" for="input"></label></div>';
12
3
  const template = document.createElement('template');
13
4
  template.innerHTML = templateHTML;
14
- defineCustomElement('sinch-toggle', (_$input = new WeakMap(), _$label = new WeakMap(), _onInput = new WeakMap(), _onCheckboxFocus = new WeakMap(), _onCheckboxBlur = new WeakMap(), _onChangeReactHandler = new WeakMap(), _onFocusReactHandler = new WeakMap(), _onBlurReactHandler = new WeakMap(), class extends NectaryElement {
5
+ defineCustomElement('sinch-toggle', class extends NectaryElement {
6
+ #$input;
7
+ #$label;
8
+
15
9
  constructor() {
16
10
  super();
17
-
18
- _classPrivateFieldInitSpec(this, _$input, {
19
- writable: true,
20
- value: void 0
21
- });
22
-
23
- _classPrivateFieldInitSpec(this, _$label, {
24
- writable: true,
25
- value: void 0
26
- });
27
-
28
- _classPrivateFieldInitSpec(this, _onInput, {
29
- writable: true,
30
- value: e => {
31
- e.stopPropagation();
32
-
33
- const isChecked = _classPrivateFieldGet(this, _$input).checked;
34
-
35
- _classPrivateFieldGet(this, _$input).checked = this.checked;
36
- this.dispatchEvent(new CustomEvent('change', {
37
- detail: isChecked,
38
- bubbles: true
39
- }));
40
- this.dispatchEvent(new CustomEvent('-change', {
41
- detail: isChecked
42
- }));
43
- }
44
- });
45
-
46
- _classPrivateFieldInitSpec(this, _onCheckboxFocus, {
47
- writable: true,
48
- value: () => {
49
- this.dispatchEvent(new CustomEvent('-focus'));
50
- }
51
- });
52
-
53
- _classPrivateFieldInitSpec(this, _onCheckboxBlur, {
54
- writable: true,
55
- value: () => {
56
- this.dispatchEvent(new CustomEvent('-blur'));
57
- }
58
- });
59
-
60
- _classPrivateFieldInitSpec(this, _onChangeReactHandler, {
61
- writable: true,
62
- value: e => {
63
- getReactEventHandler(this, 'on-change')?.(e);
64
- }
65
- });
66
-
67
- _classPrivateFieldInitSpec(this, _onFocusReactHandler, {
68
- writable: true,
69
- value: e => {
70
- getReactEventHandler(this, 'on-focus')?.(e);
71
- }
72
- });
73
-
74
- _classPrivateFieldInitSpec(this, _onBlurReactHandler, {
75
- writable: true,
76
- value: e => {
77
- getReactEventHandler(this, 'on-blur')?.(e);
78
- }
79
- });
80
-
81
11
  const shadowRoot = this.attachShadow();
82
12
  shadowRoot.appendChild(template.content.cloneNode(true));
83
-
84
- _classPrivateFieldSet(this, _$input, shadowRoot.querySelector('#input'));
85
-
86
- _classPrivateFieldSet(this, _$label, shadowRoot.querySelector('#label'));
13
+ this.#$input = shadowRoot.querySelector('#input');
14
+ this.#$label = shadowRoot.querySelector('#label');
87
15
  }
88
16
 
89
17
  connectedCallback() {
90
18
  this.setAttribute('role', 'checkbox');
91
-
92
- _classPrivateFieldGet(this, _$input).addEventListener('input', _classPrivateFieldGet(this, _onInput));
93
-
94
- _classPrivateFieldGet(this, _$input).addEventListener('focus', _classPrivateFieldGet(this, _onCheckboxFocus));
95
-
96
- _classPrivateFieldGet(this, _$input).addEventListener('blur', _classPrivateFieldGet(this, _onCheckboxBlur));
97
-
98
- this.addEventListener('-change', _classPrivateFieldGet(this, _onChangeReactHandler));
99
- this.addEventListener('-focus', _classPrivateFieldGet(this, _onFocusReactHandler));
100
- this.addEventListener('-blur', _classPrivateFieldGet(this, _onBlurReactHandler));
19
+ this.#$input.addEventListener('input', this.#onInput);
20
+ this.#$input.addEventListener('focus', this.#onCheckboxFocus);
21
+ this.#$input.addEventListener('blur', this.#onCheckboxBlur);
22
+ this.addEventListener('-change', this.#onChangeReactHandler);
23
+ this.addEventListener('-focus', this.#onFocusReactHandler);
24
+ this.addEventListener('-blur', this.#onBlurReactHandler);
101
25
  }
102
26
 
103
27
  disconnectedCallback() {
104
- _classPrivateFieldGet(this, _$input).removeEventListener('input', _classPrivateFieldGet(this, _onInput));
105
-
106
- _classPrivateFieldGet(this, _$input).removeEventListener('focus', _classPrivateFieldGet(this, _onCheckboxFocus));
107
-
108
- _classPrivateFieldGet(this, _$input).removeEventListener('blur', _classPrivateFieldGet(this, _onCheckboxBlur));
109
-
110
- this.removeEventListener('-change', _classPrivateFieldGet(this, _onChangeReactHandler));
111
- this.removeEventListener('-focus', _classPrivateFieldGet(this, _onFocusReactHandler));
112
- this.removeEventListener('-blur', _classPrivateFieldGet(this, _onBlurReactHandler));
28
+ this.#$input.removeEventListener('input', this.#onInput);
29
+ this.#$input.removeEventListener('focus', this.#onCheckboxFocus);
30
+ this.#$input.removeEventListener('blur', this.#onCheckboxBlur);
31
+ this.removeEventListener('-change', this.#onChangeReactHandler);
32
+ this.removeEventListener('-focus', this.#onFocusReactHandler);
33
+ this.removeEventListener('-blur', this.#onBlurReactHandler);
113
34
  }
114
35
 
115
36
  static get observedAttributes() {
@@ -161,21 +82,21 @@ defineCustomElement('sinch-toggle', (_$input = new WeakMap(), _$label = new Weak
161
82
  }
162
83
 
163
84
  get text() {
164
- return getAttribute(this, 'text', null);
85
+ return getAttribute(this, 'text');
165
86
  }
166
87
 
167
88
  attributeChangedCallback(name, _, newVal) {
168
89
  switch (name) {
169
90
  case 'text':
170
91
  {
171
- _classPrivateFieldGet(this, _$label).textContent = newVal;
92
+ this.#$label.textContent = newVal;
172
93
  break;
173
94
  }
174
95
 
175
96
  case 'checked':
176
97
  {
177
98
  const isChecked = isAttrTrue(newVal);
178
- _classPrivateFieldGet(this, _$input).checked = isChecked;
99
+ this.#$input.checked = isChecked;
179
100
  updateExplicitBooleanAttribute(this, 'aria-checked', isChecked);
180
101
  break;
181
102
  }
@@ -183,19 +104,50 @@ defineCustomElement('sinch-toggle', (_$input = new WeakMap(), _$label = new Weak
183
104
  case 'disabled':
184
105
  {
185
106
  const isDisabled = isAttrTrue(newVal);
186
- _classPrivateFieldGet(this, _$input).disabled = isDisabled;
107
+ this.#$input.disabled = isDisabled;
187
108
  updateBooleanAttribute(this, 'disabled', isDisabled);
188
109
  break;
189
110
  }
190
111
  }
191
112
  }
192
113
 
193
- focus() {
194
- _classPrivateFieldGet(this, _$input).focus();
114
+ get focusable() {
115
+ return true;
195
116
  }
196
117
 
197
- blur() {
198
- _classPrivateFieldGet(this, _$input).blur();
118
+ focus() {
119
+ this.#$input.focus();
199
120
  }
200
121
 
201
- }));
122
+ blur() {
123
+ this.#$input.blur();
124
+ }
125
+
126
+ #onInput = e => {
127
+ e.stopPropagation();
128
+ const isChecked = this.#$input.checked;
129
+ this.#$input.checked = this.checked;
130
+ this.dispatchEvent(new CustomEvent('change', {
131
+ detail: isChecked,
132
+ bubbles: true
133
+ }));
134
+ this.dispatchEvent(new CustomEvent('-change', {
135
+ detail: isChecked
136
+ }));
137
+ };
138
+ #onCheckboxFocus = () => {
139
+ this.dispatchEvent(new CustomEvent('-focus'));
140
+ };
141
+ #onCheckboxBlur = () => {
142
+ this.dispatchEvent(new CustomEvent('-blur'));
143
+ };
144
+ #onChangeReactHandler = e => {
145
+ getReactEventHandler(this, 'on-change')?.(e);
146
+ };
147
+ #onFocusReactHandler = e => {
148
+ getReactEventHandler(this, 'on-focus')?.(e);
149
+ };
150
+ #onBlurReactHandler = e => {
151
+ getReactEventHandler(this, 'on-blur')?.(e);
152
+ };
153
+ });
@@ -1,3 +1,5 @@
1
+ import '../text';
2
+ import '../pop';
1
3
  import type { TSinchTooltipElement, TSinchTooltipReact } from './types';
2
4
  declare global {
3
5
  namespace JSX {
package/tooltip/index.js CHANGED
@@ -1,18 +1,81 @@
1
- import { attrValueToPixels, defineCustomElement, getBooleanAttribute, getIntegerAttribute, getAttribute, getLiteralAttribute, updateBooleanAttribute, updateAttribute, updateLiteralAttribute, getRect, NectaryElement } from '../utils';
2
- const templateHTML = '<style>:host{display:inline-block;vertical-align:middle}#wrapper{position:relative;display:block}#tooltip{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity .2s linear .2s}@media (prefers-reduced-motion){#tooltip{transition:none}}:host(:hover) #tooltip{opacity:1}#text{position:absolute;padding:2px 6px;font:var(--sinch-font-extra-small-text);color:var(--sinch-color-text-default);background-color:var(--sinch-color-snow-600);width:max-content;border-radius:var(--sinch-shape-radius-xs);top:-8px;left:50%;transform:translateX(-50%) translateY(-100%);word-break:break-word}#arrow{position:absolute;top:-8px;left:50%;transform:translateX(-50%);fill:var(--sinch-color-snow-600)}:host([orientation=top-left]) #text{transform:translateX(-80%) translateY(-100%)}:host([orientation=top-right]) #text{transform:translateX(-20%) translateY(-100%)}:host([orientation=bottom-right]) #text{top:calc(100% + 8px);transform:translateX(-20%)}:host([orientation=bottom-left]) #text{top:calc(100% + 8px);transform:translateX(-80%)}:host([orientation=bottom]) #text{top:calc(100% + 8px);transform:translateX(-50%)}:host([orientation^=bottom]) #arrow{top:calc(100% + 4px);transform:translateX(-50%) rotate(180deg)}:host([orientation=left]) #text{left:unset;right:calc(100% + 8px);top:50%;transform:translateY(-50%)}:host([orientation=left]) #arrow{left:-11px;top:50%;transform:translateY(-50%) rotate(270deg)}:host([orientation=right]) #text{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}:host([orientation=right]) #arrow{top:50%;left:calc(100% + 2px);transform:translateY(-50%) rotate(90deg)}:host([inverted]:not([inverted=false])) #text{color:var(--sinch-color-text-inverted);background-color:var(--sinch-color-stormy-500)}:host([inverted]:not([inverted=false])) #arrow{fill:var(--sinch-color-stormy-500)}::slotted(*){display:block}</style><div id="wrapper"><slot></slot><div id="tooltip"><div id="text"></div><svg id="arrow" width="9" height="4" aria-hidden="true"><path d="m4.5 4 4-4h-8l4 4Z"/></svg></div></div>';
3
- import { orientationValues } from './utils';
1
+ import '../text';
2
+ import '../pop';
3
+ import { defineCustomElement, getBooleanAttribute, getAttribute, getLiteralAttribute, updateBooleanAttribute, updateAttribute, updateLiteralAttribute, NectaryElement, setClass, rectOverlap } from '../utils';
4
+ import { TooltipState } from '../utils/animation';
5
+ const templateHTML = '<style>:host{display:contents}#content-wrapper{padding-bottom:8px}#content{position:relative;display:block;max-width:300px;padding:2px 6px;box-sizing:border-box;color:var(--sinch-color-text-default);background-color:var(--sinch-color-snow-600);border-radius:var(--sinch-shape-radius-xs);pointer-events:none;opacity:0}:host([orientation=left]) #content-wrapper{padding-bottom:0;padding-right:8px}:host([orientation=right]) #content-wrapper{padding-bottom:0;padding-left:8px}:host([orientation^=bottom]) #content-wrapper{padding-bottom:0;padding-top:8px}#text{word-break:break-word;pointer-events:none}#tip{position:absolute;left:50%;top:100%;transform:translateX(-50%) rotate(0);transform-origin:top center;fill:var(--sinch-color-snow-600);pointer-events:none}#tip.hidden{display:none}:host([orientation=left]) #tip{transform:translateX(-50%) rotate(270deg);top:50%;left:100%}:host([orientation=right]) #tip{transform:translateX(-50%) rotate(90deg);top:50%;left:0}:host([orientation^=bottom]) #tip{transform:translateX(-50%) rotate(180deg);top:0}:host([inverted]:not([inverted=false])) #content{background-color:var(--sinch-color-stormy-500);color:var(--sinch-color-text-inverted)}:host([inverted]:not([inverted=false])) #tip{fill:var(--sinch-color-stormy-500)}</style><sinch-pop id="pop"><slot id="target" slot="target"></slot><div id="content-wrapper" slot="content"><div id="content"><sinch-text id="text" type="s"></sinch-text><svg id="tip" width="8" height="4" aria-hidden="true"><path d="m4 4 4-4h-8l4 4Z"/></svg></div></div></sinch-pop>';
6
+ import { assertOrientation, getPopOrientation, orientationValues } from './utils';
7
+ const TIP_SIZE = 8;
8
+ const SHOW_DELAY = 1000;
9
+ const HIDE_DELAY = 100;
10
+ const ANIMATION_DURATION = 100;
4
11
  const template = document.createElement('template');
5
12
  template.innerHTML = templateHTML;
6
13
  defineCustomElement('sinch-tooltip', class extends NectaryElement {
14
+ #$pop;
15
+ #$tooltipText;
16
+ #$content;
17
+ #$contentWrapper;
18
+ #$tip;
19
+ #$target;
20
+ #controller = null;
21
+ #tooltipState;
22
+ #animation = null;
23
+ #shouldReduceMotion = false;
24
+
7
25
  constructor() {
8
26
  super();
9
27
  const shadowRoot = this.attachShadow();
10
28
  shadowRoot.appendChild(template.content.cloneNode(true));
11
- this.$tooltipText = shadowRoot.querySelector('#text');
29
+ this.#$pop = shadowRoot.querySelector('#pop');
30
+ this.#$tooltipText = shadowRoot.querySelector('#text');
31
+ this.#$content = shadowRoot.querySelector('#content');
32
+ this.#$contentWrapper = shadowRoot.querySelector('#content-wrapper');
33
+ this.#$tip = shadowRoot.querySelector('#tip');
34
+ this.#$target = shadowRoot.querySelector('#target');
35
+ this.#shouldReduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
36
+ this.#tooltipState = new TooltipState({
37
+ showDelay: SHOW_DELAY,
38
+ hideDelay: this.#shouldReduceMotion ? HIDE_DELAY + ANIMATION_DURATION : HIDE_DELAY,
39
+ hideAnimationDuration: this.#shouldReduceMotion ? 0 : ANIMATION_DURATION,
40
+ onShow: this.#onShow,
41
+ onHideStart: this.#onHideStart,
42
+ onHideEnd: this.#onHideEnd
43
+ });
44
+ }
45
+
46
+ connectedCallback() {
47
+ this.#controller = new AbortController();
48
+ const {
49
+ signal
50
+ } = this.#controller;
51
+ this.#$pop.addEventListener('-close', this.#onPopClose, {
52
+ signal
53
+ });
54
+ this.#$target.addEventListener('mousedown', this.#onMouseDown, {
55
+ signal
56
+ });
57
+ this.#$target.addEventListener('mouseenter', this.#onMouseEnter, {
58
+ signal
59
+ });
60
+ this.#$target.addEventListener('mouseleave', this.#onMouseLeave, {
61
+ signal
62
+ });
63
+ this.#$contentWrapper.addEventListener('mouseenter', this.#onMouseEnter, {
64
+ signal
65
+ });
66
+ this.#$contentWrapper.addEventListener('mouseleave', this.#onMouseLeave, {
67
+ signal
68
+ });
69
+ updateAttribute(this.#$pop, 'orientation', getPopOrientation(this.orientation));
70
+ }
71
+
72
+ disconnectedCallback() {
73
+ this.#controller.abort();
74
+ this.#tooltipState.destroy();
12
75
  }
13
76
 
14
77
  static get observedAttributes() {
15
- return ['text', 'width'];
78
+ return ['text', 'orientation'];
16
79
  }
17
80
 
18
81
  get text() {
@@ -23,14 +86,6 @@ defineCustomElement('sinch-tooltip', class extends NectaryElement {
23
86
  updateAttribute(this, 'text', value);
24
87
  }
25
88
 
26
- get width() {
27
- return getIntegerAttribute(this, 'width', null);
28
- }
29
-
30
- set width(value) {
31
- updateAttribute(this, 'width', value);
32
- }
33
-
34
89
  get inverted() {
35
90
  return getBooleanAttribute(this, 'inverted');
36
91
  }
@@ -47,24 +102,112 @@ defineCustomElement('sinch-tooltip', class extends NectaryElement {
47
102
  updateLiteralAttribute(this, orientationValues, 'orientation', value);
48
103
  }
49
104
 
105
+ get footprintRect() {
106
+ return this.#$pop.footprintRect;
107
+ }
108
+
50
109
  get tooltipRect() {
51
- return getRect(this.$tooltipText);
110
+ return this.#$pop.popoverRect;
52
111
  }
53
112
 
54
113
  attributeChangedCallback(name, _, newVal) {
55
114
  switch (name) {
56
115
  case 'text':
57
116
  {
58
- this.$tooltipText.textContent = newVal;
117
+ this.#$tooltipText.textContent = newVal;
59
118
  break;
60
119
  }
61
120
 
62
- case 'width':
121
+ case 'orientation':
63
122
  {
64
- this.$tooltipText.style.maxWidth = attrValueToPixels(newVal);
123
+ assertOrientation(newVal);
124
+ updateAttribute(this.#$pop, 'orientation', getPopOrientation(newVal));
125
+
126
+ if (this.#isOpen()) {
127
+ this.#resetTipOrientation();
128
+ this.#updateTipOrientation();
129
+ }
130
+
65
131
  break;
66
132
  }
67
133
  }
68
134
  }
69
135
 
136
+ #onMouseDown = () => {
137
+ this.#tooltipState.interrupt();
138
+ };
139
+ #onPopClose = () => {
140
+ this.#tooltipState.destroy();
141
+ };
142
+ #onMouseEnter = () => {
143
+ this.#tooltipState.show();
144
+ };
145
+ #onMouseLeave = e => {
146
+ if (!this.#isOpen() || e.relatedTarget !== this.#$contentWrapper && e.relatedTarget !== this.#$target) {
147
+ this.#tooltipState.hide();
148
+ }
149
+ };
150
+ #onShow = () => {
151
+ updateBooleanAttribute(this.#$pop, 'open', true);
152
+ requestAnimationFrame(this.#updateTipOrientation);
153
+
154
+ if (this.#animation !== null) {
155
+ this.#animation.updatePlaybackRate(1);
156
+ this.#animation.play();
157
+ } else {
158
+ this.#animation = this.#$content.animate({
159
+ opacity: [0, 1]
160
+ }, {
161
+ duration: this.#shouldReduceMotion ? 0 : ANIMATION_DURATION,
162
+ iterations: 1,
163
+ fill: 'forwards'
164
+ });
165
+ }
166
+ };
167
+ #onHideStart = () => {
168
+ this.#animation.updatePlaybackRate(-1);
169
+ this.#animation.play();
170
+ };
171
+ #onHideEnd = () => {
172
+ this.#animation.finish();
173
+ this.#resetTipOrientation();
174
+ updateBooleanAttribute(this.#$pop, 'open', false);
175
+ };
176
+
177
+ #resetTipOrientation() {
178
+ this.#$tip.style.top = '';
179
+ this.#$tip.style.left = '';
180
+ }
181
+
182
+ #updateTipOrientation = () => {
183
+ const orient = this.orientation;
184
+ const targetRect = this.#$pop.footprintRect;
185
+ const contentRect = this.#$content.getBoundingClientRect();
186
+ const diffX = targetRect.x - contentRect.x;
187
+ const diffY = targetRect.y - contentRect.y;
188
+
189
+ if (orient === 'left' || orient === 'right') {
190
+ const yPos = Math.max(TIP_SIZE, Math.min(diffY + targetRect.height / 2, contentRect.height - TIP_SIZE));
191
+ this.#$tip.style.top = `${yPos}px`;
192
+ } else {
193
+ let xPos = Math.max(TIP_SIZE, Math.min(diffX + targetRect.width / 2, contentRect.width - TIP_SIZE));
194
+
195
+ if (orient === 'bottom-left' || orient === 'top-left') {
196
+ xPos = Math.max(xPos, contentRect.width * 0.75);
197
+ }
198
+
199
+ if (orient === 'bottom-right' || orient === 'top-right') {
200
+ xPos = Math.min(xPos, contentRect.width * 0.25);
201
+ }
202
+
203
+ this.#$tip.style.left = `${xPos}px`;
204
+ }
205
+
206
+ setClass(this.#$tip, 'hidden', rectOverlap(targetRect, contentRect));
207
+ };
208
+
209
+ #isOpen() {
210
+ return this.#$pop.hasAttribute('open');
211
+ }
212
+
70
213
  });
@@ -1,19 +1,32 @@
1
1
  import type { TRect, TSinchElementReact } from '../types';
2
2
  export declare type TSinchTooltipOrientation = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
3
  export declare type TSinchTooltipElement = HTMLElement & {
4
+ /** Text */
4
5
  text: string;
6
+ /** @deprecated */
5
7
  width: number | null;
8
+ /** Inverted */
6
9
  inverted: boolean;
10
+ /** Orientation, where it *points to* from origin */
7
11
  orientation: TSinchTooltipOrientation;
12
+ readonly footprintRect: TRect;
8
13
  readonly tooltipRect: TRect;
14
+ /** Text */
9
15
  setAttribute(name: 'text', value: string): void;
16
+ /** @deprecated */
10
17
  setAttribute(name: 'width', value: string): void;
18
+ /** Inverted */
11
19
  setAttribute(name: 'inverted', value: ''): void;
20
+ /** Orientation, where it *points to* from origin */
12
21
  setAttribute(name: 'orientation', value: TSinchTooltipOrientation): void;
13
22
  };
14
23
  export declare type TSinchTooltipReact = TSinchElementReact<TSinchTooltipElement> & {
24
+ /** Text */
15
25
  text: string;
26
+ /** @deprecated */
16
27
  width?: number;
28
+ /** Inverted */
17
29
  inverted?: boolean;
30
+ /** Orientation, where it *points to* from origin */
18
31
  orientation?: TSinchTooltipOrientation;
19
32
  };
@@ -1,2 +1,7 @@
1
+ import type { TSinchPopOrientation } from '../pop/types';
1
2
  import type { TSinchTooltipOrientation } from './types';
2
3
  export declare const orientationValues: readonly TSinchTooltipOrientation[];
4
+ export declare const getPopOrientation: (orientation: TSinchTooltipOrientation) => TSinchPopOrientation;
5
+ declare type TAssertOrientation = (value: string | null) => asserts value is TSinchTooltipOrientation;
6
+ export declare const assertOrientation: TAssertOrientation;
7
+ export {};
package/tooltip/utils.js CHANGED
@@ -1 +1,25 @@
1
- export const orientationValues = ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'];
1
+ export const orientationValues = ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'];
2
+ export const getPopOrientation = orientation => {
3
+ if (orientation === 'top') {
4
+ return 'top-center';
5
+ }
6
+
7
+ if (orientation === 'bottom') {
8
+ return 'bottom-center';
9
+ }
10
+
11
+ if (orientation === 'left') {
12
+ return 'center-left';
13
+ }
14
+
15
+ if (orientation === 'right') {
16
+ return 'center-right';
17
+ }
18
+
19
+ return orientation;
20
+ };
21
+ export const assertOrientation = value => {
22
+ if (value === null || !orientationValues.includes(value)) {
23
+ throw new Error(`sinch-tooltip: invalid orientation attribute: ${value}`);
24
+ }
25
+ };
@@ -0,0 +1,17 @@
1
+ declare type TTooltipStateOptions = {
2
+ showDelay: number;
3
+ hideDelay: number;
4
+ hideAnimationDuration: number;
5
+ onShow(): void;
6
+ onHideStart(): void;
7
+ onHideEnd(): void;
8
+ };
9
+ export declare class TooltipState {
10
+ #private;
11
+ constructor(options: TTooltipStateOptions);
12
+ show(): void;
13
+ hide(): void;
14
+ interrupt(): void;
15
+ destroy(): void;
16
+ }
17
+ export {};
@@ -0,0 +1,142 @@
1
+ export class TooltipState {
2
+ #timerId = null;
3
+ #state = 'initial';
4
+ #options;
5
+
6
+ constructor(options) {
7
+ this.#options = options;
8
+ }
9
+
10
+ show() {
11
+ switch (this.#state) {
12
+ case 'initial':
13
+ {
14
+ this.#delayShow();
15
+ break;
16
+ }
17
+
18
+ case 'hide-delay':
19
+ {
20
+ this.#cancelStateChange('show');
21
+ break;
22
+ }
23
+
24
+ case 'hide':
25
+ {
26
+ this.#cancelStateChange();
27
+ this.#onShow();
28
+ break;
29
+ }
30
+ }
31
+ }
32
+
33
+ hide() {
34
+ switch (this.#state) {
35
+ case 'show-delay':
36
+ {
37
+ this.#cancelStateChange('initial');
38
+ break;
39
+ }
40
+
41
+ case 'show':
42
+ {
43
+ this.#delayHide();
44
+ break;
45
+ }
46
+ }
47
+ }
48
+
49
+ interrupt() {
50
+ switch (this.#state) {
51
+ case 'show-delay':
52
+ {
53
+ this.#cancelStateChange('initial');
54
+ break;
55
+ }
56
+
57
+ case 'show':
58
+ {
59
+ this.#onHideStart();
60
+ break;
61
+ }
62
+
63
+ case 'hide-delay':
64
+ {
65
+ this.#cancelStateChange();
66
+ this.#onHideStart();
67
+ break;
68
+ }
69
+ }
70
+ }
71
+
72
+ destroy() {
73
+ switch (this.#state) {
74
+ case 'show-delay':
75
+ {
76
+ this.#cancelStateChange('initial');
77
+ break;
78
+ }
79
+
80
+ case 'show':
81
+ {
82
+ this.#onHideStart(true);
83
+ break;
84
+ }
85
+
86
+ case 'hide-delay':
87
+ {
88
+ this.#cancelStateChange();
89
+ this.#onHideStart(true);
90
+ break;
91
+ }
92
+
93
+ case 'hide':
94
+ {
95
+ this.#cancelStateChange();
96
+ this.#onHideEnd();
97
+ break;
98
+ }
99
+ }
100
+ }
101
+
102
+ #delayShow() {
103
+ this.#state = 'show-delay';
104
+ this.#timerId = window.setTimeout(this.#onShow, this.#options.showDelay);
105
+ }
106
+
107
+ #delayHide() {
108
+ this.#state = 'hide-delay';
109
+ this.#timerId = window.setTimeout(this.#onHideStart, this.#options.hideDelay);
110
+ }
111
+
112
+ #onShow = () => {
113
+ this.#state = 'show';
114
+ this.#options.onShow();
115
+ };
116
+ #onHideStart = isImmediate => {
117
+ this.#state = 'hide';
118
+ this.#options.onHideStart();
119
+
120
+ if (isImmediate === true || this.#options.hideAnimationDuration === 0) {
121
+ this.#onHideEnd();
122
+ } else {
123
+ this.#timerId = window.setTimeout(this.#onHideEnd, this.#options.hideAnimationDuration);
124
+ }
125
+ };
126
+ #onHideEnd = () => {
127
+ this.#state = 'initial';
128
+ this.#options.onHideEnd();
129
+ };
130
+
131
+ #cancelStateChange(nextState) {
132
+ if (nextState != null) {
133
+ this.#state = nextState;
134
+ }
135
+
136
+ if (this.#timerId !== null) {
137
+ clearTimeout(this.#timerId);
138
+ this.#timerId = null;
139
+ }
140
+ }
141
+
142
+ }