@nectary/components 0.42.0 → 0.43.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/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 +1 -2
- package/color-menu/types.d.ts +2 -2
- 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/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 +3 -1
- package/pop/index.js +1 -2
- package/popover/index.js +1 -2
- package/segment/index.js +1 -1
- package/select-menu/index.js +1 -2
- 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/avatar.css +25 -0
- package/theme/badge.css +15 -0
- package/theme/chip.css +53 -0
- package/theme/color-swatch.css +65 -0
- package/theme/elevation.css +7 -0
- package/theme/emoji.css +6 -0
- package/theme/fonts.css +85 -0
- package/theme/fonts.json +88 -0
- package/theme/icon.css +7 -0
- package/theme/palette.css +90 -0
- package/theme/shapes.css +7 -0
- package/theme/tag.css +53 -0
- package/theme/typography.css +16 -0
- package/theme.css +2 -0
- package/tooltip/index.js +78 -31
- package/utils/csv.d.ts +3 -0
- package/utils/csv.js +21 -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/action-menu/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isSinchActionMenuOption } from '../action-menu-option/utils';
|
|
2
|
-
import { attrValueToPixels, defineCustomElement, getBooleanAttribute, getIntegerAttribute, NectaryElement, updateBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
3
|
-
import { dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils/context';
|
|
2
|
+
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getBooleanAttribute, getIntegerAttribute, NectaryElement, updateBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
4
3
|
const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}</style><div id="listbox" role="presentation"><slot></slot></div>';
|
|
5
4
|
const ITEM_HEIGHT = 40;
|
|
6
5
|
const template = document.createElement('template');
|
package/card-container/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCustomElement, NectaryElement } from '../utils';
|
|
2
|
-
const templateHTML = '<style>:host{display:block}#wrapper{height:100%;padding:
|
|
2
|
+
const templateHTML = '<style>:host{display:block}#wrapper{height:100%;padding:20px 0;box-sizing:border-box;background-color:var(--sinch-color-snow-100);border-radius:var(--sinch-shape-radius-l);border:1px solid var(--sinch-color-snow-700);font:var(--sinch-font-body)}#scroll{overflow:auto;height:100%;box-sizing:border-box;padding:4px 24px}</style><div id="wrapper"><div id="scroll"><slot></slot></div></div>';
|
|
3
3
|
const template = document.createElement('template');
|
|
4
4
|
template.innerHTML = templateHTML;
|
|
5
5
|
defineCustomElement('sinch-card-container', class extends NectaryElement {
|
package/color-menu/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import '../color-swatch';
|
|
2
2
|
import '../tooltip';
|
|
3
3
|
import '../icons/check';
|
|
4
|
-
import type {
|
|
4
|
+
import type { TSinchColorMenuElement, TSinchColorMenuReact } from './types';
|
|
5
5
|
declare global {
|
|
6
6
|
namespace JSX {
|
|
7
7
|
interface IntrinsicElements {
|
|
8
|
-
'sinch-color-menu':
|
|
8
|
+
'sinch-color-menu': TSinchColorMenuReact;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
interface HTMLElementTagNameMap {
|
|
12
|
-
'sinch-color-menu':
|
|
12
|
+
'sinch-color-menu': TSinchColorMenuElement;
|
|
13
13
|
}
|
|
14
14
|
}
|
package/color-menu/index.js
CHANGED
|
@@ -3,8 +3,7 @@ import '../tooltip';
|
|
|
3
3
|
import '../icons/check';
|
|
4
4
|
import { getSwatchColorFg } from '../color-swatch/utils';
|
|
5
5
|
import { lightColorNames, darkColorNames, vibrantColorNames } from '../theme/colors';
|
|
6
|
-
import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, getCsvSet, getIntegerAttribute, getReactEventHandler, getRect, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
7
|
-
import { dispatchContextConnectEvent, dispatchContextDisconnectEvent } from '../utils/context';
|
|
6
|
+
import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute, getCsvSet, getIntegerAttribute, getReactEventHandler, getRect, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
|
|
8
7
|
const optionTemplateHTML = '<div class="option" role="option"><sinch-tooltip inverted class="tooltip"><div class="swatch-wrapper"><sinch-color-swatch class="swatch"></sinch-color-swatch><sinch-icon-check class="check"></sinch-icon-check></div></sinch-tooltip></div>';
|
|
9
8
|
const templateHTML = '<style>:host{display:block;outline:0}#listbox{display:flex;flex-direction:row;flex-wrap:wrap;padding:4px 10px;overflow-y:auto}#listbox:empty{display:none}.option{padding:12px 6px;--sinch-color-icon:var(--sinch-color-stormy-500)}.swatch-wrapper{position:relative;cursor:pointer;width:32px;height:32px}.swatch-wrapper::after{content:"";position:absolute;width:34px;height:34px;inset:-3px;border:2px solid transparent;border-radius:50%;pointer-events:none}.option[data-selected]:not([data-checked]) .swatch-wrapper::after{border-color:var(--sinch-color-border-focus)}.check{display:none;position:absolute;left:4px;top:4px;pointer-events:none;--sinch-size-icon:24px}.option[data-checked] .check{display:block}</style><div id="listbox" role="presentation"></div>';
|
|
10
9
|
import { getParentOption } from './utils';
|
package/color-menu/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TRect, TSinchElementReact } from '../types';
|
|
2
|
-
export declare type
|
|
2
|
+
export declare type TSinchColorMenuElement = HTMLElement & {
|
|
3
3
|
/** Value */
|
|
4
4
|
value: string;
|
|
5
5
|
/** How many rows to show and scroll the rest */
|
|
@@ -20,7 +20,7 @@ export declare type TSinchSelectMenuElement = HTMLElement & {
|
|
|
20
20
|
/** How many cols to show and scroll the rest */
|
|
21
21
|
setAttribute(name: 'cols', value: string): void;
|
|
22
22
|
};
|
|
23
|
-
export declare type
|
|
23
|
+
export declare type TSinchColorMenuReact = TSinchElementReact<TSinchColorMenuElement> & {
|
|
24
24
|
/** Value */
|
|
25
25
|
value: string;
|
|
26
26
|
/** How many rows to show and scroll the rest */
|
package/dialog/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import dialogPolyfill from 'dialog-polyfill';
|
|
2
1
|
import '../icon-button';
|
|
3
2
|
import '../icons/close';
|
|
4
3
|
import '../stop-events';
|
|
5
4
|
import '../title';
|
|
5
|
+
import dialogPolyfill from 'dialog-polyfill';
|
|
6
6
|
import { defineCustomElement, getAttribute, getBooleanAttribute, getRect, isAttrTrue, updateAttribute, getReactEventHandler, NectaryElement, updateBooleanAttribute } from '../utils';
|
|
7
|
-
const templateHTML = '<style>:host{display:inline-block}dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px;max-width:var(--sinch-dialog-max-width,512px);max-height:unset;border-radius:var(--sinch-shape-radius-l);box-sizing:border-box;contain:content;background-color:var(--sinch-color-snow-100);color:var(--sinch-color-text-default);font:var(--sinch-font-body);border:none;box-shadow:var(--sinch-elevation-level-3)}dialog:not([open]){display:none}dialog+.backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#000;opacity:.55}dialog::backdrop{background-color:#000;opacity:.55}._dialog_overlay{position:fixed;top:0;right:0;bottom:0;left:0}dialog.fixed{position:fixed;top:50%;transform:translate(0,-50%)}#header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;margin-bottom:
|
|
7
|
+
const templateHTML = '<style>:host{display:inline-block}dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;max-width:var(--sinch-dialog-max-width,512px);max-height:unset;border-radius:var(--sinch-shape-radius-l);box-sizing:border-box;contain:content;background-color:var(--sinch-color-snow-100);color:var(--sinch-color-text-default);font:var(--sinch-font-body);border:none;box-shadow:var(--sinch-elevation-level-3)}dialog:not([open]){display:none}dialog+.backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#000;opacity:.55}dialog::backdrop{background-color:#000;opacity:.55}._dialog_overlay{position:fixed;top:0;right:0;bottom:0;left:0}dialog.fixed{position:fixed;top:50%;transform:translate(0,-50%)}#header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;margin-bottom:12px;padding:0 24px}#caption{color:var(--sinch-color-text-default)}#content{min-height:0;overflow:auto;max-height:var(--sinch-dialog-max-height,50vh);padding:4px 24px}#buttons{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#close{transform:translate(4px,-4px)}</style><dialog><div id="header"><sinch-title id="caption" type="m" level="3" ellipsis></sinch-title><sinch-icon-button id="close" small tabindex="0"><sinch-icon-close slot="icon"></sinch-icon-close></sinch-icon-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="buttons"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
|
|
8
8
|
const template = document.createElement('template');
|
|
9
9
|
template.innerHTML = templateHTML;
|
|
10
10
|
defineCustomElement('sinch-dialog', class extends NectaryElement {
|
package/emoji/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TSinchEmojiElement, TSinchEmojiReact } from './types';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace JSX {
|
|
4
|
+
interface IntrinsicElements {
|
|
5
|
+
'sinch-emoji': TSinchEmojiReact;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
interface HTMLElementTagNameMap {
|
|
9
|
+
'sinch-emoji': TSinchEmojiElement;
|
|
10
|
+
}
|
|
11
|
+
}
|
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/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 {};
|