@nectary/components 5.18.4 → 5.19.1

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.
@@ -0,0 +1,159 @@
1
+ import "../rich-text/index.js";
2
+ import { getAttribute, setClass, isAttrEqual, updateBooleanAttribute, isAttrTrue, updateAttribute, getBooleanAttribute } from "../utils/dom.js";
3
+ import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
+ import { getFirstSlotElement } from "../utils/slot.js";
5
+ import { getReactEventHandler } from "../utils/get-react-event-handler.js";
6
+ const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;width:100%}#top{display:flex;align-items:baseline;height:24px;margin-bottom:2px}#bottom{display:flex;flex-direction:column;align-items:baseline;width:100%}#top.empty{display:none}#label,#optional{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#label{font:var(--sinch-comp-field-font-label);color:var(--sinch-comp-field-color-default-label-initial)}#optional{flex:1;font:var(--sinch-comp-field-font-optional);color:var(--sinch-comp-field-color-default-optional-initial);text-align:right}#additional{flex:1;text-align:left;line-height:20px;margin-top:2px;white-space:normal;overflow:visible;--sinch-comp-rich-text-font:var(--sinch-comp-field-font-additional);--sinch-global-color-text:var(--sinch-comp-field-color-default-additional-initial);--sinch-comp-link-color-default-text-initial:var(--sinch-comp-field-color-default-additional-initial);--sinch-comp-link-color-default-text-hover:var(--sinch-comp-field-color-default-additional-initial)}#additional:is([text=""],:not([text])){display:none}#invalid{font:var(--sinch-comp-field-font-invalid);color:var(--sinch-comp-field-color-invalid-text-initial);line-height:20px;margin-top:2px;white-space:normal;overflow:visible;--sinch-comp-rich-text-font:var(--sinch-comp-field-font-invalid);--sinch-global-color-text:var(--sinch-comp-field-color-invalid-text-initial);--sinch-comp-link-color-default-text-initial:var(--sinch-comp-field-color-invalid-text-initial);--sinch-comp-link-color-default-text-hover:var(--sinch-comp-field-color-invalid-text-initial)}#invalid:is([text=""],:not([text])){display:none}#tooltip{align-self:center;margin:0 8px;display:flex}#tooltip.empty{display:none}:host([disabled]) #label{color:var(--sinch-comp-field-color-disabled-label-initial)}:host([disabled]) #additional{--sinch-global-color-text:var(--sinch-comp-field-color-disabled-additional-initial);--sinch-comp-link-color-default-text-initial:var(--sinch-comp-field-color-disabled-additional-initial);--sinch-comp-link-color-default-text-hover:var(--sinch-comp-field-color-disabled-additional-initial)}:host([disabled]) #optional{color:var(--sinch-comp-field-color-disabled-optional-initial)}</style><div id="wrapper"><div id="top"><label id="label" for="input"></label><div id="tooltip"><slot name="tooltip"></slot></div><span id="optional"></span></div><slot name="input"></slot><div id="bottom"><sinch-rich-text id="additional"></sinch-rich-text><sinch-rich-text id="invalid"></sinch-rich-text></div></div>';
7
+ const template = document.createElement("template");
8
+ template.innerHTML = templateHTML;
9
+ class FieldV2 extends NectaryElement {
10
+ #topSection;
11
+ #$label;
12
+ #$optionalText;
13
+ #$additionalText;
14
+ #$invalidText;
15
+ #$inputSlot;
16
+ #$tooltipWrapper;
17
+ #$tooltipSlot;
18
+ #controller = null;
19
+ constructor() {
20
+ super();
21
+ const shadowRoot = this.attachShadow();
22
+ shadowRoot.appendChild(template.content.cloneNode(true));
23
+ this.#topSection = shadowRoot.querySelector("#top");
24
+ this.#$label = shadowRoot.querySelector("#label");
25
+ this.#$optionalText = shadowRoot.querySelector("#optional");
26
+ this.#$additionalText = shadowRoot.querySelector("#additional");
27
+ this.#$invalidText = shadowRoot.querySelector("#invalid");
28
+ this.#$inputSlot = shadowRoot.querySelector('slot[name="input"]');
29
+ this.#$tooltipSlot = shadowRoot.querySelector('slot[name="tooltip"]');
30
+ this.#$tooltipWrapper = shadowRoot.querySelector("#tooltip");
31
+ }
32
+ connectedCallback() {
33
+ this.#controller = new AbortController();
34
+ const { signal } = this.#controller;
35
+ const options = { signal };
36
+ this.#shouldShowTopSection();
37
+ this.#$label.addEventListener("click", this.#onLabelClick, options);
38
+ this.#$tooltipSlot.addEventListener("slotchange", this.#onTooltipSlotChange, options);
39
+ this.#$inputSlot.addEventListener("slotchange", this.#onInputSlotChange, options);
40
+ this.#$additionalText.addEventListener("-element-click", this.#onRichTextElementClick, options);
41
+ this.#$invalidText.addEventListener("-element-click", this.#onRichTextElementClick, options);
42
+ this.addEventListener("-element-click", this.#onElementClickReactHandler, options);
43
+ }
44
+ disconnectedCallback() {
45
+ this.#controller.abort();
46
+ this.#controller = null;
47
+ }
48
+ static get observedAttributes() {
49
+ return [
50
+ "label",
51
+ "optionaltext",
52
+ "additionaltext",
53
+ "invalidtext",
54
+ "disabled"
55
+ ];
56
+ }
57
+ #shouldShowTopSection() {
58
+ const label = getAttribute(this, "label");
59
+ const optionaltext = getAttribute(this, "optionaltext");
60
+ setClass(this.#topSection, "empty", label === null && optionaltext === null);
61
+ }
62
+ attributeChangedCallback(name, oldVal, newVal) {
63
+ switch (name) {
64
+ case "label": {
65
+ this.#$label.textContent = newVal;
66
+ this.#syncInputAriaLabel(newVal);
67
+ break;
68
+ }
69
+ case "optionaltext": {
70
+ this.#$optionalText.textContent = newVal;
71
+ break;
72
+ }
73
+ case "additionaltext": {
74
+ updateAttribute(this.#$additionalText, "text", newVal);
75
+ break;
76
+ }
77
+ case "invalidtext": {
78
+ updateAttribute(this.#$invalidText, "text", newVal);
79
+ break;
80
+ }
81
+ case "disabled": {
82
+ if (isAttrEqual(oldVal, newVal)) {
83
+ break;
84
+ }
85
+ updateBooleanAttribute(this, name, isAttrTrue(newVal));
86
+ break;
87
+ }
88
+ }
89
+ this.#shouldShowTopSection();
90
+ }
91
+ set label(value) {
92
+ updateAttribute(this, "label", value);
93
+ }
94
+ get label() {
95
+ return getAttribute(this, "label");
96
+ }
97
+ set optionalText(value) {
98
+ updateAttribute(this, "optionaltext", value);
99
+ }
100
+ get optionalText() {
101
+ return getAttribute(this, "optionaltext");
102
+ }
103
+ set additionalText(value) {
104
+ updateAttribute(this, "additionaltext", value);
105
+ }
106
+ get additionalText() {
107
+ return getAttribute(this, "additionaltext");
108
+ }
109
+ set invalidText(value) {
110
+ updateAttribute(this, "invalidtext", value);
111
+ }
112
+ get invalidText() {
113
+ return getAttribute(this, "invalidtext");
114
+ }
115
+ set disabled(isDisabled) {
116
+ updateBooleanAttribute(this, "disabled", isDisabled);
117
+ }
118
+ get disabled() {
119
+ return getBooleanAttribute(this, "disabled");
120
+ }
121
+ #onRichTextElementClick = (e) => {
122
+ if (this.disabled) {
123
+ return;
124
+ }
125
+ const forwarded = new CustomEvent("-element-click");
126
+ const originalTarget = e.currentTarget;
127
+ Object.defineProperty(forwarded, "target", { value: originalTarget });
128
+ Object.defineProperty(forwarded, "currentTarget", { value: originalTarget });
129
+ this.dispatchEvent(forwarded);
130
+ };
131
+ #onElementClickReactHandler = (e) => {
132
+ getReactEventHandler(this, "on-element-click")?.(e);
133
+ getReactEventHandler(this, "onElementClick")?.(e);
134
+ };
135
+ #onLabelClick = () => {
136
+ getFirstSlotElement(this.#$inputSlot)?.focus?.();
137
+ };
138
+ #onTooltipSlotChange = () => {
139
+ setClass(this.#$tooltipWrapper, "empty", this.#$tooltipSlot.assignedElements().length === 0);
140
+ };
141
+ #syncInputAriaLabel(labelText) {
142
+ const inputElement = getFirstSlotElement(this.#$inputSlot);
143
+ if (inputElement === null) {
144
+ return;
145
+ }
146
+ if (labelText != null && labelText.length > 0) {
147
+ inputElement.setAttribute("aria-label", labelText);
148
+ } else {
149
+ inputElement.removeAttribute("aria-label");
150
+ }
151
+ }
152
+ #onInputSlotChange = () => {
153
+ this.#syncInputAriaLabel(this.#$label.textContent);
154
+ };
155
+ }
156
+ defineCustomElement("sinch-field-v2", FieldV2);
157
+ export {
158
+ FieldV2
159
+ };
@@ -0,0 +1,58 @@
1
+ import type { ElementClickedEvent } from '../rich-text/types';
2
+ import type { NectaryComponentReactByType, NectaryComponentVanillaByType, NectaryComponentReact, NectaryComponentVanilla } from '../types';
3
+ export type TSinchFieldV2Events = {
4
+ /** Forwarded click from links in additionalText / invalidText */
5
+ '-element-click'?: (e: ElementClickedEvent) => void;
6
+ };
7
+ export type TSinchFieldV2Props = {
8
+ /** Label that shows in UI */
9
+ label?: string;
10
+ /** @hidden */
11
+ optionalText?: string;
12
+ /** Additional text */
13
+ additionalText?: string;
14
+ /** Invalid text, controls the overall invalid state of the text field */
15
+ invalidText?: string;
16
+ /** Disabled */
17
+ disabled?: boolean;
18
+ };
19
+ export type TSinchFieldV2Style = {
20
+ '--sinch-comp-field-font-label'?: string;
21
+ '--sinch-comp-field-font-optional'?: string;
22
+ '--sinch-comp-field-font-additional'?: string;
23
+ '--sinch-comp-field-font-invalid'?: string;
24
+ '--sinch-comp-field-color-default-label-initial'?: string;
25
+ '--sinch-comp-field-color-default-optional-initial'?: string;
26
+ '--sinch-comp-field-color-default-additional-initial'?: string;
27
+ '--sinch-comp-field-color-disabled-label-initial'?: string;
28
+ '--sinch-comp-field-color-disabled-optional-initial'?: string;
29
+ '--sinch-comp-field-color-disabled-additional-initial'?: string;
30
+ '--sinch-comp-field-color-invalid-text-initial'?: string;
31
+ };
32
+ export type TSinchFieldV2 = {
33
+ props: TSinchFieldV2Props;
34
+ events: TSinchFieldV2Events;
35
+ style: TSinchFieldV2Style;
36
+ };
37
+ export type TSinchFieldV2Element = NectaryComponentVanillaByType<TSinchFieldV2>;
38
+ export type TSinchFieldV2React = NectaryComponentReactByType<TSinchFieldV2>;
39
+ declare global {
40
+ interface NectaryComponentMap {
41
+ 'sinch-field-v2': TSinchFieldV2;
42
+ }
43
+ interface HTMLElementTagNameMap {
44
+ 'sinch-field-v2': NectaryComponentVanilla<'sinch-field-v2'>;
45
+ }
46
+ namespace JSX {
47
+ interface IntrinsicElements {
48
+ 'sinch-field-v2': NectaryComponentReact<'sinch-field-v2'>;
49
+ }
50
+ }
51
+ }
52
+ declare module 'react' {
53
+ namespace JSX {
54
+ interface IntrinsicElements extends globalThis.JSX.IntrinsicElements {
55
+ 'sinch-field-v2': NectaryComponentReact<'sinch-field-v2'>;
56
+ }
57
+ }
58
+ }
@@ -0,0 +1 @@
1
+
package/link/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import '../code-tag';
1
2
  import '../icon';
2
3
  import { NectaryElement } from '../utils';
3
4
  export * from './types';
@@ -10,6 +11,8 @@ export declare class Link extends NectaryElement {
10
11
  attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
11
12
  get text(): string;
12
13
  set text(value: string);
14
+ get contentAsCode(): boolean;
15
+ set contentAsCode(value: boolean);
13
16
  get href(): string;
14
17
  set href(value: string);
15
18
  set 'use-history'(value: boolean);
package/link/index.js CHANGED
@@ -1,24 +1,32 @@
1
+ import "../code-tag/index.js";
1
2
  import "../icon/index.js";
2
- import { isAttrEqual, updateAttribute, updateBooleanAttribute, isAttrTrue, getAttribute, getBooleanAttribute } from "../utils/dom.js";
3
+ import { getAttribute, getBooleanAttribute, isAttrEqual, updateAttribute, updateBooleanAttribute, isAttrTrue } from "../utils/dom.js";
3
4
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
5
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
5
- const templateHTML = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}</style><a referrerpolicy="no-referer"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a>';
6
+ const templateHTML = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}button{display:none;border:none;background:0 0;padding:0;margin:0;cursor:pointer;font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap}button:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover)}button:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([disabled]) button{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled)}#button-content{white-space:var(--sinch-global-text-white-space,normal)}:host([preventdefault]:not([use-history])) a{display:none}:host([preventdefault]:not([use-history])) button{display:inline}</style><a referrerpolicy="no-referer"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a><button type="button"><span id="button-content"></span></button>';
6
7
  const template = document.createElement("template");
7
8
  template.innerHTML = templateHTML;
8
9
  class Link extends NectaryElement {
9
10
  #$anchor;
10
11
  #$text;
12
+ #$button;
13
+ #$buttonText;
11
14
  constructor() {
12
15
  super();
13
16
  const shadowRoot = this.attachShadow();
14
17
  shadowRoot.appendChild(template.content.cloneNode(true));
15
18
  this.#$anchor = shadowRoot.querySelector("a");
16
19
  this.#$text = shadowRoot.querySelector("#content");
20
+ this.#$button = shadowRoot.querySelector("button");
21
+ this.#$buttonText = shadowRoot.querySelector("#button-content");
17
22
  }
18
23
  connectedCallback() {
19
24
  this.#$anchor.addEventListener("click", this.#onAnchorClick);
20
25
  this.#$anchor.addEventListener("focus", this.#onAnchorFocus);
21
26
  this.#$anchor.addEventListener("blur", this.#onAnchorBlur);
27
+ this.#$button.addEventListener("click", this.#onButtonClick);
28
+ this.#$button.addEventListener("focus", this.#onAnchorFocus);
29
+ this.#$button.addEventListener("blur", this.#onAnchorBlur);
22
30
  this.addEventListener("-click", this.#onClickReactHandler);
23
31
  this.addEventListener("-focus", this.#onFocusReactHandler);
24
32
  this.addEventListener("-blur", this.#onBlurReactHandler);
@@ -27,6 +35,9 @@ class Link extends NectaryElement {
27
35
  this.#$anchor.removeEventListener("click", this.#onAnchorClick);
28
36
  this.#$anchor.removeEventListener("focus", this.#onAnchorFocus);
29
37
  this.#$anchor.removeEventListener("blur", this.#onAnchorBlur);
38
+ this.#$button.removeEventListener("click", this.#onButtonClick);
39
+ this.#$button.removeEventListener("focus", this.#onAnchorFocus);
40
+ this.#$button.removeEventListener("blur", this.#onAnchorBlur);
30
41
  this.removeEventListener("-click", this.#onClickReactHandler);
31
42
  this.removeEventListener("-focus", this.#onFocusReactHandler);
32
43
  this.removeEventListener("-blur", this.#onBlurReactHandler);
@@ -35,19 +46,38 @@ class Link extends NectaryElement {
35
46
  return [
36
47
  "text",
37
48
  "href",
49
+ "content-as-code",
38
50
  "use-history",
39
51
  "external",
40
52
  "standalone",
41
53
  "disabled"
42
54
  ];
43
55
  }
56
+ #renderContent() {
57
+ const text = getAttribute(this, "text", "");
58
+ const asCode = getBooleanAttribute(this, "content-as-code");
59
+ this.#$text.textContent = "";
60
+ this.#$buttonText.textContent = "";
61
+ if (asCode && text !== "") {
62
+ const $code = document.createElement("sinch-code-tag");
63
+ $code.text = text;
64
+ this.#$text.appendChild($code);
65
+ const $buttonCode = document.createElement("sinch-code-tag");
66
+ $buttonCode.text = text;
67
+ this.#$buttonText.appendChild($buttonCode);
68
+ } else {
69
+ this.#$text.textContent = text;
70
+ this.#$buttonText.textContent = text;
71
+ }
72
+ }
44
73
  attributeChangedCallback(name, oldVal, newVal) {
45
74
  if (isAttrEqual(oldVal, newVal)) {
46
75
  return;
47
76
  }
48
77
  switch (name) {
49
- case "text": {
50
- this.#$text.textContent = newVal;
78
+ case "text":
79
+ case "content-as-code": {
80
+ this.#renderContent();
51
81
  break;
52
82
  }
53
83
  case "href": {
@@ -64,7 +94,11 @@ class Link extends NectaryElement {
64
94
  }
65
95
  case "standalone":
66
96
  case "disabled": {
67
- updateBooleanAttribute(this, name, isAttrTrue(newVal));
97
+ const isTrue = isAttrTrue(newVal);
98
+ updateBooleanAttribute(this, name, isTrue);
99
+ if (name === "disabled") {
100
+ this.#$button.disabled = isTrue;
101
+ }
68
102
  break;
69
103
  }
70
104
  case "external": {
@@ -81,6 +115,12 @@ class Link extends NectaryElement {
81
115
  set text(value) {
82
116
  updateAttribute(this, "text", value);
83
117
  }
118
+ get contentAsCode() {
119
+ return getBooleanAttribute(this, "content-as-code");
120
+ }
121
+ set contentAsCode(value) {
122
+ updateBooleanAttribute(this, "content-as-code", value);
123
+ }
84
124
  get href() {
85
125
  return getAttribute(this, "href", "");
86
126
  }
@@ -117,15 +157,24 @@ class Link extends NectaryElement {
117
157
  get preventDefault() {
118
158
  return getBooleanAttribute(this, "preventdefault");
119
159
  }
160
+ get #$activeElement() {
161
+ return this.preventDefault && !this["use-history"] ? this.#$button : this.#$anchor;
162
+ }
120
163
  get focusable() {
121
164
  return true;
122
165
  }
123
166
  focus() {
124
- this.#$anchor.focus();
167
+ this.#$activeElement.focus();
125
168
  }
126
169
  blur() {
127
- this.#$anchor.blur();
170
+ this.#$activeElement.blur();
128
171
  }
172
+ #onButtonClick = () => {
173
+ if (this.disabled) {
174
+ return;
175
+ }
176
+ this.dispatchEvent(new CustomEvent("-click"));
177
+ };
129
178
  #onAnchorClick = (e) => {
130
179
  if (this.preventDefault) {
131
180
  e.preventDefault();
package/link/types.d.ts CHANGED
@@ -14,6 +14,8 @@ export type TSinchLinkProps = {
14
14
  standalone?: boolean;
15
15
  /** Prevents default behaviour on hyperlink click */
16
16
  preventDefault?: boolean;
17
+ /** Render link text as code (e.g. for Markdown `` [`word`](url) ``) */
18
+ contentAsCode?: boolean;
17
19
  /** Label that is used for a11y – might be different from `text` */
18
20
  'aria-label': string;
19
21
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.18.4",
3
+ "version": "5.19.1",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
package/readme.md CHANGED
@@ -10,7 +10,7 @@ Design System's framework-agnostic Component Library implementation.
10
10
 
11
11
  Add the component library dependency to `package.json`:
12
12
 
13
- ```
13
+ ```bash
14
14
  npm install @nectary/components
15
15
  # or
16
16
  yarn add @nectary/components
@@ -74,7 +74,7 @@ Use it in React/Vue/Angular/etc, for example:
74
74
  <sinch-button value="Click me" onClick={() => console.log('click')}></sinch-button>
75
75
  ```
76
76
 
77
- ⚠️ Note that it's not allowed to self-close custom element tags.
77
+ > ⚠️ Note: it's not allowed to self-close custom element tags.
78
78
 
79
79
  ## Testing
80
80
 
@@ -110,6 +110,7 @@ class RichText extends NectaryElement {
110
110
  #handleElementClick = (e) => {
111
111
  const eventTarget = e.target;
112
112
  const elementClickEvent = new CustomEvent("-element-click");
113
+ Object.defineProperty(elementClickEvent, "target", { value: eventTarget });
113
114
  Object.defineProperty(elementClickEvent, "currentTarget", { value: eventTarget });
114
115
  this.dispatchEvent(elementClickEvent);
115
116
  };
@@ -24,14 +24,14 @@ const createParseVisitor = (doc) => {
24
24
  let $li = null;
25
25
  const $lists = [];
26
26
  return {
27
- // Add new escaped method to handle escaped characters
28
27
  escaped(char) {
29
- const $text = doc.createTextNode(char);
28
+ const $inline = doc.createElement("SPAN");
29
+ $inline.append(doc.createTextNode(char));
30
30
  if ($p != null) {
31
- $p.appendChild($text);
31
+ $p.appendChild($inline);
32
32
  } else {
33
33
  this.paragraph();
34
- $p.appendChild($text);
34
+ $p.appendChild($inline);
35
35
  }
36
36
  },
37
37
  emoji(emojiChar) {
@@ -46,6 +46,9 @@ const createParseVisitor = (doc) => {
46
46
  $codeTag.text = text;
47
47
  $p.appendChild($codeTag);
48
48
  },
49
+ linkPlaceholder(_name) {
50
+ return false;
51
+ },
49
52
  tag(text) {
50
53
  const $chip = doc.createElement("sinch-rich-textarea-chip");
51
54
  const resolved = chipResolver?.(text);
@@ -61,6 +64,9 @@ const createParseVisitor = (doc) => {
61
64
  }
62
65
  $p.appendChild($chip);
63
66
  },
67
+ buttonPlaceholder(name) {
68
+ this.inline(`[[${name}]]`, {});
69
+ },
64
70
  inline(text, { isBold, isItalic, isStrikethrough }) {
65
71
  const $inline = doc.createElement("SPAN");
66
72
  $inline.append(doc.createTextNode(text));
@@ -79,10 +85,13 @@ const createParseVisitor = (doc) => {
79
85
  const $br = doc.createElement("br");
80
86
  $p.appendChild($br);
81
87
  },
82
- link(text, href, attributes) {
88
+ link(text, href, attributes, isCode) {
83
89
  const $link = doc.createElement("sinch-link");
84
90
  $link.text = text;
85
91
  $link.href = href;
92
+ if (isCode === true) {
93
+ $link.contentAsCode = true;
94
+ }
86
95
  if (attributes != null) {
87
96
  attributes.forEach((attr) => {
88
97
  if (attr.startsWith("#")) {
@@ -176,14 +176,28 @@ const copyFormatName = ($source, $target) => {
176
176
  $target.className = $inline.className;
177
177
  if (isFormatLink($inline)) {
178
178
  $target.setAttribute(LINK_HREF_ATTR_NAME, $inline.getAttribute(LINK_HREF_ATTR_NAME) ?? "");
179
+ } else {
180
+ $target.removeAttribute(LINK_HREF_ATTR_NAME);
179
181
  }
180
182
  };
181
183
  const setInlineFormat = ($n, formatName, shouldEnable) => {
182
184
  if (shouldEnable) {
183
- if (formatName === "c" || isFormatName($n, "c")) {
184
- $n.className = "";
185
- }
186
- if (formatName === "l" || isFormatName($n, "l")) {
185
+ if (formatName === "c") {
186
+ if (isFormatName($n, "l")) {
187
+ $n.className = "l";
188
+ } else {
189
+ $n.className = "";
190
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
191
+ }
192
+ } else if (formatName === "l") {
193
+ if (!isFormatName($n, "c")) {
194
+ $n.className = "";
195
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
196
+ } else {
197
+ $n.className = "c";
198
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
199
+ }
200
+ } else if (isFormatName($n, "c") || isFormatName($n, "l")) {
187
201
  $n.className = "";
188
202
  $n.removeAttribute(LINK_HREF_ATTR_NAME);
189
203
  }
@@ -224,14 +238,14 @@ const areSameInlineFormat = ($a, $b) => {
224
238
  if ($a.classList.length !== $b.classList.length) {
225
239
  return false;
226
240
  }
227
- if ($a.className === "l") {
228
- return $b.className === "l" && $a.getAttribute(LINK_HREF_ATTR_NAME) === $b.getAttribute(LINK_HREF_ATTR_NAME);
229
- }
230
241
  for (let i = 0; i < $a.classList.length; i++) {
231
242
  if (!$b.classList.contains($a.classList[i])) {
232
243
  return false;
233
244
  }
234
245
  }
246
+ if ($a.classList.contains("l")) {
247
+ return $a.getAttribute(LINK_HREF_ATTR_NAME) === $b.getAttribute(LINK_HREF_ATTR_NAME);
248
+ }
235
249
  return true;
236
250
  };
237
251
  const createInlineWithText = (data, doc) => {
@@ -1511,6 +1525,11 @@ const serializeDescriptorReducer = (range) => (state, $n) => {
1511
1525
  if (isEmptyText(text)) {
1512
1526
  return state;
1513
1527
  }
1528
+ if (isFormatCodetag($n) && isFormatLink($n)) {
1529
+ const href = $n.getAttribute(LINK_HREF_ATTR_NAME) ?? "#";
1530
+ state.push({ isCodetag: true, isLink: true, text, href });
1531
+ return state;
1532
+ }
1514
1533
  if (isFormatCodetag($n)) {
1515
1534
  state.push({ isCodetag: true, text });
1516
1535
  return state;
@@ -1561,7 +1580,8 @@ const MD_PARAGRAPH_JOIN = "\n\n";
1561
1580
  const serializeTextReducer = (state, desc, i, descArray) => {
1562
1581
  const { chunks } = state;
1563
1582
  if (desc.isLink === true) {
1564
- chunks.push(`[${desc.text}](${desc.href})`);
1583
+ const inner = desc.isCodetag === true ? `${MD_CODETAG_TOKEN}${desc.text}${MD_CODETAG_TOKEN}` : desc.text;
1584
+ chunks.push(`[${inner}](${desc.href})`);
1565
1585
  return state;
1566
1586
  }
1567
1587
  if (desc.isEmoji === true) {
@@ -1742,12 +1762,12 @@ const createParseVisitor = (doc) => {
1742
1762
  let isFirstListItem = false;
1743
1763
  return {
1744
1764
  escaped(char) {
1745
- const $text = doc.createTextNode(char);
1765
+ const $inline = createInlineWithText(char, doc);
1746
1766
  if ($currentBlock != null) {
1747
- $currentBlock.appendChild($text);
1767
+ $currentBlock.appendChild($inline);
1748
1768
  } else {
1749
1769
  this.paragraph();
1750
- $currentBlock.appendChild($text);
1770
+ $currentBlock.appendChild($inline);
1751
1771
  }
1752
1772
  },
1753
1773
  emoji(emojiChar) {
@@ -1759,11 +1779,18 @@ const createParseVisitor = (doc) => {
1759
1779
  setInlineFormat($inline, "c", true);
1760
1780
  $currentBlock.appendChild($inline);
1761
1781
  },
1782
+ linkPlaceholder(_name) {
1783
+ return false;
1784
+ },
1762
1785
  tag(text) {
1763
1786
  const resolved = chipResolver?.(text);
1764
1787
  const $tag = createTag(text, doc, resolved?.color ?? chipColor, resolved?.icon ?? chipIcon);
1765
1788
  $currentBlock.appendChild($tag);
1766
1789
  },
1790
+ buttonPlaceholder(name) {
1791
+ const $inline = createInlineWithText(`[[${name}]]`, doc);
1792
+ $currentBlock.appendChild($inline);
1793
+ },
1767
1794
  inline(text, { isBold, isItalic, isStrikethrough }) {
1768
1795
  const $inline = createInlineWithText(text, doc);
1769
1796
  setInlineFormat($inline, "b", isBold === true);
@@ -1777,8 +1804,11 @@ const createParseVisitor = (doc) => {
1777
1804
  $root.appendChild($currentBlock);
1778
1805
  }
1779
1806
  },
1780
- link(text, href) {
1807
+ link(text, href, _attributes, isCode) {
1781
1808
  const $link = createLink(text, href, doc);
1809
+ if (isCode === true) {
1810
+ setInlineFormat($link, "c", true);
1811
+ }
1782
1812
  $currentBlock.appendChild($link);
1783
1813
  },
1784
1814
  list(isOrdered) {
@@ -1,3 +1,3 @@
1
- export declare const BASE_COMPONENT_NAMES_LIST: readonly ["accordion-item", "accordion", "action-menu-option", "action-menu", "alert", "avatar", "badge", "button-group-item", "button-group", "button", "card-container", "card-v2-title", "card-v2", "checkbox", "chip", "code-tag", "color-menu-option", "color-menu", "color-swatch", "date-picker", "dialog", "emoji-picker", "emoji", "field", "file-drop", "file-picker", "file-status", "flag", "grid-item", "grid", "help-tooltip", "icon", "inline-alert", "input", "link", "list-item", "list", "pagination", "persistent-overlay", "pop", "popover", "progress-stepper-item", "progress-stepper", "progress", "radio-option", "radio", "rich-text", "rich-textarea", "rich-textarea-chip", "segment-collapse", "segmented-control-option", "segmented-control", "segmented-icon-control-option", "segmented-icon-control", "select-button", "select-menu-option", "select-menu", "sheet", "sheet-title", "skeleton-item", "skeleton", "spinner", "stop-events", "table-body", "table-cell", "table-head-cell", "table-head", "table-row", "table", "tabs-icon-option", "tabs-option", "tabs", "tag", "text", "textarea", "time-picker", "title", "toast-manager", "toast", "toggle", "tooltip"];
2
- export declare const BASE_COMPONENT_NAMES: Set<"pop" | "button" | "dialog" | "input" | "link" | "progress" | "table" | "textarea" | "title" | "accordion-item" | "accordion" | "action-menu-option" | "action-menu" | "alert" | "avatar" | "badge" | "button-group-item" | "button-group" | "card-container" | "card-v2-title" | "card-v2" | "checkbox" | "chip" | "code-tag" | "color-menu-option" | "color-menu" | "color-swatch" | "date-picker" | "emoji-picker" | "emoji" | "field" | "file-drop" | "file-picker" | "file-status" | "flag" | "grid-item" | "grid" | "help-tooltip" | "icon" | "inline-alert" | "list-item" | "list" | "pagination" | "persistent-overlay" | "popover" | "progress-stepper-item" | "progress-stepper" | "radio-option" | "radio" | "rich-text" | "rich-textarea" | "rich-textarea-chip" | "segment-collapse" | "segmented-control-option" | "segmented-control" | "segmented-icon-control-option" | "segmented-icon-control" | "select-button" | "select-menu-option" | "select-menu" | "sheet" | "sheet-title" | "skeleton-item" | "skeleton" | "spinner" | "stop-events" | "table-body" | "table-cell" | "table-head-cell" | "table-head" | "table-row" | "tabs-icon-option" | "tabs-option" | "tabs" | "tag" | "text" | "time-picker" | "toast-manager" | "toast" | "toggle" | "tooltip">;
1
+ export declare const BASE_COMPONENT_NAMES_LIST: readonly ["accordion-item", "accordion", "action-menu-option", "action-menu", "alert", "avatar", "badge", "button-group-item", "button-group", "button", "card-container", "card-v2-title", "card-v2", "checkbox", "chip", "code-tag", "color-menu-option", "color-menu", "color-swatch", "date-picker", "dialog", "emoji-picker", "emoji", "field", "field-v2", "file-drop", "file-picker", "file-status", "flag", "grid-item", "grid", "help-tooltip", "icon", "inline-alert", "input", "link", "list-item", "list", "pagination", "persistent-overlay", "pop", "popover", "progress-stepper-item", "progress-stepper", "progress", "radio-option", "radio", "rich-text", "rich-textarea", "rich-textarea-chip", "segment-collapse", "segmented-control-option", "segmented-control", "segmented-icon-control-option", "segmented-icon-control", "select-button", "select-menu-option", "select-menu", "sheet", "sheet-title", "skeleton-item", "skeleton", "spinner", "stop-events", "table-body", "table-cell", "table-head-cell", "table-head", "table-row", "table", "tabs-icon-option", "tabs-option", "tabs", "tag", "text", "textarea", "time-picker", "title", "toast-manager", "toast", "toggle", "tooltip"];
2
+ export declare const BASE_COMPONENT_NAMES: Set<"pop" | "button" | "dialog" | "input" | "link" | "progress" | "table" | "textarea" | "title" | "accordion-item" | "accordion" | "action-menu-option" | "action-menu" | "alert" | "avatar" | "badge" | "button-group-item" | "button-group" | "card-container" | "card-v2-title" | "card-v2" | "checkbox" | "chip" | "code-tag" | "color-menu-option" | "color-menu" | "color-swatch" | "date-picker" | "emoji-picker" | "emoji" | "field" | "field-v2" | "file-drop" | "file-picker" | "file-status" | "flag" | "grid-item" | "grid" | "help-tooltip" | "icon" | "inline-alert" | "list-item" | "list" | "pagination" | "persistent-overlay" | "popover" | "progress-stepper-item" | "progress-stepper" | "radio-option" | "radio" | "rich-text" | "rich-textarea" | "rich-textarea-chip" | "segment-collapse" | "segmented-control-option" | "segmented-control" | "segmented-icon-control-option" | "segmented-icon-control" | "select-button" | "select-menu-option" | "select-menu" | "sheet" | "sheet-title" | "skeleton-item" | "skeleton" | "spinner" | "stop-events" | "table-body" | "table-cell" | "table-head-cell" | "table-head" | "table-row" | "tabs-icon-option" | "tabs-option" | "tabs" | "tag" | "text" | "time-picker" | "toast-manager" | "toast" | "toggle" | "tooltip">;
3
3
  export type ComponentName = `sinch-${typeof BASE_COMPONENT_NAMES_LIST[number]}`;
@@ -23,6 +23,7 @@ const BASE_COMPONENT_NAMES_LIST = [
23
23
  "emoji-picker",
24
24
  "emoji",
25
25
  "field",
26
+ "field-v2",
26
27
  "file-drop",
27
28
  "file-picker",
28
29
  "file-status",
@@ -32,6 +32,7 @@ declare const BASE_COMPONENT_NAMES_LIST: readonly [
32
32
  "emoji-picker",
33
33
  "emoji",
34
34
  "field",
35
+ "field-v2",
35
36
  "file-drop",
36
37
  "file-picker",
37
38
  "file-status",
@@ -5,10 +5,13 @@ export type TMarkdownInlineParams = {
5
5
  };
6
6
  export type TMarkdownParseVisitor = {
7
7
  escaped(char: string): void;
8
- link(text: string, href: string, attributes?: string[]): void;
8
+ link(text: string, href: string, attributes?: string[], isCode?: boolean): void;
9
9
  emoji(emojiChar: string): void;
10
10
  codetag(text: string): void;
11
+ /** Returns true if placeholder was handled as a link; false to fall through to tag (chip). */
12
+ linkPlaceholder(name: string): boolean;
11
13
  tag(username: string): void;
14
+ buttonPlaceholder(name: string): void;
12
15
  inline(text: string, params: TMarkdownInlineParams): void;
13
16
  linebreak(): void;
14
17
  paragraph(): void;