@nectary/components 0.42.1 → 0.44.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.
- package/accordion/index.js +3 -3
- package/action-menu/index.js +1 -2
- package/card-container/index.js +1 -1
- package/color-menu/index.d.ts +3 -3
- package/color-menu/index.js +2 -3
- package/color-menu/types.d.ts +2 -2
- package/date-picker/index.js +173 -64
- package/date-picker/types.d.ts +6 -0
- package/date-picker/utils.d.ts +6 -3
- package/date-picker/utils.js +19 -4
- package/dialog/index.js +2 -2
- package/emoji/index.d.ts +11 -0
- package/emoji/index.js +47 -0
- package/emoji/types.d.ts +11 -0
- package/emoji/types.js +1 -0
- package/emoji/utils.d.ts +1 -0
- package/emoji/utils.js +46 -0
- package/emoji-picker/index.d.ts +28 -0
- package/emoji-picker/index.js +319 -0
- package/emoji-picker/types.d.ts +25 -0
- package/emoji-picker/types.js +1 -0
- package/file-drop/types.d.ts +2 -2
- package/file-picker/types.d.ts +1 -1
- package/icon/index.d.ts +11 -0
- package/icon/index.js +32 -0
- package/icon/types.d.ts +11 -0
- package/icon/types.js +1 -0
- package/icon-button/index.js +1 -1
- package/package.json +2 -2
- package/pop/index.js +1 -2
- package/popover/index.js +1 -2
- package/segment/index.js +1 -1
- package/segmented-icon-control/index.js +3 -3
- package/select-menu/index.js +3 -4
- package/stop-events/index.js +3 -3
- package/tabs/index.js +24 -76
- package/tabs/types.d.ts +9 -2
- package/tabs-icon-option/index.d.ts +11 -0
- package/tabs-icon-option/index.js +75 -0
- package/tabs-icon-option/types.d.ts +19 -0
- package/tabs-icon-option/types.js +1 -0
- package/tabs-option/index.d.ts +1 -0
- package/tabs-option/index.js +8 -15
- package/tabs-option/types.d.ts +13 -5
- package/theme/emoji.css +6 -0
- package/theme/fonts.css +9 -0
- package/theme/fonts.json +9 -0
- package/theme/icon.css +7 -0
- package/theme.css +2 -0
- package/tile-control/index.js +3 -3
- package/tooltip/index.js +78 -31
- package/utils/csv.d.ts +5 -0
- package/utils/csv.js +22 -0
- package/utils/dom.d.ts +30 -0
- package/utils/dom.js +143 -0
- package/utils/element.d.ts +12 -0
- package/utils/element.js +38 -0
- package/utils/get-react-event-handler.d.ts +1 -0
- package/utils/get-react-event-handler.js +8 -0
- package/utils/index.d.ts +8 -57
- package/utils/index.js +8 -301
- package/utils/rect.d.ts +4 -0
- package/utils/rect.js +29 -0
- package/utils/slot.d.ts +4 -0
- package/utils/slot.js +38 -0
- package/utils/throttle.d.ts +4 -0
- package/utils/throttle.js +25 -0
- /package/{utils/animation.d.ts → tooltip/tooltip-state.d.ts} +0 -0
- /package/{utils/animation.js → tooltip/tooltip-state.js} +0 -0
package/emoji/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineCustomElement, getAttribute, NectaryElement, updateAttribute } from '../utils';
|
|
2
|
+
const templateHTML = '<style>:host{display:contents}#image{pointer-events:none;width:var(--sinch-size-icon,48px);height:var(--sinch-size-icon,48px)}</style><img id="image" src="" alt="" loading="lazy"/>';
|
|
3
|
+
import { getEmojiUrl } from './utils';
|
|
4
|
+
const template = document.createElement('template');
|
|
5
|
+
template.innerHTML = templateHTML;
|
|
6
|
+
defineCustomElement('sinch-emoji', class extends NectaryElement {
|
|
7
|
+
#$img;
|
|
8
|
+
#isConnected = false;
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
const shadowRoot = this.attachShadow();
|
|
12
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
13
|
+
this.#$img = shadowRoot.querySelector('#image');
|
|
14
|
+
}
|
|
15
|
+
connectedCallback() {
|
|
16
|
+
this.#isConnected = true;
|
|
17
|
+
this.#updateChar();
|
|
18
|
+
}
|
|
19
|
+
disconnectedCallback() {
|
|
20
|
+
this.#isConnected = false;
|
|
21
|
+
}
|
|
22
|
+
static get observedAttributes() {
|
|
23
|
+
return ['char'];
|
|
24
|
+
}
|
|
25
|
+
attributeChangedCallback(name, _, newVal) {
|
|
26
|
+
switch (name) {
|
|
27
|
+
case 'char':
|
|
28
|
+
{
|
|
29
|
+
this.#$img.alt = newVal ?? '';
|
|
30
|
+
this.#updateChar();
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
set char(value) {
|
|
36
|
+
updateAttribute(this, 'char', value);
|
|
37
|
+
}
|
|
38
|
+
get char() {
|
|
39
|
+
return getAttribute(this, 'char', '');
|
|
40
|
+
}
|
|
41
|
+
#updateChar() {
|
|
42
|
+
if (!this.#isConnected) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#$img.src = getEmojiUrl(this, this.char);
|
|
46
|
+
}
|
|
47
|
+
});
|
package/emoji/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TSinchElementReact } from '../types';
|
|
2
|
+
export declare type TSinchEmojiElement = HTMLElement & {
|
|
3
|
+
/** Emoji character */
|
|
4
|
+
char: string;
|
|
5
|
+
/** Emoji character */
|
|
6
|
+
setAttribute(name: 'char', value: string): void;
|
|
7
|
+
};
|
|
8
|
+
export declare type TSinchEmojiReact = TSinchElementReact<TSinchEmojiElement> & {
|
|
9
|
+
/** Emoji character */
|
|
10
|
+
char: string;
|
|
11
|
+
};
|
package/emoji/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/emoji/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getEmojiUrl: (root: Element, char: string | null) => string;
|
package/emoji/utils.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getCssVar } from '../utils';
|
|
2
|
+
const vs16RegExp = /\uFE0F/g;
|
|
3
|
+
const zeroWidthJoiner = String.fromCharCode(0x200d);
|
|
4
|
+
const removeVS16s = rawEmoji => rawEmoji.indexOf(zeroWidthJoiner) < 0 ? rawEmoji.replace(vs16RegExp, '') : rawEmoji;
|
|
5
|
+
function toCodePoints(unicodeSurrogates) {
|
|
6
|
+
const points = [];
|
|
7
|
+
let char = 0;
|
|
8
|
+
let previous = 0;
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < unicodeSurrogates.length) {
|
|
11
|
+
char = unicodeSurrogates.charCodeAt(i++);
|
|
12
|
+
if (previous !== 0) {
|
|
13
|
+
points.push((0x10000 + (previous - 0xd800 << 10) + (char - 0xdc00)).toString(16));
|
|
14
|
+
previous = 0;
|
|
15
|
+
} else if (char > 0xd800 && char <= 0xdbff) {
|
|
16
|
+
previous = char;
|
|
17
|
+
} else {
|
|
18
|
+
points.push(char.toString(16));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return points;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let emojiUrl = null;
|
|
25
|
+
export const getEmojiUrl = (root, char) => {
|
|
26
|
+
if (char === null || char.length === 0) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (emojiUrl === null) {
|
|
31
|
+
emojiUrl = getCssVar(root, '--sinch-emoji-src-url');
|
|
32
|
+
if (emojiUrl !== null) {
|
|
33
|
+
emojiUrl = emojiUrl.replaceAll('"', '').trim();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (emojiUrl === null) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
let codepoints = toCodePoints(removeVS16s(char)).join('-');
|
|
40
|
+
|
|
41
|
+
if (codepoints === '1f441-fe0f-200d-1f5e8-fe0f') {
|
|
42
|
+
codepoints = '1f441-200d-1f5e8';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return emojiUrl.replace('%s', codepoints);
|
|
46
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '../input';
|
|
2
|
+
import '../icon-button';
|
|
3
|
+
import '../color-swatch';
|
|
4
|
+
import '../color-menu';
|
|
5
|
+
import '../popover';
|
|
6
|
+
import '../tabs';
|
|
7
|
+
import '../tabs-icon-option';
|
|
8
|
+
import '../emoji';
|
|
9
|
+
import '../icons/search';
|
|
10
|
+
import '../icons/sentiment-satisfied';
|
|
11
|
+
import '../icons/emoji-people';
|
|
12
|
+
import '../icons/emoji-nature';
|
|
13
|
+
import '../icons/emoji-food-beverage';
|
|
14
|
+
import '../icons/emoji-objects';
|
|
15
|
+
import '../icons/emoji-transportation';
|
|
16
|
+
import '../icons/emoji-events';
|
|
17
|
+
import '../icons/emoji-symbols';
|
|
18
|
+
import type { TSinchEmojiPickerElement, TSinchEmojiPickerReact } from './types';
|
|
19
|
+
declare global {
|
|
20
|
+
namespace JSX {
|
|
21
|
+
interface IntrinsicElements {
|
|
22
|
+
'sinch-emoji-picker': TSinchEmojiPickerReact;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
interface HTMLElementTagNameMap {
|
|
26
|
+
'sinch-emoji-picker': TSinchEmojiPickerElement;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import '../input';
|
|
2
|
+
import '../icon-button';
|
|
3
|
+
import '../color-swatch';
|
|
4
|
+
import '../color-menu';
|
|
5
|
+
import '../popover';
|
|
6
|
+
import '../tabs';
|
|
7
|
+
import '../tabs-icon-option';
|
|
8
|
+
import '../emoji';
|
|
9
|
+
import '../icons/search';
|
|
10
|
+
import '../icons/sentiment-satisfied';
|
|
11
|
+
import '../icons/emoji-people';
|
|
12
|
+
import '../icons/emoji-nature';
|
|
13
|
+
import '../icons/emoji-food-beverage';
|
|
14
|
+
import '../icons/emoji-objects';
|
|
15
|
+
import '../icons/emoji-transportation';
|
|
16
|
+
import '../icons/emoji-events';
|
|
17
|
+
import '../icons/emoji-symbols';
|
|
18
|
+
import { defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute, NectaryElement, updateAttribute, updateBooleanAttribute, getReactEventHandler, getRect } from '../utils';
|
|
19
|
+
import dataJson from './data.json';
|
|
20
|
+
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}#input{flex:1}#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}</style><div id="wrapper"><div id="toolbar"><sinch-input id="input" aria-label="Search emojis"><sinch-icon-search slot="icon"></sinch-icon-search></sinch-input><sinch-popover id="skin-popover" orientation="bottom-left" aria-label="Emoji skin tone select"><sinch-icon-button id="skin-button" slot="target" aria-label="Select emoji skin tones"><sinch-color-swatch id="skin-swatch" slot="icon" name="skin-tone-0"></sinch-color-swatch></sinch-icon-button><sinch-color-menu id="skin-menu" slot="content" cols="1" value="skin-tone-0" colors="skin-tone-0,skin-tone-10,skin-tone-20,skin-tone-30,skin-tone-40,skin-tone-50" aria-label="Emoji skin tone menu"></sinch-color-menu></sinch-popover></div><sinch-tabs id="tabs" aria-label="Emoji groups"></sinch-tabs><div id="list-wrapper"><div id="list"></div></div></div>';
|
|
21
|
+
const groupIconTagNames = ['sinch-icon-sentiment-satisfied', 'sinch-icon-emoji-people', 'sinch-icon-emoji-nature', 'sinch-icon-emoji-food-beverage', 'sinch-icon-emoji-transportation', 'sinch-icon-emoji-events', 'sinch-icon-emoji-objects', 'sinch-icon-emoji-symbols'];
|
|
22
|
+
const groupLabels = ['Emotions', 'People', 'Animals and nature', 'Food and drinks', 'Travel and places', 'Sports and activities', 'Objects', 'Symbols and flags'];
|
|
23
|
+
const data = dataJson;
|
|
24
|
+
const template = document.createElement('template');
|
|
25
|
+
template.innerHTML = templateHTML;
|
|
26
|
+
defineCustomElement('sinch-emoji-picker', class extends NectaryElement {
|
|
27
|
+
#$tabs;
|
|
28
|
+
#$input;
|
|
29
|
+
#$skinPopover;
|
|
30
|
+
#$skinMenu;
|
|
31
|
+
#$skinSwatch;
|
|
32
|
+
#$skinButton;
|
|
33
|
+
#$list;
|
|
34
|
+
#controller = null;
|
|
35
|
+
#$sh;
|
|
36
|
+
#currentSkinTone = 0;
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
const shadowRoot = this.attachShadow();
|
|
40
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
41
|
+
this.#$sh = shadowRoot;
|
|
42
|
+
this.#$tabs = shadowRoot.querySelector('#tabs');
|
|
43
|
+
this.#$input = shadowRoot.querySelector('#input');
|
|
44
|
+
this.#$skinPopover = shadowRoot.querySelector('#skin-popover');
|
|
45
|
+
this.#$skinMenu = shadowRoot.querySelector('#skin-menu');
|
|
46
|
+
this.#$skinSwatch = shadowRoot.querySelector('#skin-swatch');
|
|
47
|
+
this.#$skinButton = shadowRoot.querySelector('#skin-button');
|
|
48
|
+
this.#$list = shadowRoot.querySelector('#list');
|
|
49
|
+
}
|
|
50
|
+
connectedCallback() {
|
|
51
|
+
this.#controller = new AbortController();
|
|
52
|
+
const {
|
|
53
|
+
signal
|
|
54
|
+
} = this.#controller;
|
|
55
|
+
this.#$tabs.addEventListener('-change', this.#onTabsChange, {
|
|
56
|
+
signal
|
|
57
|
+
});
|
|
58
|
+
this.#$input.addEventListener('-change', this.#onSearchChange, {
|
|
59
|
+
signal
|
|
60
|
+
});
|
|
61
|
+
this.addEventListener('keydown', this.#onListboxKeyDown, {
|
|
62
|
+
signal
|
|
63
|
+
});
|
|
64
|
+
this.addEventListener('-keydown', this.#onContexKeydown, {
|
|
65
|
+
signal
|
|
66
|
+
});
|
|
67
|
+
this.#$skinButton.addEventListener('-click', this.#onSkinButtonClick, {
|
|
68
|
+
signal
|
|
69
|
+
});
|
|
70
|
+
this.#$skinPopover.addEventListener('-close', this.#onSkinPopoverClose, {
|
|
71
|
+
signal
|
|
72
|
+
});
|
|
73
|
+
this.#$skinMenu.addEventListener('-change', this.#onSkinMenuChange, {
|
|
74
|
+
signal
|
|
75
|
+
});
|
|
76
|
+
this.#$list.addEventListener('click', this.#onListClick, {
|
|
77
|
+
signal
|
|
78
|
+
});
|
|
79
|
+
this.addEventListener('-change', this.#onChangeReactHandler, {
|
|
80
|
+
signal
|
|
81
|
+
});
|
|
82
|
+
this.addEventListener('-visibility', this.#onContextVisibility, {
|
|
83
|
+
signal
|
|
84
|
+
});
|
|
85
|
+
dispatchContextConnectEvent(this, 'keydown');
|
|
86
|
+
dispatchContextConnectEvent(this, 'visibility');
|
|
87
|
+
this.#updateTabs();
|
|
88
|
+
this.#updateEmojis();
|
|
89
|
+
}
|
|
90
|
+
disconnectedCallback() {
|
|
91
|
+
dispatchContextDisconnectEvent(this, 'keydown');
|
|
92
|
+
dispatchContextDisconnectEvent(this, 'visibility');
|
|
93
|
+
this.#controller.abort();
|
|
94
|
+
}
|
|
95
|
+
get skinToneButtonRect() {
|
|
96
|
+
return getRect(this.#$skinButton);
|
|
97
|
+
}
|
|
98
|
+
get searchInputRect() {
|
|
99
|
+
return getRect(this.#$input);
|
|
100
|
+
}
|
|
101
|
+
nthSkinToneRect(index) {
|
|
102
|
+
return this.#$skinMenu.nthItemRect(index);
|
|
103
|
+
}
|
|
104
|
+
nthTabRect(index) {
|
|
105
|
+
return this.#$tabs.nthOptionRect(index);
|
|
106
|
+
}
|
|
107
|
+
nthEmojiRect(index) {
|
|
108
|
+
const $el = this.#$list.children[index];
|
|
109
|
+
return $el != null ? getRect($el) : null;
|
|
110
|
+
}
|
|
111
|
+
#onListClick = e => {
|
|
112
|
+
const value = e.target.getAttribute('data-value');
|
|
113
|
+
if (value === null) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.dispatchEvent(new CustomEvent('-change', {
|
|
117
|
+
detail: value
|
|
118
|
+
}));
|
|
119
|
+
};
|
|
120
|
+
#onContexKeydown = e => {
|
|
121
|
+
this.#handleKeydown(e.detail);
|
|
122
|
+
};
|
|
123
|
+
#onContextVisibility = e => {
|
|
124
|
+
if (e.detail) {
|
|
125
|
+
} else {
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
#onListboxKeyDown = e => {
|
|
129
|
+
this.#handleKeydown(e);
|
|
130
|
+
};
|
|
131
|
+
#handleKeydown(e) {
|
|
132
|
+
switch (e.code) {
|
|
133
|
+
case 'Space':
|
|
134
|
+
case 'Enter':
|
|
135
|
+
{
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'ArrowUp':
|
|
139
|
+
{
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'ArrowDown':
|
|
144
|
+
{
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
#onTabsChange = e => {
|
|
151
|
+
const value = e.detail;
|
|
152
|
+
updateAttribute(this.#$tabs, 'value', value);
|
|
153
|
+
updateAttribute(this.#$input, 'value', '');
|
|
154
|
+
this.#updateEmojis();
|
|
155
|
+
};
|
|
156
|
+
#onSearchChange = e => {
|
|
157
|
+
const value = e.detail;
|
|
158
|
+
this.#$input.value = value;
|
|
159
|
+
if (value.length < 3) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
updateAttribute(this.#$tabs, 'value', '');
|
|
163
|
+
this.#updateSearchEmojis();
|
|
164
|
+
};
|
|
165
|
+
#onChangeReactHandler = e => {
|
|
166
|
+
getReactEventHandler(this, 'on-change')?.(e);
|
|
167
|
+
};
|
|
168
|
+
get focusable() {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
#getDocumentRoot() {
|
|
172
|
+
return Reflect.has(this.#$sh, 'createElement') ? this.#$sh : document;
|
|
173
|
+
}
|
|
174
|
+
#updateTabs() {
|
|
175
|
+
const doc = this.#getDocumentRoot();
|
|
176
|
+
const tabsFragment = document.createDocumentFragment();
|
|
177
|
+
const activeTab = data[0].name;
|
|
178
|
+
for (let i = 0; i < data.length; i++) {
|
|
179
|
+
const group = data[i];
|
|
180
|
+
const tabOption = doc.createElement('sinch-tabs-icon-option');
|
|
181
|
+
const icon = doc.createElement(groupIconTagNames[i]);
|
|
182
|
+
icon.setAttribute('slot', 'icon');
|
|
183
|
+
tabOption.setAttribute('value', group.name);
|
|
184
|
+
tabOption.setAttribute('aria-label', groupLabels[i]);
|
|
185
|
+
tabOption.appendChild(icon);
|
|
186
|
+
tabsFragment.appendChild(tabOption);
|
|
187
|
+
}
|
|
188
|
+
this.#$tabs.replaceChildren(tabsFragment);
|
|
189
|
+
updateAttribute(this.#$tabs, 'value', activeTab);
|
|
190
|
+
}
|
|
191
|
+
*#iterateSearchEmojis(searchValue, skinTone) {
|
|
192
|
+
for (const group of data) {
|
|
193
|
+
for (const entry of group.emojis) {
|
|
194
|
+
if (entry.label.includes(searchValue)) {
|
|
195
|
+
const hasSkins = entry.skins != null;
|
|
196
|
+
if (skinTone === 0 || !hasSkins) {
|
|
197
|
+
yield entry;
|
|
198
|
+
} else if (hasSkins) {
|
|
199
|
+
for (const skin of entry.skins) {
|
|
200
|
+
if (skinTone === skin.tone || Array.isArray(skin.tone) && skin.tone.includes(skinTone)) {
|
|
201
|
+
yield skin;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
*#iterateGroupEmojis(group, skinTone) {
|
|
210
|
+
for (const entry of group.emojis) {
|
|
211
|
+
const hasSkins = entry.skins != null;
|
|
212
|
+
if (skinTone === 0 || !hasSkins) {
|
|
213
|
+
yield entry;
|
|
214
|
+
} else if (hasSkins) {
|
|
215
|
+
for (const skin of entry.skins) {
|
|
216
|
+
if (skinTone === skin.tone || Array.isArray(skin.tone) && skin.tone.includes(skinTone)) {
|
|
217
|
+
yield skin;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
#updateSearchEmojis() {
|
|
224
|
+
const searchValue = this.#$input.value;
|
|
225
|
+
if (searchValue.length < 3) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const doc = this.#getDocumentRoot();
|
|
229
|
+
const fragment = document.createDocumentFragment();
|
|
230
|
+
for (const entry of this.#iterateSearchEmojis(searchValue, this.#currentSkinTone)) {
|
|
231
|
+
const el = this.#createEmojiElement(doc, entry);
|
|
232
|
+
fragment.appendChild(el);
|
|
233
|
+
}
|
|
234
|
+
this.#$list.replaceChildren(fragment);
|
|
235
|
+
this.#$list.scrollTo(0, 0);
|
|
236
|
+
}
|
|
237
|
+
#updateEmojis() {
|
|
238
|
+
if (this.#isSearchMode()) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const activeGroup = getAttribute(this.#$tabs, 'value');
|
|
242
|
+
const group = data.find(group => group.name === activeGroup);
|
|
243
|
+
if (group == null) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const doc = this.#getDocumentRoot();
|
|
247
|
+
const fragment = document.createDocumentFragment();
|
|
248
|
+
for (const entry of this.#iterateGroupEmojis(group, this.#currentSkinTone)) {
|
|
249
|
+
const el = this.#createEmojiElement(doc, entry);
|
|
250
|
+
fragment.appendChild(el);
|
|
251
|
+
}
|
|
252
|
+
this.#$list.replaceChildren(fragment);
|
|
253
|
+
this.#$list.scrollTo(0, 0);
|
|
254
|
+
}
|
|
255
|
+
#onSkinButtonClick = () => {
|
|
256
|
+
updateBooleanAttribute(this.#$skinPopover, 'open', !getBooleanAttribute(this.#$skinPopover, 'open'));
|
|
257
|
+
};
|
|
258
|
+
#onSkinPopoverClose = () => {
|
|
259
|
+
updateBooleanAttribute(this.#$skinPopover, 'open', false);
|
|
260
|
+
};
|
|
261
|
+
#onSkinMenuChange = e => {
|
|
262
|
+
this.#$skinSwatch.name = e.detail;
|
|
263
|
+
this.#$skinMenu.value = e.detail;
|
|
264
|
+
switch (e.detail) {
|
|
265
|
+
case 'skin-tone-0':
|
|
266
|
+
{
|
|
267
|
+
this.#currentSkinTone = 0;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 'skin-tone-10':
|
|
271
|
+
{
|
|
272
|
+
this.#currentSkinTone = 1;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 'skin-tone-20':
|
|
276
|
+
{
|
|
277
|
+
this.#currentSkinTone = 2;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case 'skin-tone-30':
|
|
281
|
+
{
|
|
282
|
+
this.#currentSkinTone = 3;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'skin-tone-40':
|
|
286
|
+
{
|
|
287
|
+
this.#currentSkinTone = 4;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'skin-tone-50':
|
|
291
|
+
{
|
|
292
|
+
this.#currentSkinTone = 5;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
this.#onSkinPopoverClose();
|
|
297
|
+
if (this.#isSearchMode()) {
|
|
298
|
+
this.#updateSearchEmojis();
|
|
299
|
+
} else {
|
|
300
|
+
this.#updateEmojis();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
#isSearchMode() {
|
|
304
|
+
const activeTab = getAttribute(this.#$tabs, 'value');
|
|
305
|
+
return activeTab === null || activeTab.length === 0;
|
|
306
|
+
}
|
|
307
|
+
#createEmojiElement(doc, emoji) {
|
|
308
|
+
const btn = doc.createElement('sinch-icon-button');
|
|
309
|
+
const el = doc.createElement('sinch-emoji');
|
|
310
|
+
el.setAttribute('slot', 'icon');
|
|
311
|
+
el.setAttribute('char', emoji.emoji);
|
|
312
|
+
el.setAttribute('label', emoji.label);
|
|
313
|
+
btn.setAttribute('aria-label', emoji.label);
|
|
314
|
+
btn.setAttribute('small', '');
|
|
315
|
+
btn.setAttribute('data-value', emoji.emoji);
|
|
316
|
+
btn.appendChild(el);
|
|
317
|
+
return btn;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TRect, TSinchElementReact } from '../types';
|
|
2
|
+
export declare type TEmoji = {
|
|
3
|
+
emoji: string;
|
|
4
|
+
code: string;
|
|
5
|
+
label: string;
|
|
6
|
+
skins?: TEmoji[];
|
|
7
|
+
tone: number | number[];
|
|
8
|
+
};
|
|
9
|
+
export declare type TEmojiGroup = {
|
|
10
|
+
name: string;
|
|
11
|
+
emojis: TEmoji[];
|
|
12
|
+
};
|
|
13
|
+
export declare type TSinchEmojiPickerElement = HTMLElement & {
|
|
14
|
+
readonly skinToneButtonRect: TRect;
|
|
15
|
+
readonly searchInputRect: TRect;
|
|
16
|
+
nthSkinToneRect(index: number): TRect | null;
|
|
17
|
+
nthTabRect(index: number): TRect | null;
|
|
18
|
+
nthEmojiRect(index: number): TRect | null;
|
|
19
|
+
/** Change value event */
|
|
20
|
+
addEventListener(type: '-change', listener: (e: CustomEvent<string>) => void): void;
|
|
21
|
+
};
|
|
22
|
+
export declare type TSinchEmojiPickerReact = TSinchElementReact<TSinchEmojiPickerElement> & {
|
|
23
|
+
/** Change value handler */
|
|
24
|
+
'on-change': (e: CustomEvent<string>) => void;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/file-drop/types.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare type TSinchFileDropReact = TSinchElementReact<TSinchFileDropEleme
|
|
|
44
44
|
/** Placeholder */
|
|
45
45
|
placeholder: string;
|
|
46
46
|
/** Change value handler */
|
|
47
|
-
'on-change'
|
|
47
|
+
'on-change': (e: CustomEvent<File[]>) => void;
|
|
48
48
|
/** Invalid handler */
|
|
49
|
-
'on-invalid'
|
|
49
|
+
'on-invalid': (e: CustomEvent<TSinchFileDropInvalidType>) => void;
|
|
50
50
|
};
|
package/file-picker/types.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export declare type TSinchFilePickerReact = TSinchElementReact<TSinchFilePickerE
|
|
|
28
28
|
/** Change value handler */
|
|
29
29
|
'on-change': (e: CustomEvent<File[]>) => void;
|
|
30
30
|
/** Invalid handler */
|
|
31
|
-
'on-invalid'
|
|
31
|
+
'on-invalid': (e: CustomEvent<TSinchFilePickerInvalidType>) => void;
|
|
32
32
|
};
|
package/icon/index.d.ts
ADDED
package/icon/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineCustomElement, getAttribute, NectaryElement, updateAttribute } from '../utils';
|
|
2
|
+
const templateHTML = '<style>:host{display:contents}#icon{display:inline-block;font-family:var(--sinch-icon-font-family);font-weight:var(--sinch-icon-font-weight);font-size:var(--sinch-size-icon, 24px);font-feature-settings:var(--sinch-icon-font-feature-settings);font-variation-settings:"FILL" 1;-webkit-font-smoothing:antialiased;line-height:1;white-space:nowrap;width:var(--sinch-size-icon,24px);height:var(--sinch-size-icon,24px);color:var(--sinch-color-icon,var(--sinch-color-text-default));user-select:none}</style><span id="icon" role="img"></span>';
|
|
3
|
+
const template = document.createElement('template');
|
|
4
|
+
template.innerHTML = templateHTML;
|
|
5
|
+
defineCustomElement('sinch-icon', class extends NectaryElement {
|
|
6
|
+
#$icon;
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
const shadowRoot = this.attachShadow();
|
|
10
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
11
|
+
this.#$icon = shadowRoot.querySelector('#icon');
|
|
12
|
+
}
|
|
13
|
+
static get observedAttributes() {
|
|
14
|
+
return ['name'];
|
|
15
|
+
}
|
|
16
|
+
attributeChangedCallback(name, _, newVal) {
|
|
17
|
+
switch (name) {
|
|
18
|
+
case 'name':
|
|
19
|
+
{
|
|
20
|
+
this.#$icon.textContent = newVal;
|
|
21
|
+
updateAttribute(this.#$icon, 'aria-label', newVal);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
set name(value) {
|
|
27
|
+
updateAttribute(this, 'name', value);
|
|
28
|
+
}
|
|
29
|
+
get name() {
|
|
30
|
+
return getAttribute(this, 'name', '');
|
|
31
|
+
}
|
|
32
|
+
});
|
package/icon/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TSinchElementReact } from '../types';
|
|
2
|
+
export declare type TSinchIconElement = HTMLElement & {
|
|
3
|
+
/** Icon name */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Icon name */
|
|
6
|
+
setAttribute(name: 'name', value: string): void;
|
|
7
|
+
};
|
|
8
|
+
export declare type TSinchIconReact = TSinchElementReact<TSinchIconElement> & {
|
|
9
|
+
/** Icon name */
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
package/icon/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/icon-button/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../tooltip';
|
|
2
2
|
import { defineCustomElement, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute } from '../utils';
|
|
3
|
-
const templateHTML = '<style>:host{display:inline-block;vertical-align:middle;outline:0}button{all:initial;position:relative;display:flex;align-items:center;justify-content:center;width:48px;height:48px;cursor:pointer;contain:size;--sinch-size-icon:24px;--sinch-color-icon:var(--sinch-icon-button-color, var(--sinch-color-stormy-500));--sinch-icon-button-shape-radius:var(--sinch-shape-radius-l)}button::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;border-radius:var(--sinch-icon-button-shape-radius);pointer-events:none;box-sizing:border-box;border:1px solid transparent;background-color:transparent;mix-blend-mode:multiply}button:focus-visible::after{position:absolute;content:"";left:50%;top:50%;transform:translate(-50%,-50%);width:100%;height:100%;padding:2px;border:2px solid var(--sinch-color-border-focus);border-radius:calc(var(--sinch-icon-button-shape-radius) + 4px);pointer-events:none}@supports not selector(:focus-visible){button:focus::after{position:absolute;content:"";left:50%;top:50%;transform:translate(-50%,-50%);width:100%;height:100%;padding:2px;border:2px solid var(--sinch-color-border-focus);border-radius:calc(var(--sinch-icon-button-shape-radius) + 4px);pointer-events:none}}button:enabled:hover::before{background-color:#f1f2f4}button:enabled:active::before{background-color:#e3e6e8}button:disabled{background-color:transparent;cursor:initial;--sinch-color-spinner-bg:var(--sinch-color-snow-200);--sinch-color-spinner-fg:var(--sinch-color-stormy-200);--sinch-color-icon:var(--sinch-color-stormy-100)}:host([small]:not([small=false])) #button{width:32px;height:32px;--sinch-icon-button-shape-radius:var(--sinch-shape-radius-m)}button>*{pointer-events:none}</style><sinch-tooltip id="tooltip"><button id="button"><slot name="icon"></slot></button></sinch-tooltip>';
|
|
3
|
+
const templateHTML = '<style>:host{display:inline-block;vertical-align:middle;outline:0}button{all:initial;position:relative;display:flex;align-items:center;justify-content:center;width:48px;height:48px;cursor:pointer;contain:size;--sinch-size-icon:24px;--sinch-color-icon:var(--sinch-icon-button-color, var(--sinch-color-stormy-500));--sinch-icon-button-shape-radius:var(--sinch-shape-radius-l)}button::before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;border-radius:var(--sinch-icon-button-shape-radius);pointer-events:none;box-sizing:border-box;border:1px solid transparent;background-color:transparent;mix-blend-mode:multiply}button:focus-visible::after{position:absolute;content:"";left:50%;top:50%;transform:translate(-50%,-50%);width:100%;height:100%;padding:2px;border:2px solid var(--sinch-color-border-focus);border-radius:calc(var(--sinch-icon-button-shape-radius) + 4px);pointer-events:none}@supports not selector(:focus-visible){button:focus::after{position:absolute;content:"";left:50%;top:50%;transform:translate(-50%,-50%);width:100%;height:100%;padding:2px;border:2px solid var(--sinch-color-border-focus);border-radius:calc(var(--sinch-icon-button-shape-radius) + 4px);pointer-events:none}}button:enabled:hover::before{background-color:#f1f2f4}button:enabled:active::before{background-color:#e3e6e8}button:disabled{background-color:transparent;cursor:initial;--sinch-color-spinner-bg:var(--sinch-color-snow-200);--sinch-color-spinner-fg:var(--sinch-color-stormy-200);--sinch-color-icon:var(--sinch-color-stormy-100)}:host([small]:not([small=false])) #button{width:32px;height:32px;--sinch-icon-button-shape-radius:var(--sinch-shape-radius-m)}button>*{pointer-events:none}</style><sinch-tooltip id="tooltip" inverted><button id="button"><slot name="icon"></slot></button></sinch-tooltip>';
|
|
4
4
|
const template = document.createElement('template');
|
|
5
5
|
template.innerHTML = templateHTML;
|
|
6
6
|
defineCustomElement('sinch-icon-button', class extends NectaryElement {
|
package/package.json
CHANGED
package/pop/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import dialogPolyfill from 'dialog-polyfill';
|
|
2
|
-
import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, getRect, isAttrTrue, updateLiteralAttribute, getReactEventHandler, updateBooleanAttribute, NectaryElement, throttleAnimationFrame, isElementFocused, updateIntegerAttribute, getIntegerAttribute, getFirstFocusableElement, getFirstSlotElement } from '../utils';
|
|
3
|
-
import { Context, dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils/context';
|
|
2
|
+
import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, getRect, isAttrTrue, updateLiteralAttribute, getReactEventHandler, updateBooleanAttribute, NectaryElement, throttleAnimationFrame, isElementFocused, updateIntegerAttribute, getIntegerAttribute, getFirstFocusableElement, getFirstSlotElement, Context, dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils';
|
|
4
3
|
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}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}#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" aria-haspopup="dialog" aria-expanded="false" name="target"></slot><div id="focus" tabindex="-1"></div><dialog id="dialog"><div id="content"><slot name="content"></slot></div><div id="target-open"><slot name="target-open"></slot></div></dialog>';
|
|
5
4
|
import { assertOrientation, disableScroll, enableScroll, orientationValues } from './utils';
|
|
6
5
|
const template = document.createElement('template');
|
package/popover/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
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';
|
|
2
|
+
import { defineCustomElement, getBooleanAttribute, getLiteralAttribute, updateLiteralAttribute, updateBooleanAttribute, NectaryElement, updateAttribute, getReactEventHandler, isAttrTrue, setClass, rectOverlap, dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils';
|
|
4
3
|
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
4
|
import { assertOrientation, getPopOrientation, orientationValues } from './utils';
|
|
6
5
|
const TIP_SIZE = 16;
|
package/segment/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../title';
|
|
2
2
|
import { getTitleLevelFromType } from '../title/utils';
|
|
3
3
|
import { defineCustomElement, getAttribute, getBooleanAttribute, getLiteralAttribute, getRect, isAttrTrue, NectaryElement, setClass, updateAttribute, updateBooleanAttribute, updateLiteralAttribute } from '../utils';
|
|
4
|
-
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;gap:
|
|
4
|
+
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;gap:12px;width:100%;height:100%;border:1px solid var(--sinch-color-snow-700);border-radius:var(--sinch-shape-radius-l);box-sizing:border-box;background-color:var(--sinch-color-snow-100);color:var(--sinch-color-text-default);font:var(--sinch-font-body);box-shadow:var(--sinch-elevation-level-2);padding:8px 0 16px}#header{display:flex;flex-direction:row;align-items:center;height:48px;gap:8px;padding:0 24px;--sinch-size-icon:32px}#caption{color:var(--sinch-color-text-default);min-width:1em}#info{display:flex;flex-direction:row;align-items:center;gap:8px;margin-left:auto;align-self:stretch}#info.empty{display:none}#preview{flex:1;flex-basis:auto;height:48px;min-width:0;overflow:hidden;margin-left:24px}#preview.empty{display:none}#info.empty+#collapse{margin-left:auto}#collapse.empty{display:none}#preview:not(.empty)+#info.empty+#collapse:not(.empty),#preview:not(.empty)+#info:not(.empty){margin-left:24px}#content-wrapper{flex:1;min-height:0;overflow-y:auto;padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;padding:0 24px}#action.empty{display:none}:host([collapsed]:not([collapsed=false])) :is(#content-wrapper,#action){display:none}:host([collapsed]:not([collapsed=false])) #wrapper{padding-bottom:8px}::slotted([slot=icon]){margin-right:8px}</style><div id="wrapper"><div id="header"><slot name="icon"></slot><sinch-title id="caption" level="3" type="m" ellipsis></sinch-title><div id="preview"><slot name="preview"></slot></div><div id="info"><slot name="info"></slot></div><div id="collapse"><slot name="collapse"></slot></div></div><div id="content-wrapper"><slot name="content"></slot></div><div id="action"><slot name="action"></slot></div></div>';
|
|
5
5
|
import { assertSize, sizeValues } from './utils';
|
|
6
6
|
const template = document.createElement('template');
|
|
7
7
|
template.innerHTML = templateHTML;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineCustomElement, getAttribute, getBooleanAttribute,
|
|
1
|
+
import { defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getReactEventHandler, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv } from '../utils';
|
|
2
2
|
const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:row}</style><div id="wrapper"><slot></slot></div>';
|
|
3
3
|
const template = document.createElement('template');
|
|
4
4
|
template.innerHTML = templateHTML;
|
|
@@ -69,9 +69,9 @@ defineCustomElement('sinch-segmented-icon-control', class extends NectaryElement
|
|
|
69
69
|
};
|
|
70
70
|
#onValueChange(csv) {
|
|
71
71
|
if (this.multiple) {
|
|
72
|
-
const values =
|
|
72
|
+
const values = unpackCsv(csv);
|
|
73
73
|
for (const $option of this.#$slot.assignedElements()) {
|
|
74
|
-
const isChecked = !getBooleanAttribute($option, 'disabled') && values.
|
|
74
|
+
const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
|
|
75
75
|
updateBooleanAttribute($option, 'data-checked', isChecked);
|
|
76
76
|
}
|
|
77
77
|
} else {
|