@nectary/components 5.18.4 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bundle.js CHANGED
@@ -1070,23 +1070,30 @@ class CodeTag extends NectaryElement {
1070
1070
  }
1071
1071
  }
1072
1072
  defineCustomElement("sinch-code-tag", CodeTag);
1073
- const templateHTML$14 = '<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>';
1073
+ const templateHTML$14 = '<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>';
1074
1074
  const template$14 = document.createElement("template");
1075
1075
  template$14.innerHTML = templateHTML$14;
1076
1076
  class Link extends NectaryElement {
1077
1077
  #$anchor;
1078
1078
  #$text;
1079
+ #$button;
1080
+ #$buttonText;
1079
1081
  constructor() {
1080
1082
  super();
1081
1083
  const shadowRoot = this.attachShadow();
1082
1084
  shadowRoot.appendChild(template$14.content.cloneNode(true));
1083
1085
  this.#$anchor = shadowRoot.querySelector("a");
1084
1086
  this.#$text = shadowRoot.querySelector("#content");
1087
+ this.#$button = shadowRoot.querySelector("button");
1088
+ this.#$buttonText = shadowRoot.querySelector("#button-content");
1085
1089
  }
1086
1090
  connectedCallback() {
1087
1091
  this.#$anchor.addEventListener("click", this.#onAnchorClick);
1088
1092
  this.#$anchor.addEventListener("focus", this.#onAnchorFocus);
1089
1093
  this.#$anchor.addEventListener("blur", this.#onAnchorBlur);
1094
+ this.#$button.addEventListener("click", this.#onButtonClick);
1095
+ this.#$button.addEventListener("focus", this.#onAnchorFocus);
1096
+ this.#$button.addEventListener("blur", this.#onAnchorBlur);
1090
1097
  this.addEventListener("-click", this.#onClickReactHandler);
1091
1098
  this.addEventListener("-focus", this.#onFocusReactHandler);
1092
1099
  this.addEventListener("-blur", this.#onBlurReactHandler);
@@ -1095,6 +1102,9 @@ class Link extends NectaryElement {
1095
1102
  this.#$anchor.removeEventListener("click", this.#onAnchorClick);
1096
1103
  this.#$anchor.removeEventListener("focus", this.#onAnchorFocus);
1097
1104
  this.#$anchor.removeEventListener("blur", this.#onAnchorBlur);
1105
+ this.#$button.removeEventListener("click", this.#onButtonClick);
1106
+ this.#$button.removeEventListener("focus", this.#onAnchorFocus);
1107
+ this.#$button.removeEventListener("blur", this.#onAnchorBlur);
1098
1108
  this.removeEventListener("-click", this.#onClickReactHandler);
1099
1109
  this.removeEventListener("-focus", this.#onFocusReactHandler);
1100
1110
  this.removeEventListener("-blur", this.#onBlurReactHandler);
@@ -1103,19 +1113,38 @@ class Link extends NectaryElement {
1103
1113
  return [
1104
1114
  "text",
1105
1115
  "href",
1116
+ "content-as-code",
1106
1117
  "use-history",
1107
1118
  "external",
1108
1119
  "standalone",
1109
1120
  "disabled"
1110
1121
  ];
1111
1122
  }
1123
+ #renderContent() {
1124
+ const text = getAttribute(this, "text", "");
1125
+ const asCode = getBooleanAttribute(this, "content-as-code");
1126
+ this.#$text.textContent = "";
1127
+ this.#$buttonText.textContent = "";
1128
+ if (asCode && text !== "") {
1129
+ const $code = document.createElement("sinch-code-tag");
1130
+ $code.text = text;
1131
+ this.#$text.appendChild($code);
1132
+ const $buttonCode = document.createElement("sinch-code-tag");
1133
+ $buttonCode.text = text;
1134
+ this.#$buttonText.appendChild($buttonCode);
1135
+ } else {
1136
+ this.#$text.textContent = text;
1137
+ this.#$buttonText.textContent = text;
1138
+ }
1139
+ }
1112
1140
  attributeChangedCallback(name, oldVal, newVal) {
1113
1141
  if (isAttrEqual(oldVal, newVal)) {
1114
1142
  return;
1115
1143
  }
1116
1144
  switch (name) {
1117
- case "text": {
1118
- this.#$text.textContent = newVal;
1145
+ case "text":
1146
+ case "content-as-code": {
1147
+ this.#renderContent();
1119
1148
  break;
1120
1149
  }
1121
1150
  case "href": {
@@ -1132,7 +1161,11 @@ class Link extends NectaryElement {
1132
1161
  }
1133
1162
  case "standalone":
1134
1163
  case "disabled": {
1135
- updateBooleanAttribute(this, name, isAttrTrue(newVal));
1164
+ const isTrue = isAttrTrue(newVal);
1165
+ updateBooleanAttribute(this, name, isTrue);
1166
+ if (name === "disabled") {
1167
+ this.#$button.disabled = isTrue;
1168
+ }
1136
1169
  break;
1137
1170
  }
1138
1171
  case "external": {
@@ -1149,6 +1182,12 @@ class Link extends NectaryElement {
1149
1182
  set text(value) {
1150
1183
  updateAttribute(this, "text", value);
1151
1184
  }
1185
+ get contentAsCode() {
1186
+ return getBooleanAttribute(this, "content-as-code");
1187
+ }
1188
+ set contentAsCode(value) {
1189
+ updateBooleanAttribute(this, "content-as-code", value);
1190
+ }
1152
1191
  get href() {
1153
1192
  return getAttribute(this, "href", "");
1154
1193
  }
@@ -1185,15 +1224,24 @@ class Link extends NectaryElement {
1185
1224
  get preventDefault() {
1186
1225
  return getBooleanAttribute(this, "preventdefault");
1187
1226
  }
1227
+ get #$activeElement() {
1228
+ return this.preventDefault && !this["use-history"] ? this.#$button : this.#$anchor;
1229
+ }
1188
1230
  get focusable() {
1189
1231
  return true;
1190
1232
  }
1191
1233
  focus() {
1192
- this.#$anchor.focus();
1234
+ this.#$activeElement.focus();
1193
1235
  }
1194
1236
  blur() {
1195
- this.#$anchor.blur();
1237
+ this.#$activeElement.blur();
1196
1238
  }
1239
+ #onButtonClick = () => {
1240
+ if (this.disabled) {
1241
+ return;
1242
+ }
1243
+ this.dispatchEvent(new CustomEvent("-click"));
1244
+ };
1197
1245
  #onAnchorClick = (e) => {
1198
1246
  if (this.preventDefault) {
1199
1247
  e.preventDefault();
@@ -1346,6 +1394,7 @@ const regEm2Underscore = new RegExp("(?<!\\\\)__(?<em2>.+?)(?<!\\\\)__");
1346
1394
  const regEm1Underscore = new RegExp("(?<!\\\\)_(?<em1>.+?)(?<!\\\\)_");
1347
1395
  const regCodeTag = new RegExp("(?<!\\\\)`(?<code>.+?)(?<!\\\\)`");
1348
1396
  const regStrikethrough = new RegExp("(?<!\\\\)~~(?<strike>.+?)(?<!\\\\)~~");
1397
+ const regButtonPlaceholder = new RegExp("(?<!\\\\)\\[\\[(?<button>[a-zA-Z0-9_-]+)\\]\\]");
1349
1398
  const regLink = new RegExp("(?<!\\\\)!?\\[(?<linktext>[^\\]]*?)\\]\\((?<linkhref>[^)]+?)\\)(\\{(?<linkattrs>[^)]+?)\\})?");
1350
1399
  const regChip = new RegExp("(?<!\\\\)\\{\\{(?<chip>[a-zA-Z0-9_-]+)\\}\\}");
1351
1400
  const regEmoji = new RegExp("(?<emoji>(?![0-9*#])\\p{Emoji})", "u");
@@ -1355,6 +1404,7 @@ const regEscapedChars = /\\(?<escaped>[\\\*_\[\]`~\{\}])/;
1355
1404
  const allRegs = [
1356
1405
  regEscapedChars,
1357
1406
  regCodeTag,
1407
+ regButtonPlaceholder,
1358
1408
  regLink,
1359
1409
  regChip,
1360
1410
  regEm3Star,
@@ -1398,13 +1448,23 @@ const createLineParser = (visitor) => function parseLine(regs, md, context = INI
1398
1448
  visitor.escaped(groups.escaped);
1399
1449
  continue;
1400
1450
  }
1451
+ if (groups?.button != null) {
1452
+ visitor.buttonPlaceholder(groups.button);
1453
+ continue;
1454
+ }
1401
1455
  if (groups?.linkhref != null) {
1402
- visitor.link(groups.linktext, groups.linkhref, groups.linkattrs?.split(" "));
1456
+ const rawText = groups.linktext;
1457
+ const isCode = rawText.length >= 2 && rawText.startsWith("`") && rawText.endsWith("`") && !rawText.slice(1, -1).includes("`");
1458
+ const linkText = isCode ? rawText.slice(1, -1) : rawText;
1459
+ visitor.link(linkText, groups.linkhref, groups.linkattrs?.split(" "), isCode);
1403
1460
  }
1404
1461
  if (groups?.code != null) {
1405
1462
  visitor.codetag(groups.code);
1406
1463
  }
1407
1464
  if (groups?.chip != null) {
1465
+ if (visitor.linkPlaceholder(groups.chip)) {
1466
+ continue;
1467
+ }
1408
1468
  visitor.tag(groups.chip);
1409
1469
  }
1410
1470
  if (groups?.emoji != null) {
@@ -1523,14 +1583,14 @@ const createParseVisitor$1 = (doc) => {
1523
1583
  let $li = null;
1524
1584
  const $lists = [];
1525
1585
  return {
1526
- // Add new escaped method to handle escaped characters
1527
1586
  escaped(char) {
1528
- const $text = doc.createTextNode(char);
1587
+ const $inline = doc.createElement("SPAN");
1588
+ $inline.append(doc.createTextNode(char));
1529
1589
  if ($p != null) {
1530
- $p.appendChild($text);
1590
+ $p.appendChild($inline);
1531
1591
  } else {
1532
1592
  this.paragraph();
1533
- $p.appendChild($text);
1593
+ $p.appendChild($inline);
1534
1594
  }
1535
1595
  },
1536
1596
  emoji(emojiChar) {
@@ -1545,6 +1605,9 @@ const createParseVisitor$1 = (doc) => {
1545
1605
  $codeTag.text = text;
1546
1606
  $p.appendChild($codeTag);
1547
1607
  },
1608
+ linkPlaceholder(_name) {
1609
+ return false;
1610
+ },
1548
1611
  tag(text) {
1549
1612
  const $chip = doc.createElement("sinch-rich-textarea-chip");
1550
1613
  const resolved = chipResolver?.(text);
@@ -1560,6 +1623,9 @@ const createParseVisitor$1 = (doc) => {
1560
1623
  }
1561
1624
  $p.appendChild($chip);
1562
1625
  },
1626
+ buttonPlaceholder(name) {
1627
+ this.inline(`[[${name}]]`, {});
1628
+ },
1563
1629
  inline(text, { isBold, isItalic, isStrikethrough }) {
1564
1630
  const $inline = doc.createElement("SPAN");
1565
1631
  $inline.append(doc.createTextNode(text));
@@ -1578,10 +1644,13 @@ const createParseVisitor$1 = (doc) => {
1578
1644
  const $br = doc.createElement("br");
1579
1645
  $p.appendChild($br);
1580
1646
  },
1581
- link(text, href, attributes) {
1647
+ link(text, href, attributes, isCode) {
1582
1648
  const $link = doc.createElement("sinch-link");
1583
1649
  $link.text = text;
1584
1650
  $link.href = href;
1651
+ if (isCode === true) {
1652
+ $link.contentAsCode = true;
1653
+ }
1585
1654
  if (attributes != null) {
1586
1655
  attributes.forEach((attr) => {
1587
1656
  if (attr.startsWith("#")) {
@@ -1745,6 +1814,7 @@ class RichText extends NectaryElement {
1745
1814
  #handleElementClick = (e) => {
1746
1815
  const eventTarget = e.target;
1747
1816
  const elementClickEvent = new CustomEvent("-element-click");
1817
+ Object.defineProperty(elementClickEvent, "target", { value: eventTarget });
1748
1818
  Object.defineProperty(elementClickEvent, "currentTarget", { value: eventTarget });
1749
1819
  this.dispatchEvent(elementClickEvent);
1750
1820
  };
@@ -8947,14 +9017,28 @@ const copyFormatName = ($source, $target) => {
8947
9017
  $target.className = $inline.className;
8948
9018
  if (isFormatLink($inline)) {
8949
9019
  $target.setAttribute(LINK_HREF_ATTR_NAME, $inline.getAttribute(LINK_HREF_ATTR_NAME) ?? "");
9020
+ } else {
9021
+ $target.removeAttribute(LINK_HREF_ATTR_NAME);
8950
9022
  }
8951
9023
  };
8952
9024
  const setInlineFormat = ($n, formatName, shouldEnable) => {
8953
9025
  if (shouldEnable) {
8954
- if (formatName === "c" || isFormatName($n, "c")) {
8955
- $n.className = "";
8956
- }
8957
- if (formatName === "l" || isFormatName($n, "l")) {
9026
+ if (formatName === "c") {
9027
+ if (isFormatName($n, "l")) {
9028
+ $n.className = "l";
9029
+ } else {
9030
+ $n.className = "";
9031
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
9032
+ }
9033
+ } else if (formatName === "l") {
9034
+ if (!isFormatName($n, "c")) {
9035
+ $n.className = "";
9036
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
9037
+ } else {
9038
+ $n.className = "c";
9039
+ $n.removeAttribute(LINK_HREF_ATTR_NAME);
9040
+ }
9041
+ } else if (isFormatName($n, "c") || isFormatName($n, "l")) {
8958
9042
  $n.className = "";
8959
9043
  $n.removeAttribute(LINK_HREF_ATTR_NAME);
8960
9044
  }
@@ -8995,14 +9079,14 @@ const areSameInlineFormat = ($a, $b) => {
8995
9079
  if ($a.classList.length !== $b.classList.length) {
8996
9080
  return false;
8997
9081
  }
8998
- if ($a.className === "l") {
8999
- return $b.className === "l" && $a.getAttribute(LINK_HREF_ATTR_NAME) === $b.getAttribute(LINK_HREF_ATTR_NAME);
9000
- }
9001
9082
  for (let i = 0; i < $a.classList.length; i++) {
9002
9083
  if (!$b.classList.contains($a.classList[i])) {
9003
9084
  return false;
9004
9085
  }
9005
9086
  }
9087
+ if ($a.classList.contains("l")) {
9088
+ return $a.getAttribute(LINK_HREF_ATTR_NAME) === $b.getAttribute(LINK_HREF_ATTR_NAME);
9089
+ }
9006
9090
  return true;
9007
9091
  };
9008
9092
  const createInlineWithText = (data2, doc) => {
@@ -10282,6 +10366,11 @@ const serializeDescriptorReducer = (range) => (state, $n) => {
10282
10366
  if (isEmptyText(text)) {
10283
10367
  return state;
10284
10368
  }
10369
+ if (isFormatCodetag($n) && isFormatLink($n)) {
10370
+ const href = $n.getAttribute(LINK_HREF_ATTR_NAME) ?? "#";
10371
+ state.push({ isCodetag: true, isLink: true, text, href });
10372
+ return state;
10373
+ }
10285
10374
  if (isFormatCodetag($n)) {
10286
10375
  state.push({ isCodetag: true, text });
10287
10376
  return state;
@@ -10332,7 +10421,8 @@ const MD_PARAGRAPH_JOIN = "\n\n";
10332
10421
  const serializeTextReducer = (state, desc, i, descArray) => {
10333
10422
  const { chunks } = state;
10334
10423
  if (desc.isLink === true) {
10335
- chunks.push(`[${desc.text}](${desc.href})`);
10424
+ const inner = desc.isCodetag === true ? `${MD_CODETAG_TOKEN}${desc.text}${MD_CODETAG_TOKEN}` : desc.text;
10425
+ chunks.push(`[${inner}](${desc.href})`);
10336
10426
  return state;
10337
10427
  }
10338
10428
  if (desc.isEmoji === true) {
@@ -10513,12 +10603,12 @@ const createParseVisitor = (doc) => {
10513
10603
  let isFirstListItem = false;
10514
10604
  return {
10515
10605
  escaped(char) {
10516
- const $text = doc.createTextNode(char);
10606
+ const $inline = createInlineWithText(char, doc);
10517
10607
  if ($currentBlock != null) {
10518
- $currentBlock.appendChild($text);
10608
+ $currentBlock.appendChild($inline);
10519
10609
  } else {
10520
10610
  this.paragraph();
10521
- $currentBlock.appendChild($text);
10611
+ $currentBlock.appendChild($inline);
10522
10612
  }
10523
10613
  },
10524
10614
  emoji(emojiChar) {
@@ -10530,11 +10620,18 @@ const createParseVisitor = (doc) => {
10530
10620
  setInlineFormat($inline, "c", true);
10531
10621
  $currentBlock.appendChild($inline);
10532
10622
  },
10623
+ linkPlaceholder(_name) {
10624
+ return false;
10625
+ },
10533
10626
  tag(text) {
10534
10627
  const resolved = chipResolver?.(text);
10535
10628
  const $tag = createTag(text, doc, resolved?.color ?? chipColor, resolved?.icon ?? chipIcon);
10536
10629
  $currentBlock.appendChild($tag);
10537
10630
  },
10631
+ buttonPlaceholder(name) {
10632
+ const $inline = createInlineWithText(`[[${name}]]`, doc);
10633
+ $currentBlock.appendChild($inline);
10634
+ },
10538
10635
  inline(text, { isBold, isItalic, isStrikethrough }) {
10539
10636
  const $inline = createInlineWithText(text, doc);
10540
10637
  setInlineFormat($inline, "b", isBold === true);
@@ -10548,8 +10645,11 @@ const createParseVisitor = (doc) => {
10548
10645
  $root.appendChild($currentBlock);
10549
10646
  }
10550
10647
  },
10551
- link(text, href) {
10648
+ link(text, href, _attributes, isCode) {
10552
10649
  const $link = createLink(text, href, doc);
10650
+ if (isCode === true) {
10651
+ setInlineFormat($link, "c", true);
10652
+ }
10553
10653
  $currentBlock.appendChild($link);
10554
10654
  },
10555
10655
  list(isOrdered) {
@@ -0,0 +1 @@
1
+ export * from '../types';
@@ -0,0 +1,2 @@
1
+ import { defineCustomElement } from "../../utils/element.js";
2
+ defineCustomElement("sinch-field-v2");
@@ -0,0 +1,21 @@
1
+ import '../rich-text';
2
+ import { NectaryElement } from '../utils';
3
+ export * from './types';
4
+ export declare class FieldV2 extends NectaryElement {
5
+ #private;
6
+ constructor();
7
+ connectedCallback(): void;
8
+ disconnectedCallback(): void;
9
+ static get observedAttributes(): string[];
10
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
11
+ set label(value: string | null);
12
+ get label(): string | null;
13
+ set optionalText(value: string | null);
14
+ get optionalText(): string | null;
15
+ set additionalText(value: string | null);
16
+ get additionalText(): string | null;
17
+ set invalidText(value: string | null);
18
+ get invalidText(): string | null;
19
+ set disabled(isDisabled: boolean);
20
+ get disabled(): boolean;
21
+ }
@@ -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.0",
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;
package/utils/markdown.js CHANGED
@@ -8,6 +8,7 @@ const regEm2Underscore = new RegExp("(?<!\\\\)__(?<em2>.+?)(?<!\\\\)__");
8
8
  const regEm1Underscore = new RegExp("(?<!\\\\)_(?<em1>.+?)(?<!\\\\)_");
9
9
  const regCodeTag = new RegExp("(?<!\\\\)`(?<code>.+?)(?<!\\\\)`");
10
10
  const regStrikethrough = new RegExp("(?<!\\\\)~~(?<strike>.+?)(?<!\\\\)~~");
11
+ const regButtonPlaceholder = new RegExp("(?<!\\\\)\\[\\[(?<button>[a-zA-Z0-9_-]+)\\]\\]");
11
12
  const regLink = new RegExp("(?<!\\\\)!?\\[(?<linktext>[^\\]]*?)\\]\\((?<linkhref>[^)]+?)\\)(\\{(?<linkattrs>[^)]+?)\\})?");
12
13
  const regChip = new RegExp("(?<!\\\\)\\{\\{(?<chip>[a-zA-Z0-9_-]+)\\}\\}");
13
14
  const regEmoji = new RegExp("(?<emoji>(?![0-9*#])\\p{Emoji})", "u");
@@ -17,6 +18,7 @@ const regEscapedChars = /\\(?<escaped>[\\\*_\[\]`~\{\}])/;
17
18
  const allRegs = [
18
19
  regEscapedChars,
19
20
  regCodeTag,
21
+ regButtonPlaceholder,
20
22
  regLink,
21
23
  regChip,
22
24
  regEm3Star,
@@ -60,13 +62,23 @@ const createLineParser = (visitor) => function parseLine(regs, md, context = INI
60
62
  visitor.escaped(groups.escaped);
61
63
  continue;
62
64
  }
65
+ if (groups?.button != null) {
66
+ visitor.buttonPlaceholder(groups.button);
67
+ continue;
68
+ }
63
69
  if (groups?.linkhref != null) {
64
- visitor.link(groups.linktext, groups.linkhref, groups.linkattrs?.split(" "));
70
+ const rawText = groups.linktext;
71
+ const isCode = rawText.length >= 2 && rawText.startsWith("`") && rawText.endsWith("`") && !rawText.slice(1, -1).includes("`");
72
+ const linkText = isCode ? rawText.slice(1, -1) : rawText;
73
+ visitor.link(linkText, groups.linkhref, groups.linkattrs?.split(" "), isCode);
65
74
  }
66
75
  if (groups?.code != null) {
67
76
  visitor.codetag(groups.code);
68
77
  }
69
78
  if (groups?.chip != null) {
79
+ if (visitor.linkPlaceholder(groups.chip)) {
80
+ continue;
81
+ }
70
82
  visitor.tag(groups.chip);
71
83
  }
72
84
  if (groups?.emoji != null) {