@nectary/components 0.44.0 → 0.45.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 (111) hide show
  1. package/accordion-item/index.js +1 -1
  2. package/action-menu/index.js +3 -11
  3. package/alert/index.js +3 -1
  4. package/avatar/index.js +12 -13
  5. package/avatar/types.d.ts +4 -4
  6. package/avatar/utils.d.ts +1 -4
  7. package/avatar/utils.js +4 -7
  8. package/badge/index.js +9 -7
  9. package/badge/types.d.ts +4 -4
  10. package/badge/utils.d.ts +1 -4
  11. package/badge/utils.js +0 -6
  12. package/button/index.js +88 -23
  13. package/button/types.d.ts +12 -11
  14. package/button/utils.d.ts +4 -1
  15. package/button/utils.js +9 -1
  16. package/card-container/index.js +1 -1
  17. package/chat-block/index.js +1 -1
  18. package/chat-bubble/index.js +1 -1
  19. package/checkbox/index.js +4 -2
  20. package/chip/index.js +7 -6
  21. package/color-menu/index.js +9 -18
  22. package/color-swatch/index.js +5 -7
  23. package/date-picker/index.js +1 -1
  24. package/dialog/index.js +4 -7
  25. package/emoji/index.js +2 -6
  26. package/emoji-picker/data.json +1 -0
  27. package/emoji-picker/index.js +6 -14
  28. package/field/index.js +1 -1
  29. package/file-status/index.js +4 -2
  30. package/flag/index.d.ts +11 -0
  31. package/flag/index.js +43 -0
  32. package/flag/types.d.ts +11 -0
  33. package/flag/types.js +1 -0
  34. package/flag/utils.d.ts +1 -0
  35. package/flag/utils.js +19 -0
  36. package/icon/index.js +1 -1
  37. package/icon-button/index.js +77 -9
  38. package/icon-button/types.d.ts +14 -6
  39. package/icon-button/utils.d.ts +5 -0
  40. package/icon-button/utils.js +9 -0
  41. package/icons/create-icon-class.js +1 -1
  42. package/inline-alert/index.js +3 -1
  43. package/input/index.d.ts +3 -0
  44. package/input/index.js +148 -71
  45. package/input/types.d.ts +7 -0
  46. package/link/index.js +1 -1
  47. package/package.json +4 -6
  48. package/pagination/index.js +1 -1
  49. package/pop/index.js +15 -37
  50. package/popover/index.js +7 -9
  51. package/radio-option/index.js +1 -1
  52. package/segment/index.js +10 -6
  53. package/segment/types.d.ts +4 -4
  54. package/segment/utils.d.ts +3 -5
  55. package/segment/utils.js +14 -4
  56. package/segment-collapse/index.js +1 -1
  57. package/segmented-control-option/index.js +1 -1
  58. package/segmented-icon-control-option/index.js +1 -1
  59. package/select-button/index.js +93 -28
  60. package/select-button/types.d.ts +8 -1
  61. package/select-menu/index.d.ts +5 -0
  62. package/select-menu/index.js +46 -17
  63. package/select-menu-option/index.d.ts +1 -0
  64. package/select-menu-option/index.js +3 -0
  65. package/select-menu-option/types.d.ts +1 -0
  66. package/spinner/index.js +52 -7
  67. package/spinner/types.d.ts +4 -5
  68. package/stop-events/index.js +9 -5
  69. package/table/index.js +1 -1
  70. package/tag/index.js +2 -6
  71. package/text/index.js +3 -1
  72. package/textarea/index.js +1 -1
  73. package/theme/button.css +146 -0
  74. package/{theme.css → theme/contextual.css} +3 -25
  75. package/theme/elevation.css +1 -1
  76. package/theme/flag.css +4 -0
  77. package/theme/fonts.css +0 -33
  78. package/theme/fonts.json +0 -33
  79. package/theme/icon-button.css +68 -0
  80. package/theme/index.css +4 -0
  81. package/theme/index.d.ts +21 -0
  82. package/theme/index.js +21 -0
  83. package/theme/input.css +7 -0
  84. package/theme/select-button.css +7 -0
  85. package/theme/shapes.css +4 -3
  86. package/theme/size.css +9 -0
  87. package/theme/spinner.css +7 -0
  88. package/theme/typography.css +7 -7
  89. package/tile-control-option/index.js +1 -1
  90. package/time-picker/index.js +1 -1
  91. package/title/index.js +7 -3
  92. package/toast/index.js +3 -1
  93. package/toggle/index.js +1 -1
  94. package/tooltip/index.js +8 -6
  95. package/utils/context.d.ts +14 -9
  96. package/utils/context.js +60 -26
  97. package/utils/countries.d.ts +5 -0
  98. package/utils/countries.js +2 -0
  99. package/utils/countries.json +998 -0
  100. package/utils/debounce.d.ts +4 -0
  101. package/utils/debounce.js +21 -0
  102. package/utils/element.d.ts +4 -0
  103. package/utils/element.js +10 -0
  104. package/utils/index.d.ts +1 -0
  105. package/utils/index.js +1 -0
  106. package/utils/size.d.ts +10 -0
  107. package/utils/size.js +19 -0
  108. package/utils/throttle.d.ts +2 -2
  109. package/utils/throttle.js +4 -9
  110. package/spinner/utils.d.ts +0 -2
  111. package/spinner/utils.js +0 -1
@@ -1,5 +1,3 @@
1
- import type { TSinchSegmentSize } from './types';
2
- export declare const sizeValues: readonly TSinchSegmentSize[];
3
- declare type TAssertSize = (value: string | null) => asserts value is TSinchSegmentSize;
4
- export declare const assertSize: TAssertSize;
5
- export {};
1
+ import type { TSinchTitleType } from '../title/types';
2
+ import type { TSinchSize } from '../utils/size';
3
+ export declare const getTitleTypeFromSize: (size: TSinchSize) => TSinchTitleType;
package/segment/utils.js CHANGED
@@ -1,6 +1,16 @@
1
- export const sizeValues = ['l', 'm', 's'];
2
- export const assertSize = value => {
3
- if (value === null || !sizeValues.includes(value)) {
4
- throw new Error(`sinch-segment: invalid size attribute: ${value}`);
1
+ export const getTitleTypeFromSize = size => {
2
+ switch (size) {
3
+ case 'l':
4
+ {
5
+ return 'l';
6
+ }
7
+ case 's':
8
+ {
9
+ return 's';
10
+ }
11
+ default:
12
+ {
13
+ return 'm';
14
+ }
5
15
  }
6
16
  };
@@ -2,7 +2,7 @@ import '../icons/expand-less';
2
2
  import '../icons/expand-more';
3
3
  import '../icon-button';
4
4
  import { defineCustomElement, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
5
- const templateHTML = '<style>:host{display:block;--sinch-size-icon:32px}#up{display:block}#down{display:none}:host([value]:not([value=false])) #up{display:none}:host([value]:not([value=false])) #down{display:block}</style><sinch-icon-button id="button" small><sinch-icon-expand-less id="up" slot="icon"></sinch-icon-expand-less><sinch-icon-expand-more id="down" slot="icon"></sinch-icon-expand-more></sinch-icon-button>';
5
+ const templateHTML = '<style>:host{display:block;--sinch-size-icon:32px}#up{display:block}#down{display:none}:host([value]:not([value=false])) #up{display:none}:host([value]:not([value=false])) #down{display:block}</style><sinch-icon-button id="button" size="s"><sinch-icon-expand-less id="up" slot="icon"></sinch-icon-expand-less><sinch-icon-expand-more id="down" slot="icon"></sinch-icon-expand-more></sinch-icon-button>';
6
6
  const template = document.createElement('template');
7
7
  template.innerHTML = templateHTML;
8
8
  defineCustomElement('sinch-segment-collapse', class extends NectaryElement {
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;position:relative;flex-direction:row;align-items:center;gap:12px;width:100%;height:32px;padding:0 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:16px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}#content{font:var(--sinch-font-title-s);flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:0;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-3px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}</style><div id="wrapper"><slot name="icon"></slot><label for="button" id="content"></label><button id="button"></button></div>';
2
+ const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;position:relative;flex-direction:row;align-items:center;gap:12px;width:100%;height:32px;padding:0 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:16px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}#content{font:var(--sinch-font-title-s);flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:0;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-3px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}</style><div id="wrapper"><slot name="icon"></slot><label for="button" id="content"></label><button id="button"></button></div>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-segmented-control-option', class extends NectaryElement {
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:block;outline:0}#wrapper{position:relative;width:56px;height:32px;padding:3px 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:24px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:-1px;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-4px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}@supports not selector(:focus-visible){#button:focus::before{border-width:2px}}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}::slotted(*){display:block}</style><div id="wrapper"><slot name="icon"></slot><button id="button"></button></div>';
2
+ const templateHTML = '<style>:host{display:block;outline:0}#wrapper{position:relative;width:56px;height:32px;padding:3px 16px;box-sizing:border-box;border:1px solid var(--sinch-color-snow-600);border-left-width:0;border-right-width:0;color:var(--sinch-color-stormy-500);background-color:var(--sinch-color-snow-100);--sinch-color-icon:var(--sinch-color-stormy-500);--sinch-size-icon:24px}#wrapper:hover{background-color:var(--sinch-color-snow-400)}:host(:first-child) #wrapper{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}:host(:last-child) #wrapper{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}:host([data-checked]) #wrapper{border-color:var(--sinch-color-stormy-500)}:host([data-checked]:not(:first-child)) #wrapper::before{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;left:-1px;top:-1px;bottom:-1px}:host([data-checked]:not(:last-child)) #wrapper::after{content:"";width:1px;background-color:var(--sinch-color-stormy-500);position:absolute;right:0;top:-1px;bottom:-1px}#button{all:initial;position:absolute;left:0;top:0;box-sizing:border-box;width:100%;height:100%;cursor:pointer;z-index:1}#button:disabled{cursor:unset}#button:focus::before{content:"";position:absolute;left:-4px;right:-3px;top:-4px;bottom:-4px;border-style:solid;border-color:var(--sinch-color-border-focus);border-width:0}#button:focus-visible::before{border-width:2px}:host(:first-child) #button:focus::before{left:-4px;border-top-left-radius:6px;border-bottom-left-radius:6px}:host(:last-child) #button:focus::before{right:-4px;border-top-right-radius:6px;border-bottom-right-radius:6px}:host([disabled]:not([disabled=false])) #wrapper{background-color:var(--sinch-color-snow-100);color:var(--sinch-color-stormy-100);--sinch-color-icon:var(--sinch-color-stormy-100)}::slotted(*){display:block}</style><div id="wrapper"><slot name="icon"></slot><button id="button"></button></div>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-segmented-icon-control-option', class extends NectaryElement {
@@ -1,21 +1,33 @@
1
1
  import '../text';
2
2
  import '../icons/keyboard-arrow-down';
3
- import { defineCustomElement, getAttribute, getBooleanAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute } from '../utils';
4
- const templateHTML = '<style>:host{display:inline-block;vertical-align:middle}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;gap:8px;box-sizing:border-box;width:100%;height:48px;padding:0 12px;background-color:var(--sinch-color-snow-100);border-radius:var(--sinch-shape-radius-s);--sinch-size-icon:24px;--sinch-color-icon:var(--sinch-color-stormy-500)}#button{all:initial;cursor:pointer;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1;border-radius:var(--sinch-shape-radius-s);border:1px solid var(--sinch-color-stormy-200);box-sizing:border-box}#text{flex:1;min-width:0;color:var(--sinch-color-text-muted)}:host([text]:not([text=""])) #text{color:var(--sinch-color-text-default)}#button:disabled{border-color:var(--sinch-color-snow-500);cursor:initial}#button:focus-visible{border-color:var(--sinch-color-stormy-600)}@supports not selector(:focus-visible){#button:focus{border-color:var(--sinch-color-stormy-600)}}:host([invalid]) #button:enabled{border-color:var(--sinch-color-text-invalid)}#button:disabled~#text{color:var(--sinch-color-stormy-100)}:host([disabled]) #wrapper{--sinch-color-icon:var(--sinch-color-stormy-100)}</style><div id="wrapper"><button id="button"></button><slot name="icon"></slot><sinch-text id="text" type="m" ellipsis></sinch-text><sinch-icon-keyboard-arrow-down></sinch-icon-keyboard-arrow-down></div>';
3
+ import { defineCustomElement, getAttribute, getBooleanAttribute, getLiteralAttribute, getReactEventHandler, isAttrTrue, NectaryElement, setClass, subscribeContext, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateLiteralAttribute, Context } from '../utils';
4
+ import { assertSize, DEFAULT_SIZE, sizeValues } from '../utils/size';
5
+ const templateHTML = '<style>:host{display:inline-block;vertical-align:middle;outline:0;--sinch-size:var(--sinch-size-m);--sinch-size-icon:var(--sinch-select-button-icon-size-m);--sinch-shape-radius:var(--sinch-shape-radius-m)}:host([data-size="l"]){--sinch-size:var(--sinch-size-l);--sinch-size-icon:var(--sinch-select-button-icon-size-l);--sinch-shape-radius:var(--sinch-shape-radius-l)}:host([data-size="m"]){--sinch-size:var(--sinch-size-m);--sinch-size-icon:var(--sinch-select-button-icon-size-m);--sinch-shape-radius:var(--sinch-shape-radius-m)}:host([data-size="s"]){--sinch-size:var(--sinch-size-s);--sinch-size-icon:var(--sinch-select-button-icon-size-s);--sinch-shape-radius:var(--sinch-shape-radius-s)}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;gap:8px;box-sizing:border-box;width:100%;height:var(--sinch-size);padding:0 8px 0 12px;background-color:var(--sinch-color-bg-primary-light);border-radius:var(--sinch-shape-radius);--sinch-color-icon:var(--sinch-color-stormy-500)}:host([disabled]:not([disabled=false])) #wrapper{--sinch-color-icon:var(--sinch-color-text-disabled)}:host([data-size="l"]) #wrapper{padding:0 12px}:host([data-size="m"]) #wrapper{padding:0 8px 0 12px}:host([data-size="s"]) #wrapper{padding:0 4px 0 12px}#button{all:initial;cursor:pointer;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}#border{position:absolute;border:1px solid var(--sinch-color-border-dark);border-radius:var(--sinch-shape-radius);inset:0;pointer-events:none}#text{flex:1;min-width:0;color:var(--sinch-color-text-muted)}:host([text]:not([text=""])) #text{color:var(--sinch-color-text-default)}#button:disabled{cursor:initial}#button:disabled+#border{border-color:var(--sinch-color-border-disabled)}#button:focus-visible+#border{border-color:var(--sinch-color-border-focus);border-width:2px}:host([invalid]) #button:enabled+#border{border-color:var(--sinch-color-text-invalid)}#button:disabled~#text{color:var(--sinch-color-text-disabled)}#left{display:flex;flex-direction:row;align-self:stretch;align-items:center;gap:4px;margin-left:-4px}#left.empty{display:none}#dropdown-icon{margin-left:-4px}</style><div id="wrapper"><button id="button"></button><div id="border"></div><div id="left"><slot name="left"></slot></div><slot name="icon"></slot><sinch-text id="text" type="m" ellipsis></sinch-text><sinch-icon-keyboard-arrow-down id="dropdown-icon"></sinch-icon-keyboard-arrow-down></div>';
5
6
  const template = document.createElement('template');
6
7
  template.innerHTML = templateHTML;
7
8
  defineCustomElement('sinch-select-button', class extends NectaryElement {
8
9
  #$button;
9
10
  #$text;
11
+ #$leftSlot;
12
+ #$leftWrapper;
13
+ #$wrapper;
10
14
  #controller = null;
15
+ #sizeContext;
11
16
  constructor() {
12
17
  super();
13
- const shadowRoot = this.attachShadow();
18
+ const shadowRoot = this.attachShadow({
19
+ delegatesFocus: true
20
+ });
14
21
  shadowRoot.appendChild(template.content.cloneNode(true));
15
22
  this.#$button = shadowRoot.querySelector('#button');
16
23
  this.#$text = shadowRoot.querySelector('#text');
24
+ this.#$leftSlot = shadowRoot.querySelector('slot[name="left"]');
25
+ this.#$leftWrapper = shadowRoot.querySelector('#left');
26
+ this.#$wrapper = shadowRoot.querySelector('#wrapper');
27
+ this.#sizeContext = new Context(this.#$wrapper, 'size');
17
28
  }
18
29
  connectedCallback() {
30
+ super.connectedCallback();
19
31
  this.#controller = new AbortController();
20
32
  const {
21
33
  signal
@@ -39,36 +51,20 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
39
51
  this.addEventListener('-blur', this.#onBlurReactHandler, {
40
52
  signal
41
53
  });
54
+ this.#$leftSlot.addEventListener('slotchange', this.#onLeftSlotChange, {
55
+ signal
56
+ });
57
+ this.#sizeContext.listen(this.#controller.signal);
58
+ subscribeContext(this, 'size', this.#onContextSize, signal);
59
+ this.#onLeftSlotChange();
60
+ this.#onSizeUpdate();
42
61
  }
43
62
  disconnectedCallback() {
63
+ super.disconnectedCallback();
44
64
  this.#controller.abort();
45
65
  }
46
66
  static get observedAttributes() {
47
- return ['text', 'placeholder', 'invalid', 'disabled'];
48
- }
49
- set text(value) {
50
- updateAttribute(this, 'text', value);
51
- }
52
- get text() {
53
- return getAttribute(this, 'text', '');
54
- }
55
- set placeholder(value) {
56
- updateAttribute(this, 'placeholder', value);
57
- }
58
- get placeholder() {
59
- return getAttribute(this, 'placeholder');
60
- }
61
- set invalid(isInvalid) {
62
- updateBooleanAttribute(this, 'invalid', isInvalid);
63
- }
64
- get invalid() {
65
- return getBooleanAttribute(this, 'invalid');
66
- }
67
- set disabled(isDisabled) {
68
- updateBooleanAttribute(this, 'disabled', isDisabled);
69
- }
70
- get disabled() {
71
- return getBooleanAttribute(this, 'disabled');
67
+ return ['text', 'placeholder', 'invalid', 'disabled', 'size', 'data-size'];
72
68
  }
73
69
  attributeChangedCallback(name, oldVal, newVal) {
74
70
  if (oldVal === newVal) {
@@ -103,8 +99,51 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
103
99
  updateBooleanAttribute(this, 'disabled', isDisabled);
104
100
  break;
105
101
  }
102
+ case 'size':
103
+ {
104
+ updateAttribute(this, 'data-size', newVal);
105
+ break;
106
+ }
107
+ case 'data-size':
108
+ {
109
+ if ('production' !== 'production') {
110
+ assertSize(newVal, 'sinch-select-button');
111
+ }
112
+ this.#onSizeUpdate();
113
+ break;
114
+ }
106
115
  }
107
116
  }
117
+ set text(value) {
118
+ updateAttribute(this, 'text', value);
119
+ }
120
+ get text() {
121
+ return getAttribute(this, 'text', '');
122
+ }
123
+ set placeholder(value) {
124
+ updateAttribute(this, 'placeholder', value);
125
+ }
126
+ get placeholder() {
127
+ return getAttribute(this, 'placeholder');
128
+ }
129
+ set invalid(isInvalid) {
130
+ updateBooleanAttribute(this, 'invalid', isInvalid);
131
+ }
132
+ get invalid() {
133
+ return getBooleanAttribute(this, 'invalid');
134
+ }
135
+ set disabled(isDisabled) {
136
+ updateBooleanAttribute(this, 'disabled', isDisabled);
137
+ }
138
+ get disabled() {
139
+ return getBooleanAttribute(this, 'disabled');
140
+ }
141
+ set size(size) {
142
+ updateLiteralAttribute(this, sizeValues, 'size', size);
143
+ }
144
+ get size() {
145
+ return getLiteralAttribute(this, sizeValues, 'size', DEFAULT_SIZE);
146
+ }
108
147
  get focusable() {
109
148
  return true;
110
149
  }
@@ -114,6 +153,32 @@ defineCustomElement('sinch-select-button', class extends NectaryElement {
114
153
  blur() {
115
154
  this.#$button.blur();
116
155
  }
156
+ #onContextSize = e => {
157
+ if (this.hasAttribute('size')) {
158
+ return;
159
+ }
160
+ switch (e.detail) {
161
+ case 'l':
162
+ {
163
+ this.setAttribute('data-size', 'm');
164
+ break;
165
+ }
166
+ default:
167
+ {
168
+ this.setAttribute('data-size', 's');
169
+ }
170
+ }
171
+ };
172
+ #onSizeUpdate() {
173
+ if (!this.isConnected) {
174
+ return;
175
+ }
176
+ const size = this.getAttribute('data-size') ?? DEFAULT_SIZE;
177
+ this.#sizeContext.dispatch(size);
178
+ }
179
+ #onLeftSlotChange = () => {
180
+ setClass(this.#$leftWrapper, 'empty', this.#$leftSlot.assignedElements().length === 0);
181
+ };
117
182
  #onButtonFocus = () => {
118
183
  this.dispatchEvent(new CustomEvent('-focus'));
119
184
  };
@@ -1,4 +1,5 @@
1
1
  import type { TSinchElementReact } from '../types';
2
+ import type { TSinchSize } from '../utils/size';
2
3
  export declare type TSinchSelectButtonElement = HTMLElement & {
3
4
  /** Text */
4
5
  text: string;
@@ -8,6 +9,8 @@ export declare type TSinchSelectButtonElement = HTMLElement & {
8
9
  invalid: boolean;
9
10
  /** Disabled */
10
11
  disabled: boolean;
12
+ /** Size, `m` by default */
13
+ size: TSinchSize;
11
14
  /** Click event */
12
15
  addEventListener(type: '-click', listener: (e: CustomEvent<void>) => void): void;
13
16
  /** Focus event */
@@ -22,6 +25,8 @@ export declare type TSinchSelectButtonElement = HTMLElement & {
22
25
  setAttribute(name: 'invalid', value: ''): void;
23
26
  /** Disabled */
24
27
  setAttribute(name: 'disabled', value: ''): void;
28
+ /** Size, `m` by default */
29
+ setAttribute(name: 'size', value: TSinchSize): void;
25
30
  };
26
31
  export declare type TSinchSelectButtonReact = TSinchElementReact<TSinchSelectButtonElement> & {
27
32
  /** Text */
@@ -34,8 +39,10 @@ export declare type TSinchSelectButtonReact = TSinchElementReact<TSinchSelectBut
34
39
  invalid?: boolean;
35
40
  /** Disabled */
36
41
  disabled?: boolean;
42
+ /** Size, `m` by default */
43
+ size?: TSinchSize;
37
44
  /** Click handler */
38
- 'on-click'?: (e: CustomEvent<void>) => void;
45
+ 'on-click': (e: CustomEvent<void>) => void;
39
46
  /** Focus handler */
40
47
  'on-focus'?: (e: CustomEvent<void>) => void;
41
48
  /** Blur handler */
@@ -1,3 +1,8 @@
1
+ import '../input';
2
+ import '../icon-button';
3
+ import '../icons/search';
4
+ import '../icons/close';
5
+ import '../text';
1
6
  import type { TSinchSelectMenuElement, TSinchSelectMenuReact } from './types';
2
7
  declare global {
3
8
  namespace JSX {
@@ -1,18 +1,30 @@
1
- import { attrValueToPixels, defineCustomElement, dispatchContextConnectEvent, dispatchContextDisconnectEvent, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}</style><div id="listbox" role="presentation"><slot></slot></div>';
1
+ import '../input';
2
+ import '../icon-button';
3
+ import '../icons/search';
4
+ import '../icons/close';
5
+ import '../text';
6
+ import { attrValueToPixels, defineCustomElement, getAttribute, getBooleanAttribute, unpackCsv, getFirstCsvValue, getIntegerAttribute, getReactEventHandler, isAttrTrue, NectaryElement, updateAttribute, updateBooleanAttribute, updateCsv, updateExplicitBooleanAttribute, updateIntegerAttribute, debounceTimeout, setClass, subscribeContext } from '../utils';
7
+ const templateHTML = '<style>:host{display:block;outline:0}#listbox{overflow-y:auto}#search{display:none;margin:10px}#search.active{display:block}#not-found{display:none;width:100%;height:30px;align-items:center;justify-content:center;margin-bottom:10px;color:var(--sinch-color-text-muted);pointer-events:none;user-select:none}#not-found.active{display:flex}::slotted(.hidden){display:none}</style><sinch-input id="search" size="s" placeholder="Search"><sinch-icon-search slot="icon"></sinch-icon-search></sinch-input><div id="not-found"><sinch-text type="m">No results</sinch-text></div><div id="listbox" role="presentation"><slot></slot></div>';
3
8
  const ITEM_HEIGHT = 40;
9
+ const NUM_ITEMS_SEARCH = 20;
4
10
  const template = document.createElement('template');
5
11
  template.innerHTML = templateHTML;
6
12
  defineCustomElement('sinch-select-menu', class extends NectaryElement {
7
13
  #$optionSlot;
8
14
  #$listbox;
15
+ #$search;
16
+ #$notFound;
9
17
  #controller = null;
18
+ #searchDebounce;
10
19
  constructor() {
11
20
  super();
12
21
  const shadowRoot = this.attachShadow();
13
22
  shadowRoot.appendChild(template.content.cloneNode(true));
14
23
  this.#$optionSlot = shadowRoot.querySelector('slot');
15
24
  this.#$listbox = shadowRoot.querySelector('#listbox');
25
+ this.#$search = shadowRoot.querySelector('#search');
26
+ this.#$notFound = shadowRoot.querySelector('#not-found');
27
+ this.#searchDebounce = debounceTimeout(200)(this.#updateSearch);
16
28
  }
17
29
  connectedCallback() {
18
30
  this.#controller = new AbortController();
@@ -24,9 +36,6 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
24
36
  this.addEventListener('keydown', this.#onListboxKeyDown, {
25
37
  signal
26
38
  });
27
- this.addEventListener('-keydown', this.#onContexKeydown, {
28
- signal
29
- });
30
39
  this.addEventListener('blur', this.#onListboxBlur, {
31
40
  signal
32
41
  });
@@ -36,22 +45,21 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
36
45
  this.#$listbox.addEventListener('click', this.#onListboxClick, {
37
46
  signal
38
47
  });
39
- this.#$optionSlot.addEventListener('slotchange', this.#onOptionSlotChange, {
48
+ this.#$search.addEventListener('-change', this.#onSearchChange, {
40
49
  signal
41
50
  });
42
- this.addEventListener('-change', this.#onChangeReactHandler, {
51
+ this.#$optionSlot.addEventListener('slotchange', this.#onOptionSlotChange, {
43
52
  signal
44
53
  });
45
- this.addEventListener('-visibility', this.#onContextVisibility, {
54
+ this.addEventListener('-change', this.#onChangeReactHandler, {
46
55
  signal
47
56
  });
48
- dispatchContextConnectEvent(this, 'keydown');
49
- dispatchContextConnectEvent(this, 'visibility');
57
+ subscribeContext(this, 'keydown', this.#onContextKeyDown, signal);
58
+ subscribeContext(this, 'visibility', this.#onContextVisibility, signal);
50
59
  }
51
60
  disconnectedCallback() {
52
- dispatchContextDisconnectEvent(this, 'keydown');
53
- dispatchContextDisconnectEvent(this, 'visibility');
54
61
  this.#controller.abort();
62
+ this.#searchDebounce.cancel();
55
63
  }
56
64
  static get observedAttributes() {
57
65
  return ['value', 'rows', 'multiple'];
@@ -115,7 +123,23 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
115
123
  this.#selectOption($elem);
116
124
  }
117
125
  };
118
- #onContexKeydown = e => {
126
+ #onSearchChange = e => {
127
+ this.#$search.value = e.detail;
128
+ this.#searchDebounce.fn();
129
+ };
130
+ #updateSearch = () => {
131
+ const searchValue = this.#$search.value.toLowerCase();
132
+ const $options = this.#getOptionElements();
133
+ let someFound = false;
134
+ for (const $opt of $options) {
135
+ const isHidden = searchValue.length > 0 && !$opt.matchesSearch(searchValue);
136
+ someFound ||= !isHidden;
137
+ setClass($opt, 'hidden', isHidden);
138
+ }
139
+ setClass(this.#$notFound, 'active', !someFound);
140
+ this.#selectOption(null);
141
+ };
142
+ #onContextKeyDown = e => {
119
143
  this.#handleKeydown(e.detail);
120
144
  };
121
145
  #onContextVisibility = e => {
@@ -155,6 +179,11 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
155
179
  }
156
180
  }
157
181
  #onOptionSlotChange = () => {
182
+ const isSearchActive = this.#$optionSlot.assignedElements().length >= NUM_ITEMS_SEARCH;
183
+ if (!isSearchActive) {
184
+ updateAttribute(this.#$search, 'value', null);
185
+ }
186
+ setClass(this.#$search, 'active', isSearchActive);
158
187
  this.#onValueChange(this.value);
159
188
  };
160
189
  #onValueChange(csv) {
@@ -176,7 +205,7 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
176
205
  const $options = this.#getOptionElements();
177
206
  for (let i = 0; i < $options.length; i++) {
178
207
  const el = $options[i];
179
- if (!getBooleanAttribute(el, 'disabled')) {
208
+ if (!getBooleanAttribute(el, 'disabled') && !el.classList.contains('hidden')) {
180
209
  return el;
181
210
  }
182
211
  }
@@ -186,7 +215,7 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
186
215
  const $options = this.#getOptionElements();
187
216
  for (let i = $options.length - 1; i >= 0; i--) {
188
217
  const el = $options[i];
189
- if (!getBooleanAttribute(el, 'disabled')) {
218
+ if (!getBooleanAttribute(el, 'disabled') && !el.classList.contains('hidden')) {
190
219
  return el;
191
220
  }
192
221
  }
@@ -198,7 +227,7 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
198
227
  const $options = this.#getOptionElements();
199
228
  for (let i = 1; i <= $options.length; i++) {
200
229
  const el = $options[(i + index) % $options.length];
201
- if (!getBooleanAttribute(el, 'disabled')) {
230
+ if (!getBooleanAttribute(el, 'disabled') && !el.classList.contains('hidden')) {
202
231
  return el;
203
232
  }
204
233
  }
@@ -211,7 +240,7 @@ defineCustomElement('sinch-select-menu', class extends NectaryElement {
211
240
  const $options = this.#getOptionElements();
212
241
  for (let i = 1; i <= $options.length; i++) {
213
242
  const el = $options[(index - i + $options.length) % $options.length];
214
- if (!getBooleanAttribute(el, 'disabled')) {
243
+ if (!getBooleanAttribute(el, 'disabled') && !el.classList.contains('hidden')) {
215
244
  return el;
216
245
  }
217
246
  }
@@ -14,6 +14,7 @@ export declare class SelectMenuOption extends NectaryElement {
14
14
  get text(): string;
15
15
  set disabled(isDisabled: boolean);
16
16
  get disabled(): boolean;
17
+ matchesSearch(searchValue: string): boolean;
17
18
  }
18
19
  declare global {
19
20
  namespace JSX {
@@ -58,5 +58,8 @@ export class SelectMenuOption extends NectaryElement {
58
58
  get disabled() {
59
59
  return getBooleanAttribute(this, 'disabled');
60
60
  }
61
+ matchesSearch(searchValue) {
62
+ return this.text.toLowerCase().includes(searchValue);
63
+ }
61
64
  }
62
65
  defineCustomElement('sinch-select-menu-option', SelectMenuOption);
@@ -6,6 +6,7 @@ export declare type TSinchSelectMenuOptionElement = HTMLElement & {
6
6
  text: string;
7
7
  /** Disabled state */
8
8
  disabled: boolean;
9
+ matchesSearch(value: string): boolean;
9
10
  /** Value */
10
11
  setAttribute(name: 'value', value: string): void;
11
12
  /** Display text */
package/spinner/index.js CHANGED
@@ -1,22 +1,67 @@
1
- import { defineCustomElement, getLiteralAttribute, NectaryElement, updateLiteralAttribute } from '../utils';
2
- const templateHTML = '<style>:host{display:inline-block;vertical-align:middle}@keyframes spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}div{animation:1.5s linear infinite spinner;animation-play-state:running;border:solid 3px var(--sinch-color-spinner-bg,#d4dadd);border-bottom-color:var(--sinch-color-spinner-fg,#0a273d);border-radius:50%;height:20px;width:20px;box-sizing:border-box;will-change:transform;margin:2px}:host([type=large]) div{height:48px;width:48px;border-width:4px}:host([type=small]) div{height:14px;width:14px;border-width:2px;margin:1px}:host([static]:not([static=false])) div{animation-play-state:paused}</style><div></div>';
3
- import { spinnerTypes } from './utils';
1
+ import { defineCustomElement, getLiteralAttribute, NectaryElement, subscribeContext, updateAttribute, updateLiteralAttribute } from '../utils';
2
+ import { assertSize, DEFAULT_SIZE, sizeValues } from '../utils/size';
3
+ const templateHTML = '<style>@keyframes spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}:host{display:block;--sinch-size-icon:var(--sinch-spinner-size-m);--sinch-spinner-thickness:3px}:host([data-size="l"]){--sinch-size-icon:var(--sinch-spinner-size-l);--sinch-spinner-thickness:4px}:host([data-size="m"]){--sinch-size-icon:var(--sinch-spinner-size-m);--sinch-spinner-thickness:3px}:host([data-size="s"]){--sinch-size-icon:var(--sinch-spinner-size-s);--sinch-spinner-thickness:2px}#wrapper{position:relative;height:var(--sinch-size-icon);width:var(--sinch-size-icon)}#bg{position:absolute;inset:1px;border:solid var(--sinch-spinner-thickness) var(--sinch-color-icon);border-radius:50%;opacity:.3}#fg{position:absolute;inset:1px;border:solid var(--sinch-spinner-thickness) transparent;border-bottom-color:var(--sinch-color-icon);border-radius:50%;will-change:transform;animation:1.5s linear infinite spinner;animation-play-state:running}</style><div id="wrapper"><div id="bg"></div><div id="fg"></div></div>';
4
4
  const template = document.createElement('template');
5
5
  template.innerHTML = templateHTML;
6
6
  defineCustomElement('sinch-spinner', class extends NectaryElement {
7
+ #controller = null;
7
8
  constructor() {
8
9
  super();
9
10
  const shadowRoot = this.attachShadow();
10
11
  shadowRoot.appendChild(template.content.cloneNode(true));
11
12
  }
12
13
  connectedCallback() {
14
+ this.#controller = new AbortController();
13
15
  this.setAttribute('aria-live', 'polite');
14
16
  this.setAttribute('aria-busy', 'true');
17
+ subscribeContext(this, 'size', this.#onContextSize, this.#controller.signal);
15
18
  }
16
- set type(value) {
17
- updateLiteralAttribute(this, spinnerTypes, 'type', value);
19
+ disconnectedCallback() {
20
+ this.#controller.abort();
18
21
  }
19
- get type() {
20
- return getLiteralAttribute(this, spinnerTypes, 'type', 'medium');
22
+ static get observedAttributes() {
23
+ return ['size', 'data-size'];
21
24
  }
25
+ attributeChangedCallback(name, oldVal, newVal) {
26
+ if (oldVal === newVal) {
27
+ return;
28
+ }
29
+ switch (name) {
30
+ case 'size':
31
+ {
32
+ updateAttribute(this, 'data-size', newVal);
33
+ break;
34
+ }
35
+ case 'data-size':
36
+ {
37
+ if ('production' !== 'production') {
38
+ assertSize(newVal, 'sinch-spinner');
39
+ }
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ set size(size) {
45
+ updateLiteralAttribute(this, sizeValues, 'size', size);
46
+ }
47
+ get size() {
48
+ return getLiteralAttribute(this, sizeValues, 'size', DEFAULT_SIZE);
49
+ }
50
+ #onContextSize = e => {
51
+ if (this.hasAttribute('size')) {
52
+ return;
53
+ }
54
+ switch (e.detail) {
55
+ case 'l':
56
+ case 'm':
57
+ {
58
+ this.setAttribute('data-size', 'm');
59
+ break;
60
+ }
61
+ default:
62
+ {
63
+ this.setAttribute('data-size', 's');
64
+ }
65
+ }
66
+ };
22
67
  });
@@ -1,10 +1,9 @@
1
1
  import type { TSinchElementReact } from '../types';
2
- export declare type TSinchSpinnerType = 'large' | 'medium' | 'small';
2
+ import type { TSinchSize } from '../utils/size';
3
3
  export declare type TSinchSpinnerElement = HTMLElement & {
4
- type: TSinchSpinnerType;
5
- setAttribute(name: 'type', value: TSinchSpinnerType): void;
4
+ size: TSinchSize;
5
+ setAttribute(name: 'size', value: TSinchSize): void;
6
6
  };
7
7
  export declare type TSinchSpinnerReact = TSinchElementReact<TSinchSpinnerElement> & {
8
- type?: TSinchSpinnerType;
9
- static?: boolean;
8
+ size?: TSinchSize;
10
9
  };
@@ -1,20 +1,24 @@
1
1
  import { defineCustomElement, unpackCsv } from '../utils';
2
2
  defineCustomElement('sinch-stop-events', class extends HTMLElement {
3
+ #controller = null;
3
4
  constructor() {
4
5
  super();
5
6
  this.style.display = 'contents';
6
7
  }
7
8
  connectedCallback() {
9
+ this.#controller = new AbortController();
10
+ const {
11
+ signal
12
+ } = this.#controller;
8
13
  const events = unpackCsv(this.getAttribute('events'));
9
14
  for (const event of events) {
10
- this.addEventListener(event, this.#stopEvent);
15
+ this.addEventListener(event, this.#stopEvent, {
16
+ signal
17
+ });
11
18
  }
12
19
  }
13
20
  disconnectedCallback() {
14
- const events = unpackCsv(this.getAttribute('events'));
15
- for (const event of events) {
16
- this.removeEventListener(event, this.#stopEvent);
17
- }
21
+ this.#controller.abort();
18
22
  }
19
23
  #stopEvent = e => {
20
24
  e.stopPropagation();
package/table/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, NectaryElement } from '../utils';
2
- const templateHTML = '<style>:host{display:table;font:var(--sinch-font-body);color:var(--sinch-color-text-default);--sinch-color-icon:var(--sinch-color-text-default)}</style><slot></slot>';
2
+ const templateHTML = '<style>:host{display:table;font:var(--sinch-font-text-m);color:var(--sinch-color-text-default);--sinch-color-icon:var(--sinch-color-stormy-500)}</style><slot></slot>';
3
3
  const template = document.createElement('template');
4
4
  template.innerHTML = templateHTML;
5
5
  defineCustomElement('sinch-table', class extends NectaryElement {
package/tag/index.js CHANGED
@@ -7,7 +7,6 @@ template.innerHTML = templateHTML;
7
7
  defineCustomElement('sinch-tag', class extends NectaryElement {
8
8
  #$text;
9
9
  #$wrapper;
10
- #isConnected = false;
11
10
  constructor() {
12
11
  super();
13
12
  const shadowRoot = this.attachShadow();
@@ -16,12 +15,9 @@ defineCustomElement('sinch-tag', class extends NectaryElement {
16
15
  this.#$text = shadowRoot.querySelector('#text');
17
16
  }
18
17
  connectedCallback() {
19
- this.#isConnected = true;
18
+ super.connectedCallback();
20
19
  this.#updateColor();
21
20
  }
22
- disconnectedCallback() {
23
- this.#isConnected = false;
24
- }
25
21
  get color() {
26
22
  return getAttribute(this, 'color');
27
23
  }
@@ -58,7 +54,7 @@ defineCustomElement('sinch-tag', class extends NectaryElement {
58
54
  }
59
55
  }
60
56
  #updateColor() {
61
- if (!this.#isConnected) {
57
+ if (!this.isConnected) {
62
58
  return;
63
59
  }
64
60
  const colorName = this.color;
package/text/index.js CHANGED
@@ -46,7 +46,9 @@ defineCustomElement('sinch-text', class extends NectaryElement {
46
46
  switch (name) {
47
47
  case 'type':
48
48
  {
49
- assertType(newVal);
49
+ if ('production' !== 'production') {
50
+ assertType(newVal);
51
+ }
50
52
  break;
51
53
  }
52
54
  case 'inline':