@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.
Files changed (69) hide show
  1. package/accordion/index.js +3 -3
  2. package/action-menu/index.js +1 -2
  3. package/card-container/index.js +1 -1
  4. package/color-menu/index.d.ts +3 -3
  5. package/color-menu/index.js +2 -3
  6. package/color-menu/types.d.ts +2 -2
  7. package/date-picker/index.js +173 -64
  8. package/date-picker/types.d.ts +6 -0
  9. package/date-picker/utils.d.ts +6 -3
  10. package/date-picker/utils.js +19 -4
  11. package/dialog/index.js +2 -2
  12. package/emoji/index.d.ts +11 -0
  13. package/emoji/index.js +47 -0
  14. package/emoji/types.d.ts +11 -0
  15. package/emoji/types.js +1 -0
  16. package/emoji/utils.d.ts +1 -0
  17. package/emoji/utils.js +46 -0
  18. package/emoji-picker/index.d.ts +28 -0
  19. package/emoji-picker/index.js +319 -0
  20. package/emoji-picker/types.d.ts +25 -0
  21. package/emoji-picker/types.js +1 -0
  22. package/file-drop/types.d.ts +2 -2
  23. package/file-picker/types.d.ts +1 -1
  24. package/icon/index.d.ts +11 -0
  25. package/icon/index.js +32 -0
  26. package/icon/types.d.ts +11 -0
  27. package/icon/types.js +1 -0
  28. package/icon-button/index.js +1 -1
  29. package/package.json +2 -2
  30. package/pop/index.js +1 -2
  31. package/popover/index.js +1 -2
  32. package/segment/index.js +1 -1
  33. package/segmented-icon-control/index.js +3 -3
  34. package/select-menu/index.js +3 -4
  35. package/stop-events/index.js +3 -3
  36. package/tabs/index.js +24 -76
  37. package/tabs/types.d.ts +9 -2
  38. package/tabs-icon-option/index.d.ts +11 -0
  39. package/tabs-icon-option/index.js +75 -0
  40. package/tabs-icon-option/types.d.ts +19 -0
  41. package/tabs-icon-option/types.js +1 -0
  42. package/tabs-option/index.d.ts +1 -0
  43. package/tabs-option/index.js +8 -15
  44. package/tabs-option/types.d.ts +13 -5
  45. package/theme/emoji.css +6 -0
  46. package/theme/fonts.css +9 -0
  47. package/theme/fonts.json +9 -0
  48. package/theme/icon.css +7 -0
  49. package/theme.css +2 -0
  50. package/tile-control/index.js +3 -3
  51. package/tooltip/index.js +78 -31
  52. package/utils/csv.d.ts +5 -0
  53. package/utils/csv.js +22 -0
  54. package/utils/dom.d.ts +30 -0
  55. package/utils/dom.js +143 -0
  56. package/utils/element.d.ts +12 -0
  57. package/utils/element.js +38 -0
  58. package/utils/get-react-event-handler.d.ts +1 -0
  59. package/utils/get-react-event-handler.js +8 -0
  60. package/utils/index.d.ts +8 -57
  61. package/utils/index.js +8 -301
  62. package/utils/rect.d.ts +4 -0
  63. package/utils/rect.js +29 -0
  64. package/utils/slot.d.ts +4 -0
  65. package/utils/slot.js +38 -0
  66. package/utils/throttle.d.ts +4 -0
  67. package/utils/throttle.js +25 -0
  68. /package/{utils/animation.d.ts → tooltip/tooltip-state.d.ts} +0 -0
  69. /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
+ });
@@ -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 {};
@@ -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 {};
@@ -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'?: (e: CustomEvent<File[]>) => void;
47
+ 'on-change': (e: CustomEvent<File[]>) => void;
48
48
  /** Invalid handler */
49
- 'on-invalid'?: (e: CustomEvent<TSinchFileDropInvalidType>) => void;
49
+ 'on-invalid': (e: CustomEvent<TSinchFileDropInvalidType>) => void;
50
50
  };
@@ -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'?: (e: CustomEvent<TSinchFilePickerInvalidType>) => void;
31
+ 'on-invalid': (e: CustomEvent<TSinchFilePickerInvalidType>) => void;
32
32
  };
@@ -0,0 +1,11 @@
1
+ import type { TSinchIconElement, TSinchIconReact } from './types';
2
+ declare global {
3
+ namespace JSX {
4
+ interface IntrinsicElements {
5
+ 'sinch-icon': TSinchIconReact;
6
+ }
7
+ }
8
+ interface HTMLElementTagNameMap {
9
+ 'sinch-icon': TSinchIconElement;
10
+ }
11
+ }
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
+ });
@@ -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 {};
@@ -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
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "0.42.1",
3
+ "version": "0.44.0",
4
4
  "files": [
5
5
  "theme.css",
6
6
  "theme/*.css",
7
- "theme/fonts.json",
7
+ "theme/*.json",
8
8
  "**/*/*.js",
9
9
  "**/*/*.d.ts",
10
10
  "types.d.ts",
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:16px;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 24px 16px}#header{display:flex;flex-direction:row;align-items:center;height:48px;gap:8px;--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}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px}#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>';
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, getCsvSet, getFirstCsvValue, getReactEventHandler, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv } from '../utils';
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 = getCsvSet(csv);
72
+ const values = unpackCsv(csv);
73
73
  for (const $option of this.#$slot.assignedElements()) {
74
- const isChecked = !getBooleanAttribute($option, 'disabled') && values.has(getAttribute($option, 'value', ''));
74
+ const isChecked = !getBooleanAttribute($option, 'disabled') && values.includes(getAttribute($option, 'value', ''));
75
75
  updateBooleanAttribute($option, 'data-checked', isChecked);
76
76
  }
77
77
  } else {