@nldd/design-system 0.8.86 → 0.8.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/custom-elements.json +660 -142
  2. package/dist/components/actions/icon-button/icon-button.d.ts +2 -2
  3. package/dist/components/actions/icon-button/icon-button.d.ts.map +1 -1
  4. package/dist/components/actions/icon-button/icon-button.js +3 -3
  5. package/dist/components/actions/icon-button/icon-button.js.map +1 -1
  6. package/dist/components/components.js +1140 -243
  7. package/dist/components/content/avatar/avatar.d.ts +6 -6
  8. package/dist/components/content/avatar/avatar.d.ts.map +1 -1
  9. package/dist/components/content/avatar/avatar.js +9 -9
  10. package/dist/components/content/avatar/avatar.js.map +1 -1
  11. package/dist/components/content/icon/icon-aliases.d.ts.map +1 -1
  12. package/dist/components/content/icon/icon-aliases.js +4 -0
  13. package/dist/components/content/icon/icon-aliases.js.map +1 -1
  14. package/dist/components/content/icon/icon-gallery-status.d.ts.map +1 -1
  15. package/dist/components/content/icon/icon-gallery-status.js +3 -26
  16. package/dist/components/content/icon/icon-gallery-status.js.map +1 -1
  17. package/dist/components/content/icon/icon-registry.d.ts.map +1 -1
  18. package/dist/components/content/icon/icon-registry.js +2 -0
  19. package/dist/components/content/icon/icon-registry.js.map +1 -1
  20. package/dist/components/content/icon/icon.d.ts +2 -2
  21. package/dist/components/content/icon/icon.d.ts.map +1 -1
  22. package/dist/components/content/icon/icon.js +3 -3
  23. package/dist/components/content/icon/icon.js.map +1 -1
  24. package/dist/components/content/icon/icon.styles.d.ts.map +1 -1
  25. package/dist/components/content/icon/icon.styles.js +8 -2
  26. package/dist/components/content/icon/icon.styles.js.map +1 -1
  27. package/dist/components/content/rich-text/rich-text.d.ts +7 -6
  28. package/dist/components/content/rich-text/rich-text.d.ts.map +1 -1
  29. package/dist/components/content/rich-text/rich-text.js +8 -7
  30. package/dist/components/content/rich-text/rich-text.js.map +1 -1
  31. package/dist/components/content/text/text.d.ts +2 -2
  32. package/dist/components/content/text/text.js +3 -3
  33. package/dist/components/content/title/title.d.ts +5 -5
  34. package/dist/components/content/title/title.d.ts.map +1 -1
  35. package/dist/components/content/title/title.js +6 -6
  36. package/dist/components/content/title/title.js.map +1 -1
  37. package/dist/components/content/tooltip/tooltip.d.ts +2 -2
  38. package/dist/components/content/tooltip/tooltip.d.ts.map +1 -1
  39. package/dist/components/content/tooltip/tooltip.js +4 -4
  40. package/dist/components/content/tooltip/tooltip.js.map +1 -1
  41. package/dist/components/{dist-C09E9-dJ.js → dist-B_Mnmrj4.js} +1 -1
  42. package/dist/components/{dist-CjaG8dOI.js → dist-ByH8hQze.js} +2 -2
  43. package/dist/components/{dist-BB7TxeMK.js → dist-CRwuSgMM.js} +2 -2
  44. package/dist/components/{dist-la_b3ZSq.js → dist-CSrO_mYe.js} +1 -1
  45. package/dist/components/{dist-762GHPyH.js → dist-CX3DK4i7.js} +3 -3
  46. package/dist/components/{dist-DZoFKUO-.js → dist-CbCO0WCd.js} +1 -1
  47. package/dist/components/{dist-B_3OW7pC.js → dist-CgDuh26K.js} +1 -1
  48. package/dist/components/{dist-DaavuU6W.js → dist-DXZ-cFAl.js} +2 -2
  49. package/dist/components/{dist-CRsR6ckU.js → dist-LX8LuMac.js} +1 -1
  50. package/dist/components/{dist-BJI4-6iQ.js → dist-eVFegyjM.js} +1 -1
  51. package/dist/components/{dist-C6gnorjP.js → dist-fIxe0l7k.js} +9 -1
  52. package/dist/components/{icon-Cd4BloIM.js → icon-C_zwJZ_g.js} +14 -4
  53. package/dist/components/{icon-button-CXN0yaVx.js → icon-button-CEz4MB9H.js} +10 -10
  54. package/dist/components/inputs/code-editor/code-editor.styles.js +2 -2
  55. package/dist/components/inputs/text-editor/text-editor.annotation-sentinels.d.ts +3 -5
  56. package/dist/components/inputs/text-editor/text-editor.annotation-sentinels.d.ts.map +1 -1
  57. package/dist/components/inputs/text-editor/text-editor.annotation-sentinels.js +30 -44
  58. package/dist/components/inputs/text-editor/text-editor.annotation-sentinels.js.map +1 -1
  59. package/dist/components/inputs/text-editor/text-editor.annotations.d.ts.map +1 -1
  60. package/dist/components/inputs/text-editor/text-editor.annotations.js +31 -5
  61. package/dist/components/inputs/text-editor/text-editor.annotations.js.map +1 -1
  62. package/dist/components/inputs/text-editor/text-editor.commands.d.ts +14 -2
  63. package/dist/components/inputs/text-editor/text-editor.commands.d.ts.map +1 -1
  64. package/dist/components/inputs/text-editor/text-editor.commands.js +115 -36
  65. package/dist/components/inputs/text-editor/text-editor.commands.js.map +1 -1
  66. package/dist/components/inputs/text-editor/text-editor.cursor.d.ts +7 -0
  67. package/dist/components/inputs/text-editor/text-editor.cursor.d.ts.map +1 -0
  68. package/dist/components/inputs/text-editor/text-editor.cursor.js +207 -0
  69. package/dist/components/inputs/text-editor/text-editor.cursor.js.map +1 -0
  70. package/dist/components/inputs/text-editor/text-editor.d.ts +65 -13
  71. package/dist/components/inputs/text-editor/text-editor.d.ts.map +1 -1
  72. package/dist/components/inputs/text-editor/text-editor.emphasis.d.ts +32 -0
  73. package/dist/components/inputs/text-editor/text-editor.emphasis.d.ts.map +1 -0
  74. package/dist/components/inputs/text-editor/text-editor.emphasis.js +229 -0
  75. package/dist/components/inputs/text-editor/text-editor.emphasis.js.map +1 -0
  76. package/dist/components/inputs/text-editor/text-editor.js +177 -18
  77. package/dist/components/inputs/text-editor/text-editor.js.map +1 -1
  78. package/dist/components/inputs/text-editor/text-editor.links.d.ts +8 -0
  79. package/dist/components/inputs/text-editor/text-editor.links.d.ts.map +1 -1
  80. package/dist/components/inputs/text-editor/text-editor.links.js +30 -4
  81. package/dist/components/inputs/text-editor/text-editor.links.js.map +1 -1
  82. package/dist/components/inputs/text-editor/text-editor.markdown.d.ts.map +1 -1
  83. package/dist/components/inputs/text-editor/text-editor.markdown.js +52 -18
  84. package/dist/components/inputs/text-editor/text-editor.markdown.js.map +1 -1
  85. package/dist/components/inputs/text-editor/text-editor.mentions.d.ts +78 -11
  86. package/dist/components/inputs/text-editor/text-editor.mentions.d.ts.map +1 -1
  87. package/dist/components/inputs/text-editor/text-editor.mentions.js +190 -41
  88. package/dist/components/inputs/text-editor/text-editor.mentions.js.map +1 -1
  89. package/dist/components/inputs/text-editor/text-editor.ordered-list.d.ts +0 -2
  90. package/dist/components/inputs/text-editor/text-editor.ordered-list.d.ts.map +1 -1
  91. package/dist/components/inputs/text-editor/text-editor.ordered-list.js +21 -1
  92. package/dist/components/inputs/text-editor/text-editor.ordered-list.js.map +1 -1
  93. package/dist/components/inputs/text-editor/text-editor.styles.d.ts.map +1 -1
  94. package/dist/components/inputs/text-editor/text-editor.styles.js +11 -10
  95. package/dist/components/inputs/text-editor/text-editor.styles.js.map +1 -1
  96. package/dist/components/inputs/text-editor/text-editor.syntax.d.ts +9 -0
  97. package/dist/components/inputs/text-editor/text-editor.syntax.d.ts.map +1 -0
  98. package/dist/components/inputs/text-editor/text-editor.syntax.js +20 -0
  99. package/dist/components/inputs/text-editor/text-editor.syntax.js.map +1 -0
  100. package/dist/components/layout/page-sections/page-section-controls.d.ts +0 -70
  101. package/dist/components/layout/page-sections/page-section-controls.d.ts.map +1 -1
  102. package/dist/components/layout/page-sections/page-section-controls.js +0 -3
  103. package/dist/components/layout/page-sections/page-section-controls.js.map +1 -1
  104. package/dist/components/layout/sheet/sheet.d.ts +3 -7
  105. package/dist/components/layout/sheet/sheet.d.ts.map +1 -1
  106. package/dist/components/layout/sheet/sheet.js +4 -18
  107. package/dist/components/layout/sheet/sheet.js.map +1 -1
  108. package/dist/components/layout/sheet/sheet.styles.d.ts.map +1 -1
  109. package/dist/components/layout/sheet/sheet.styles.js +0 -4
  110. package/dist/components/layout/sheet/sheet.styles.js.map +1 -1
  111. package/dist/components/layout/sheet/sheet.template.js +2 -2
  112. package/dist/components/layout/sheet/sheet.template.js.map +1 -1
  113. package/dist/components/layout/window/window.d.ts +2 -4
  114. package/dist/components/layout/window/window.d.ts.map +1 -1
  115. package/dist/components/layout/window/window.js +4 -14
  116. package/dist/components/layout/window/window.js.map +1 -1
  117. package/dist/components/layout/window/window.template.d.ts.map +1 -1
  118. package/dist/components/layout/window/window.template.js +2 -2
  119. package/dist/components/layout/window/window.template.js.map +1 -1
  120. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.d.ts +2 -2
  121. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.js +3 -3
  122. package/dist/components/lists-and-tables/cells/text-cell/text-cell.d.ts +2 -2
  123. package/dist/components/lists-and-tables/cells/text-cell/text-cell.js +3 -3
  124. package/dist/components/lists-and-tables/cells/title-cell/title-cell.d.ts +2 -2
  125. package/dist/components/lists-and-tables/cells/title-cell/title-cell.js +3 -3
  126. package/dist/components/lists-and-tables/list/list.d.ts +4 -2
  127. package/dist/components/lists-and-tables/list/list.d.ts.map +1 -1
  128. package/dist/components/lists-and-tables/list/list.js +23 -5
  129. package/dist/components/lists-and-tables/list/list.js.map +1 -1
  130. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js +1 -1
  131. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js.map +1 -1
  132. package/dist/components/navigation/link/link.d.ts +2 -2
  133. package/dist/components/navigation/link/link.d.ts.map +1 -1
  134. package/dist/components/navigation/link/link.js +3 -2
  135. package/dist/components/navigation/link/link.js.map +1 -1
  136. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts +3 -3
  137. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts.map +1 -1
  138. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js +4 -4
  139. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js.map +1 -1
  140. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts +8 -0
  141. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts.map +1 -1
  142. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js +14 -0
  143. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js.map +1 -1
  144. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.d.ts.map +1 -1
  145. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js +22 -1
  146. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js.map +1 -1
  147. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.d.ts.map +1 -1
  148. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js +6 -2
  149. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js.map +1 -1
  150. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts +3 -1
  151. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts.map +1 -1
  152. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js +5 -0
  153. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js.map +1 -1
  154. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.d.ts.map +1 -1
  155. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js +1 -0
  156. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js.map +1 -1
  157. package/dist/favicon.svg +8 -2
  158. package/dist/nldd.min.js +525 -496
  159. package/dist/types/vue.d.ts +31 -23
  160. package/package.json +1 -1
@@ -3,14 +3,14 @@ import { n as slottedReset, t as inheritedTextReset } from "./shadow-resets-aG-r
3
3
  import { t as __decorate } from "./decorate-lwt_OrAG.js";
4
4
  import { t as withTranslations } from "./with-translations-CqUV9S5g.js";
5
5
  import { t as reflectNonDefault } from "./reflect-non-default-OLaK60rT.js";
6
- import { i as PopupAnchorController, n as NLDDTooltip, r as NLDDActivityIndicator, t as NLDDIconButton } from "./icon-button-CXN0yaVx.js";
7
- import { t as NLDDIcon } from "./icon-Cd4BloIM.js";
6
+ import { i as PopupAnchorController, n as NLDDTooltip, r as NLDDActivityIndicator, t as NLDDIconButton } from "./icon-button-CEz4MB9H.js";
7
+ import { t as NLDDIcon } from "./icon-C_zwJZ_g.js";
8
8
  import { n as isPointerMode, r as isTouchMode, t as isKeyboardMode } from "./input-modality-DhTeUxKi.js";
9
9
  import { t as breakpoints } from "./breakpoints-CkxsiRgH.js";
10
10
  import { t as VisibilityMixin } from "./visibility-mixin-BU1vIMW7.js";
11
- import { $ as placeholder, A as syntaxTree, B as Tree, C as indentNodeProp, D as matchBrackets, E as languageDataProp, F as NodeProp, G as ViewPlugin, H as Decoration, I as NodeSet, J as dropCursor, K as WidgetType, L as NodeType, M as styleTags, N as tags, S as getIndentation, T as indentUnit, U as Direction, V as parseMixed, W as EditorView, X as keymap, Z as lineNumbers, a as HighlightStyle, at as EditorSelection, b as foldService, bt as findClusterBreak, c as Language, d as ParseContext, f as StreamLanguage, ft as StateEffect, h as defineLanguageFacet, ht as Transaction, it as Compartment, j as Tag, k as syntaxHighlighting, l as LanguageDescription, lt as Prec, mt as Text, nt as ChangeDesc, o as IndentContext, ot as EditorState, pt as StateField, q as drawSelection, rt as ChangeSet, st as Facet, tt as Annotation, u as LanguageSupport, ut as RangeSetBuilder, vt as combineConfig, w as indentString, x as getIndentUnit, y as foldNodeProp, yt as countColumn, z as Parser } from "./dist-C6gnorjP.js";
12
- import { i as completionStatus, n as autocompletion, s as startCompletion, t as CompletionContext } from "./dist-B_3OW7pC.js";
13
- import { n as html$2, r as htmlCompletionSource } from "./dist-762GHPyH.js";
11
+ import { $ as layer, A as syntaxTree, B as Tree, C as indentNodeProp, Ct as findClusterBreak, D as matchBrackets, E as languageDataProp, F as NodeProp, G as RectangleMarker, H as Decoration, I as NodeSet, J as drawSelection, K as ViewPlugin, L as NodeType, M as styleTags, N as tags, Q as keymap, S as getIndentation, St as countColumn, T as indentUnit, U as Direction, V as parseMixed, W as EditorView, X as getDrawSelectionConfig, Y as dropCursor, _t as Text, a as HighlightStyle, at as ChangeDesc, b as foldService, c as Language, ct as EditorSelection, d as ParseContext, et as lineNumbers, f as StreamLanguage, ft as Prec, gt as StateField, h as defineLanguageFacet, ht as StateEffect, it as Annotation, j as Tag, k as syntaxHighlighting, l as LanguageDescription, lt as EditorState, nt as placeholder, o as IndentContext, ot as ChangeSet, pt as RangeSetBuilder, q as WidgetType, st as Compartment, u as LanguageSupport, ut as Facet, vt as Transaction, w as indentString, x as getIndentUnit, xt as combineConfig, y as foldNodeProp, z as Parser } from "./dist-fIxe0l7k.js";
12
+ import { i as completionStatus, n as autocompletion, s as startCompletion, t as CompletionContext } from "./dist-CgDuh26K.js";
13
+ import { n as html$2, r as htmlCompletionSource } from "./dist-CX3DK4i7.js";
14
14
  import { LitElement, css, html, nothing, svg, unsafeCSS } from "lit";
15
15
  import { customElement, property, query, state } from "lit/decorators.js";
16
16
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
@@ -1956,7 +1956,7 @@ function template$21() {
1956
1956
  * @element nldd-icon-cell
1957
1957
  * @attr {string} vertical-alignment - Vertical alignment: 'top' | 'center' | 'bottom' (default: 'center')
1958
1958
  * @attr {string} size - Size: '16' | '20' | '24' | '32' (default: '24')
1959
- * @attr {'default' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Color variant of the icon (default: 'default')
1959
+ * @attr {'content' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Color variant of the icon (default: 'content')
1960
1960
  * @attr {string} icon - Icon name (renders `<nldd-icon>`). Takes precedence over the default slot.
1961
1961
  *
1962
1962
  * @slot - Fallback for custom icon content when `icon` is not set.
@@ -1968,7 +1968,7 @@ var NLDDIconCell = (_NLDDIconCell = class NLDDIconCell extends VisibilityMixin(L
1968
1968
  super(..._args);
1969
1969
  this.verticalAlignment = "center";
1970
1970
  this.size = "24";
1971
- this.color = "default";
1971
+ this.color = "content";
1972
1972
  this.icon = "";
1973
1973
  }
1974
1974
  render() {
@@ -1986,7 +1986,7 @@ __decorate([property({
1986
1986
  })], NLDDIconCell.prototype, "size", void 0);
1987
1987
  __decorate([property({
1988
1988
  reflect: true,
1989
- converter: reflectNonDefault("default")
1989
+ converter: reflectNonDefault("content")
1990
1990
  })], NLDDIconCell.prototype, "color", void 0);
1991
1991
  __decorate([property({
1992
1992
  type: String,
@@ -2364,7 +2364,7 @@ function template$20() {
2364
2364
  *
2365
2365
  * @element nldd-text-cell
2366
2366
  * @attr {string} size - Cell size: 'sm' | 'md' (default: 'md')
2367
- * @attr {string} color - Text color variant: 'default' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical' (default: 'default'). All non-default/-secondary variants apply to all three text fields so the cell reads as a coherent state.
2367
+ * @attr {string} color - Text color variant: 'content' (the default) | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'. All variants other than those two apply to all three text fields so the cell reads as a coherent state.
2368
2368
  * @attr {string} width - 'full' | 'fit-content' | CSS length (e.g. '200px', '20rem'). Default: 'full'
2369
2369
  * @attr {string} min-width - Minimum width as CSS length (e.g. '80px', '5rem')
2370
2370
  * @attr {string} max-width - Maximum width as CSS length (e.g. '200px', '20rem')
@@ -2401,7 +2401,7 @@ var NLDDTextCell = (_NLDDTextCell = class NLDDTextCell extends VisibilityMixin(L
2401
2401
  constructor(..._args) {
2402
2402
  super(..._args);
2403
2403
  this.size = "md";
2404
- this.color = "default";
2404
+ this.color = "content";
2405
2405
  this.width = "full";
2406
2406
  this.horizontalAlignment = "left";
2407
2407
  this.verticalAlignment = "center";
@@ -2458,7 +2458,7 @@ __decorate([property({
2458
2458
  })], NLDDTextCell.prototype, "size", void 0);
2459
2459
  __decorate([property({
2460
2460
  reflect: true,
2461
- converter: reflectNonDefault("default")
2461
+ converter: reflectNonDefault("content")
2462
2462
  })], NLDDTextCell.prototype, "color", void 0);
2463
2463
  __decorate([property({
2464
2464
  reflect: true,
@@ -2878,7 +2878,7 @@ var inlineDialogStyles = css`
2878
2878
 
2879
2879
  /* # Elements */
2880
2880
 
2881
- .inline-dialog__body {
2881
+ .inline-dialog {
2882
2882
  box-sizing: border-box;
2883
2883
  display: flex;
2884
2884
  max-width: var(--primitives-area-480);
@@ -2887,6 +2887,10 @@ var inlineDialogStyles = css`
2887
2887
  align-items: center;
2888
2888
  }
2889
2889
 
2890
+ .inline-dialog--left-aligned {
2891
+ align-items: stretch;
2892
+ }
2893
+
2890
2894
  .inline-dialog__main {
2891
2895
  display: flex;
2892
2896
  width: 100%;
@@ -2895,6 +2899,10 @@ var inlineDialogStyles = css`
2895
2899
  gap: var(--primitives-space-2);
2896
2900
  }
2897
2901
 
2902
+ .inline-dialog--left-aligned .inline-dialog__main {
2903
+ align-items: stretch;
2904
+ }
2905
+
2898
2906
  .inline-dialog__icon {
2899
2907
  display: flex;
2900
2908
  width: var(--_icon-size);
@@ -2926,8 +2934,21 @@ var inlineDialogStyles = css`
2926
2934
  text-wrap: pretty;
2927
2935
  }
2928
2936
 
2937
+ .inline-dialog--left-aligned .inline-dialog__text,
2938
+ .inline-dialog--left-aligned .inline-dialog__supporting-text {
2939
+ text-align: left;
2940
+ }
2941
+
2929
2942
  .inline-dialog__content {
2943
+ display: flex;
2930
2944
  width: 100%;
2945
+ flex-direction: column;
2946
+ align-items: center;
2947
+ padding-top: var(--primitives-space-16);
2948
+ }
2949
+
2950
+ .inline-dialog--left-aligned .inline-dialog__content {
2951
+ align-items: stretch;
2931
2952
  }
2932
2953
 
2933
2954
  .inline-dialog__content[hidden] {
@@ -2946,8 +2967,10 @@ var inlineDialogStyles = css`
2946
2967
  //#endregion
2947
2968
  //#region src/components/status-and-feedback/inline-dialog/inline-dialog.template.ts
2948
2969
  function inlineDialogTemplate(component) {
2970
+ const alignment = component._resolvedHorizontalAlignment;
2971
+ const blockClass = ["inline-dialog", alignment === "left" ? "inline-dialog--left-aligned" : ""].filter(Boolean).join(" ");
2949
2972
  return html`
2950
- <div class="inline-dialog__body">
2973
+ <div class=${blockClass}>
2951
2974
  <div class="inline-dialog__main">
2952
2975
  ${component.variant === "loading" ? html`
2953
2976
  <div class="inline-dialog__icon">
@@ -2976,7 +2999,7 @@ function inlineDialogTemplate(component) {
2976
2999
  <div class="inline-dialog__footer"
2977
3000
  ?hidden=${!component._hasActions}
2978
3001
  >
2979
- <nldd-button-group orientation="vertical">
3002
+ <nldd-button-group orientation=${alignment === "left" ? "horizontal" : "vertical"}>
2980
3003
  <slot name="actions"></slot>
2981
3004
  </nldd-button-group>
2982
3005
  </div>
@@ -3110,6 +3133,7 @@ NLDDButtonGroup = __decorate([customElement("nldd-button-group")], NLDDButtonGro
3110
3133
  * @attr {string} text - Main text (heading or paragraph, depending on heading-level)
3111
3134
  * @attr {string} supporting-text - Supporting text below the heading
3112
3135
  * @attr {1|2|3|4|5|6} heading-level - Renders text as h1–h6; absent renders a p
3136
+ * @attr {'left'|'center'} horizontal-alignment - Overrides the alignment of text, icon and actions. Unset (the default) derives it: content in the default slot means a task, which aligns left; a bare message stays centered. Left alignment also lays the actions out in a row instead of stacked full-width.
3113
3137
  *
3114
3138
  * @slot - Optional custom content between text and actions
3115
3139
  * @slot actions - nldd-button elements, wrapped in nldd-button-group (max 3)
@@ -3125,6 +3149,7 @@ var NLDDInlineDialog = (_NLDDInlineDialog = class NLDDInlineDialog extends LitEl
3125
3149
  this.text = "";
3126
3150
  this.supportingText = "";
3127
3151
  this.headingLevel = null;
3152
+ this.horizontalAlignment = "";
3128
3153
  this._hasContent = false;
3129
3154
  this._hasActions = false;
3130
3155
  }
@@ -3134,6 +3159,14 @@ var NLDDInlineDialog = (_NLDDInlineDialog = class NLDDInlineDialog extends LitEl
3134
3159
  if (this.icon) return this.icon;
3135
3160
  return "";
3136
3161
  }
3162
+ /** Anything in the default slot is a task (a form, a list) rather than a
3163
+ * message, and a task reads left-aligned. Derived rather than defaulted so
3164
+ * the empty state — the other half of this component — stays centered
3165
+ * without every consumer setting an attribute. */
3166
+ get _resolvedHorizontalAlignment() {
3167
+ if (this.horizontalAlignment) return this.horizontalAlignment;
3168
+ return this._hasContent ? "left" : "center";
3169
+ }
3137
3170
  firstUpdated() {
3138
3171
  const contentSlot = this.shadowRoot?.querySelector("slot:not([name])");
3139
3172
  const actionsSlot = this.shadowRoot?.querySelector("slot[name=\"actions\"]");
@@ -3188,6 +3221,11 @@ __decorate([property({
3188
3221
  reflect: true,
3189
3222
  attribute: "heading-level"
3190
3223
  })], NLDDInlineDialog.prototype, "headingLevel", void 0);
3224
+ __decorate([property({
3225
+ reflect: true,
3226
+ attribute: "horizontal-alignment",
3227
+ converter: reflectNonDefault("")
3228
+ })], NLDDInlineDialog.prototype, "horizontalAlignment", void 0);
3191
3229
  __decorate([state()], NLDDInlineDialog.prototype, "_hasContent", void 0);
3192
3230
  __decorate([state()], NLDDInlineDialog.prototype, "_hasActions", void 0);
3193
3231
  NLDDInlineDialog = __decorate([customElement("nldd-inline-dialog")], NLDDInlineDialog);
@@ -6379,8 +6417,8 @@ function avatarTemplate(component) {
6379
6417
  * @element nldd-avatar
6380
6418
  *
6381
6419
  * @attr {string} type - `person` (circle, person icon) or `organization` (rounded, building icon); default `person`
6382
- * @attr {string} size - `full` (default) scales with the container, like nldd-icon; or a fixed size in px (spacer-aligned: 16, 20, 24, 28, 32, 40, 44, 48, 56, 64, 80, 96). Empty behaves as `full`. The initials and the icon scale along
6383
- * @attr {string} color - `default` (neutral fill) or `inherit` (fill in the content color: the `--context-content-color` channel, or `currentColor` when that is unset; text in the contrast color, so the avatar can replace an icon in a button for instance); default `default`
6420
+ * @attr {string} size - `full` (the default) scales with the container, like nldd-icon; or a fixed size in px (spacer-aligned: 16, 20, 24, 28, 32, 40, 44, 48, 56, 64, 80, 96). The initials and the icon scale along
6421
+ * @attr {string} color - `neutral` (the default, a neutral fill) or `inherit` (fill in the content color: the `--context-content-color` channel, or `currentColor` when that is unset; text in the contrast color, so the avatar can replace an icon in a button for instance)
6384
6422
  * @attr {boolean} icon-aligned - Shrinks the visible shape to 5/6 of the host, centered, so the avatar aligns optically with an icon on the same grid (an icon glyph has built-in margin)
6385
6423
  * @attr {string} name - Name of the person or organization; supplies the derived initials and the accessible label
6386
6424
  * @attr {string} initials - Explicit initials, at most 3 characters (overrides what is derived from `name`; also for organization acronyms)
@@ -6389,7 +6427,7 @@ function avatarTemplate(component) {
6389
6427
  * @attr {string} icon - Overrides the type-dependent fallback icon
6390
6428
  * @attr {string} accessible-label - Name of the link or button; without it `name` is used
6391
6429
  * @attr {boolean} decorative - Hides the avatar from assistive software (use when the name already stands beside it as text)
6392
- * @attr {string} tooltip-timing - When the name appears as a tooltip on hover or focus: `default` (after 700ms; the default), `instant`, or `never`. An avatar shows no text, so without a tooltip the name is readable by assistive software only. A `decorative` avatar shows none regardless: there the name already stands beside it as text
6430
+ * @attr {string} tooltip-timing - When the name appears as a tooltip on hover or focus: `delay` (the default, after 700ms), `instant`, or `never`. An avatar shows no text, so without a tooltip the name is readable by assistive software only. A `decorative` avatar shows none regardless: there the name already stands beside it as text
6393
6431
  * @attr {string} href - Makes the avatar a link to this URL; the shape itself becomes the link, so the hit area and the focus ring follow it
6394
6432
  * @attr {boolean} no-tab - Takes the control out of the tab order (tabindex="-1"), for an avatar that is a link or a button inside a roving container (a row of an nldd-list). Does nothing on a decorative avatar.
6395
6433
  * @attr {boolean} button - Makes the avatar a button; ignored when `href` is set
@@ -6416,8 +6454,8 @@ var NLDDAvatar = (_NLDDAvatar = class NLDDAvatar extends withTranslations(LitEle
6416
6454
  constructor(..._args) {
6417
6455
  super(..._args);
6418
6456
  this.type = "person";
6419
- this.size = "";
6420
- this.color = "default";
6457
+ this.size = "full";
6458
+ this.color = "neutral";
6421
6459
  this.iconAligned = false;
6422
6460
  this.name = "";
6423
6461
  this.initials = "";
@@ -6426,7 +6464,7 @@ var NLDDAvatar = (_NLDDAvatar = class NLDDAvatar extends withTranslations(LitEle
6426
6464
  this.icon = "";
6427
6465
  this.accessibleLabel = "";
6428
6466
  this.decorative = false;
6429
- this.tooltipTiming = "default";
6467
+ this.tooltipTiming = "delay";
6430
6468
  this.href = "";
6431
6469
  this.noTab = false;
6432
6470
  this.button = false;
@@ -6527,11 +6565,11 @@ __decorate([property({
6527
6565
  })], NLDDAvatar.prototype, "type", void 0);
6528
6566
  __decorate([property({
6529
6567
  reflect: true,
6530
- converter: reflectNonDefault("")
6568
+ converter: reflectNonDefault("full")
6531
6569
  })], NLDDAvatar.prototype, "size", void 0);
6532
6570
  __decorate([property({
6533
6571
  reflect: true,
6534
- converter: reflectNonDefault("default")
6572
+ converter: reflectNonDefault("neutral")
6535
6573
  })], NLDDAvatar.prototype, "color", void 0);
6536
6574
  __decorate([property({
6537
6575
  type: Boolean,
@@ -6554,7 +6592,7 @@ __decorate([property({
6554
6592
  __decorate([property({
6555
6593
  reflect: true,
6556
6594
  attribute: "tooltip-timing",
6557
- converter: reflectNonDefault("default")
6595
+ converter: reflectNonDefault("delay")
6558
6596
  })], NLDDAvatar.prototype, "tooltipTiming", void 0);
6559
6597
  __decorate([property({
6560
6598
  type: String,
@@ -9085,7 +9123,7 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
9085
9123
  });
9086
9124
  }
9087
9125
  _updateEmpty() {
9088
- const items = this._getItems();
9126
+ const items = this.hasUpdated ? this._getItems() : Array.from(this.querySelectorAll(":scope > nldd-list-item"));
9089
9127
  this._isEmpty = items.length === 0 || items.every((item) => item.hasAttribute("hidden"));
9090
9128
  this._hasItems = items.length > 0;
9091
9129
  this._hasEmptyState = !!this.querySelector(":scope > [slot=\"empty\"]");
@@ -9100,8 +9138,10 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
9100
9138
  * the app's to write and a house sentence was wrong more often than right.
9101
9139
  * That leaves a real risk of a blank area with nothing in the accessibility
9102
9140
  * tree either, so the list asks the person building it rather than
9103
- * answering for them. A listbox before you have typed is left alone: an
9104
- * empty search is not an empty list.
9141
+ * answering for them. It asks about the state it is actually in: rows that
9142
+ * are all hidden are covered by [slot="no-results"] as well as by the
9143
+ * [slot="empty"] it falls back to. A listbox before you have typed is left
9144
+ * alone: an empty search is not an empty list.
9105
9145
  */
9106
9146
  _warnSilentEmpty() {}
9107
9147
  /** Every option needs a stable id for aria-activedescendant. Assign one to
@@ -11540,16 +11580,16 @@ var nlddCodeMirrorTheme = [baseTheme, syntaxHighlighting(highlightStyle)];
11540
11580
  //#endregion
11541
11581
  //#region src/utilities/codemirror/languages.ts
11542
11582
  var loaders = {
11543
- json: async () => (await import("./dist-CRsR6ckU.js")).json(),
11544
- yaml: async () => (await import("./dist-la_b3ZSq.js")).yaml(),
11545
- javascript: async () => (await import("./dist-CjaG8dOI.js").then((n) => n.t)).javascript(),
11546
- typescript: async () => (await import("./dist-CjaG8dOI.js").then((n) => n.t)).javascript({ typescript: true }),
11547
- css: async () => (await import("./dist-C09E9-dJ.js").then((n) => n.r)).css(),
11548
- html: async () => (await import("./dist-762GHPyH.js").then((n) => n.t)).html(),
11549
- xml: async () => (await import("./dist-BJI4-6iQ.js")).xml(),
11550
- python: async () => (await import("./dist-BB7TxeMK.js")).python(),
11551
- rust: async () => (await import("./dist-DZoFKUO-.js")).rust(),
11552
- sql: async () => (await import("./dist-DaavuU6W.js")).sql(),
11583
+ json: async () => (await import("./dist-LX8LuMac.js")).json(),
11584
+ yaml: async () => (await import("./dist-CSrO_mYe.js")).yaml(),
11585
+ javascript: async () => (await import("./dist-ByH8hQze.js").then((n) => n.t)).javascript(),
11586
+ typescript: async () => (await import("./dist-ByH8hQze.js").then((n) => n.t)).javascript({ typescript: true }),
11587
+ css: async () => (await import("./dist-B_Mnmrj4.js").then((n) => n.r)).css(),
11588
+ html: async () => (await import("./dist-CX3DK4i7.js").then((n) => n.t)).html(),
11589
+ xml: async () => (await import("./dist-eVFegyjM.js")).xml(),
11590
+ python: async () => (await import("./dist-CRwuSgMM.js")).python(),
11591
+ rust: async () => (await import("./dist-CbCO0WCd.js")).rust(),
11592
+ sql: async () => (await import("./dist-DXZ-cFAl.js")).sql(),
11553
11593
  markdown: async () => (await Promise.resolve().then(() => dist_exports)).markdown(),
11554
11594
  bash: async () => new LanguageSupport(StreamLanguage.define((await import("./shell-C61GCsJA.js")).shell)),
11555
11595
  toml: async () => new LanguageSupport(StreamLanguage.define((await import("./toml-C2HBbPgG.js")).toml)),
@@ -12834,11 +12874,12 @@ var nlddRichTextTranslations = { "components.rich-text.table-scroll-label": "Scr
12834
12874
  * `overflow-wrap: break-word` safety net on p/li is always on, independent of
12835
12875
  * this attribute, so long URLs and compounds break neatly instead of
12836
12876
  * overflowing even without a dictionary.
12837
- * @attr {string} color - 'inherit' lets all text follow the color of the
12838
- * surface (for colored areas such as the filled categories). Links stay
12839
- * underlined as an affordance; secondary text (figcaption) gets the same
12840
- * color at a lowered opacity. Known v1 gaps: inline code, mark, tables and hr
12841
- * keep their own surfaces. Empty = the default content colors.
12877
+ * @attr {string} color - 'content' (the default) takes the system's own content
12878
+ * colors, each element its own. 'inherit' lets all text follow the color of
12879
+ * the surface instead (for colored areas such as the filled categories).
12880
+ * Links stay underlined as an affordance; secondary text (figcaption) gets
12881
+ * the same color at a lowered opacity. Known v1 gaps: inline code, mark,
12882
+ * tables and hr keep their own surfaces.
12842
12883
  * @attr {object} translations - Override translation keys; unset keys fall back to Dutch
12843
12884
  */
12844
12885
  var MANAGED_LABEL_ATTR = "data-nldd-managed-label";
@@ -12848,7 +12889,7 @@ var NLDDRichText = class NLDDRichText extends LitElement {
12848
12889
  this.spacing = "snug";
12849
12890
  this.centered = false;
12850
12891
  this.hyphens = false;
12851
- this.color = "";
12892
+ this.color = "content";
12852
12893
  this.translations = {};
12853
12894
  }
12854
12895
  _t(key) {
@@ -12907,7 +12948,7 @@ __decorate([property({
12907
12948
  })], NLDDRichText.prototype, "hyphens", void 0);
12908
12949
  __decorate([property({
12909
12950
  reflect: true,
12910
- converter: reflectNonDefault("")
12951
+ converter: reflectNonDefault("content")
12911
12952
  })], NLDDRichText.prototype, "color", void 0);
12912
12953
  __decorate([property({ type: Object })], NLDDRichText.prototype, "translations", void 0);
12913
12954
  NLDDRichText = __decorate([customElement("nldd-rich-text")], NLDDRichText);
@@ -13420,7 +13461,7 @@ function template$10() {
13420
13461
  * @attr {string} size - Text size on the body scale: 'xxs' | 'xs' | 'sm' | 'md' | 'lg' (default: 'md')
13421
13462
  * @attr {string} weight - Font weight: 'regular' | 'medium' | 'bold' (default: 'regular'). With 'bold' a slotted `<strong>` no longer stands out: there is nothing bolder in the scale.
13422
13463
  * @attr {string} line-height - Line height: 'flat' | 'tight' | 'snug' | 'loose' (default: 'snug')
13423
- * @attr {string} color - Text color: 'default' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical' | 'inherit' (default: 'default'). 'default' and 'secondary' follow the surrounding content channel; 'inherit' takes the color it inherits, for text on a painted surface.
13464
+ * @attr {string} color - Text color: 'content' (the default) | 'secondary' | 'accent' | 'success' | 'warning' | 'critical' | 'inherit'. 'content' and 'secondary' follow the surrounding content channel; 'inherit' takes the color it inherits, for text on a painted surface.
13424
13465
  * @attr {string} horizontal-alignment - Alignment of the text within the block: 'left' | 'center' | 'right' (default: 'left'). Aligns the words; `horizontal-alignment` on nldd-container moves the box.
13425
13466
  *
13426
13467
  * @slot - The text. Inline elements work as they are: `<strong>` and `<b>` take the bold weight from the scale, `<a>`, `<em>` and components such as `nldd-tag` are left alone.
@@ -13432,7 +13473,7 @@ var NLDDText = (_NLDDText = class NLDDText extends LitElement {
13432
13473
  this.size = "md";
13433
13474
  this.weight = "regular";
13434
13475
  this.lineHeight = "snug";
13435
- this.color = "default";
13476
+ this.color = "content";
13436
13477
  this.horizontalAlignment = "left";
13437
13478
  }
13438
13479
  render() {
@@ -13454,7 +13495,7 @@ __decorate([property({
13454
13495
  })], NLDDText.prototype, "lineHeight", void 0);
13455
13496
  __decorate([property({
13456
13497
  reflect: true,
13457
- converter: reflectNonDefault("default")
13498
+ converter: reflectNonDefault("content")
13458
13499
  })], NLDDText.prototype, "color", void 0);
13459
13500
  __decorate([property({
13460
13501
  reflect: true,
@@ -13743,10 +13784,10 @@ function titleTemplate() {
13743
13784
  * @element nldd-title
13744
13785
  *
13745
13786
  * @attr {number} size - Visual size of the title: 1–6 (default: 3)
13746
- * @attr {string} color - 'inherit' lets the title follow the text color of the
13747
- * surface (for colored areas such as the filled categories); overline and
13748
- * subtitle get the same color at a lowered opacity. Empty = the default
13749
- * content colors.
13787
+ * @attr {string} color - 'content' (the default) takes the system's own content
13788
+ * colors. 'inherit' lets the title follow the text color of the surface
13789
+ * instead (for colored areas such as the filled categories); overline and
13790
+ * subtitle get the same color at a lowered opacity.
13750
13791
  *
13751
13792
  * @slot overline - Optional overline above the title
13752
13793
  * @slot - Title text (use h1–h6 for semantics)
@@ -13758,7 +13799,7 @@ var NLDDTitle = (_NLDDTitle = class NLDDTitle extends LitElement {
13758
13799
  constructor(..._args) {
13759
13800
  super(..._args);
13760
13801
  this.size = 3;
13761
- this.color = "";
13802
+ this.color = "content";
13762
13803
  }
13763
13804
  render() {
13764
13805
  return titleTemplate();
@@ -13770,7 +13811,7 @@ __decorate([property({
13770
13811
  })], NLDDTitle.prototype, "size", void 0);
13771
13812
  __decorate([property({
13772
13813
  reflect: true,
13773
- converter: reflectNonDefault("")
13814
+ converter: reflectNonDefault("content")
13774
13815
  })], NLDDTitle.prototype, "color", void 0);
13775
13816
  NLDDTitle = __decorate([customElement("nldd-title")], NLDDTitle);
13776
13817
  //#endregion
@@ -17834,7 +17875,7 @@ var codeEditorStyles = css`
17834
17875
  /* # Host */
17835
17876
 
17836
17877
  :host {
17837
- --__caret-width: var(--primitives-border-width-regular);
17878
+ --_caret-width: var(--primitives-border-width-regular);
17838
17879
  --_corner-radius: 0;
17839
17880
  --_background-color: transparent;
17840
17881
  --_border-color: transparent;
@@ -17936,7 +17977,7 @@ var codeEditorStyles = css`
17936
17977
  CodeMirror's theme cursor color without depending on an attribute. */
17937
17978
  :host .cm-cursor.cm-cursor {
17938
17979
  border-left-color: var(--primitives-color-accent-600);
17939
- border-left-width: var(--__caret-width);
17980
+ border-left-width: var(--_caret-width);
17940
17981
  }
17941
17982
 
17942
17983
  /* Resize model — rows is the floor in every mode:
@@ -28668,8 +28709,65 @@ var pasteURLAsLink = /*@__PURE__*/ EditorView.domEventHandlers({ paste: (event,
28668
28709
  return true;
28669
28710
  } });
28670
28711
  //#endregion
28712
+ //#region src/components/inputs/text-editor/text-editor.syntax.ts
28713
+ /** The innermost node at `pos` that `match` accepts, looking outward from there.
28714
+ * `side` decides which node a position between two of them belongs to: -1 the
28715
+ * one that ends there, 1 the one that starts there. Null when none matches. */
28716
+ function enclosingNode(state, pos, side, match) {
28717
+ for (let node = syntaxTree(state).resolveInner(pos, side); node; node = node.parent) if (match(node)) return node;
28718
+ return null;
28719
+ }
28720
+ /** `enclosingNode` for the common case: a node that goes by one of these names. */
28721
+ function enclosingNamed(state, pos, side, names) {
28722
+ return enclosingNode(state, pos, side, typeof names === "string" ? (node) => node.name === names : (node) => names.has(node.name));
28723
+ }
28724
+ //#endregion
28671
28725
  //#region src/components/inputs/text-editor/text-editor.mentions.ts
28672
- var MENTION_QUERY = /@[\w.-]*$/;
28726
+ /** The query being typed: a trigger at the start of the line or after
28727
+ * whitespace, followed by name characters, up to the caret. Anchored that way
28728
+ * so a trigger inside a word (an e-mail address, `piet@sam`) stays a plain
28729
+ * character. The name class is ASCII for now; widening it changes what a
28730
+ * source receives and is its own change. */
28731
+ var queryPatterns = /* @__PURE__ */ new Map();
28732
+ function queryPattern(triggers) {
28733
+ const chars = Array.from(new Set(triggers.filter((t) => t.length === 1 && !/[\s\w]/.test(t)))).sort().join("");
28734
+ if (!chars) return null;
28735
+ let pattern = queryPatterns.get(chars);
28736
+ if (!pattern) {
28737
+ pattern = new RegExp(`(^|\\s)([${chars.replace(/[\\\]^-]/g, "\\$&")}])([\\w.-]*)$`);
28738
+ queryPatterns.set(chars, pattern);
28739
+ }
28740
+ return pattern;
28741
+ }
28742
+ /** Code is quoted verbatim, so a trigger in a fenced block, an indented block
28743
+ * or a backtick span is not a typeahead and must not open the list. */
28744
+ var CODE_NODES = /* @__PURE__ */ new Set([
28745
+ "FencedCode",
28746
+ "CodeBlock",
28747
+ "InlineCode"
28748
+ ]);
28749
+ function inCode(state, pos) {
28750
+ return enclosingNamed(state, pos, 1, CODE_NODES) !== null;
28751
+ }
28752
+ /** The typeahead query at `pos` for one of `triggers`, or null when the text
28753
+ * before the caret is not one. `from` is the trigger, `query` the typed text
28754
+ * without it. Both the completion source and the reopen-on-delete path go
28755
+ * through this, so they cannot disagree about what counts as a query. */
28756
+ function typeaheadQueryAt(state, pos, triggers) {
28757
+ const pattern = queryPattern(triggers);
28758
+ if (!pattern) return null;
28759
+ const line = state.doc.lineAt(pos);
28760
+ const match = pattern.exec(state.sliceDoc(line.from, pos));
28761
+ if (!match) return null;
28762
+ const from = line.from + match.index + match[1].length;
28763
+ if (inCode(state, from)) return null;
28764
+ return {
28765
+ from,
28766
+ to: pos,
28767
+ query: match[3],
28768
+ trigger: match[2]
28769
+ };
28770
+ }
28673
28771
  /** Href prefix that marks a markdown link as a mention. */
28674
28772
  var MENTION_HREF_PREFIX = "user:";
28675
28773
  /** The markdown-compatible token stored for a mention (degrades to a plain link).
@@ -28679,7 +28777,11 @@ var MENTION_HREF_PREFIX = "user:";
28679
28777
  * `]` or `)` would otherwise start a second, attacker-shaped link. The render
28680
28778
  * layer reverses both via `unescapeMentionLabel` / `decodeMentionId`. */
28681
28779
  function mentionToken(candidate) {
28682
- return `[@${candidate.label.replace(/[[\]\\]/g, (c) => "\\" + c)}](${MENTION_HREF_PREFIX}${encodeURIComponent(candidate.id).replace(/[()<>]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase())})`;
28780
+ return `[@${candidate.text.replace(/[[\]\\]/g, (c) => "\\" + c)}](${MENTION_HREF_PREFIX}${encodeURIComponent(candidate.id).replace(/[()<>]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase())})`;
28781
+ }
28782
+ /** What the built-in mention writes: the token and a space to go on typing. */
28783
+ function mentionInsert(candidate) {
28784
+ return `${mentionToken(candidate)} `;
28683
28785
  }
28684
28786
  /** Reverse `mentionToken`'s label escaping for display. */
28685
28787
  function unescapeMentionLabel(label) {
@@ -28694,38 +28796,86 @@ function decodeMentionId(id) {
28694
28796
  return id;
28695
28797
  }
28696
28798
  }
28697
- function completionSource(getSource, onInsert) {
28799
+ function defaultInsert(trigger) {
28800
+ return (candidate) => `${trigger}${candidate.text} `;
28801
+ }
28802
+ /** The avatar, icon or symbol in front of a row, or nothing. A decorative
28803
+ * avatar: the name stands beside it as the row's text. The icon is the size of
28804
+ * a menu item's, and the symbol takes the same box, so a list that mixes them
28805
+ * lines up. */
28806
+ function renderLead(completion) {
28807
+ const { candidate } = completion;
28808
+ if (candidate.avatar) {
28809
+ const avatar = document.createElement("nldd-avatar");
28810
+ avatar.setAttribute("name", candidate.text);
28811
+ avatar.setAttribute("size", "32");
28812
+ avatar.setAttribute("decorative", "");
28813
+ if (candidate.avatar.src) avatar.setAttribute("src", candidate.avatar.src);
28814
+ if (candidate.avatar.type) avatar.setAttribute("type", candidate.avatar.type);
28815
+ return avatar;
28816
+ }
28817
+ if (candidate.icon) {
28818
+ const icon = document.createElement("nldd-icon");
28819
+ icon.setAttribute("name", candidate.icon);
28820
+ icon.setAttribute("size", "20");
28821
+ icon.setAttribute("aria-hidden", "true");
28822
+ return icon;
28823
+ }
28824
+ if (candidate.symbol) {
28825
+ const symbol = document.createElement("span");
28826
+ symbol.className = "cm-nldd-symbol";
28827
+ symbol.textContent = candidate.symbol;
28828
+ symbol.setAttribute("aria-hidden", "true");
28829
+ return symbol;
28830
+ }
28831
+ return null;
28832
+ }
28833
+ /** A row with an avatar takes two lines: the text above, the supporting text
28834
+ * under it, like a title cell. */
28835
+ function rowClass(completion) {
28836
+ return completion.candidate.avatar ? "cm-nldd-row-avatar" : "";
28837
+ }
28838
+ function completionSource(getTypeaheads, onChoose) {
28698
28839
  return async (context) => {
28699
- const match = context.matchBefore(/@[\w.-]*/);
28700
- if (!match || match.from === match.to && !context.explicit) return null;
28701
- const source = getSource();
28702
- if (!source) return null;
28703
- const candidates = await source(match.text.slice(1));
28704
- if (!candidates?.length) return null;
28705
- return {
28706
- from: match.from,
28707
- filter: false,
28708
- options: candidates.map((candidate) => ({
28709
- label: `@${candidate.label}`,
28710
- detail: candidate.detail,
28840
+ const lists = getTypeaheads();
28841
+ const match = typeaheadQueryAt(context.state, context.pos, lists.map((t) => t.trigger));
28842
+ if (!match) return null;
28843
+ const onTrigger = lists.filter((t) => t.trigger === match.trigger);
28844
+ const results = await Promise.all(onTrigger.map((t) => t.source(match.query)));
28845
+ const options = [];
28846
+ results.forEach((candidates, index) => {
28847
+ const typeahead = onTrigger[index];
28848
+ const insert = typeahead.insert ?? defaultInsert(match.trigger);
28849
+ for (const candidate of candidates ?? []) options.push({
28850
+ label: `${match.trigger}${candidate.text}`,
28851
+ detail: candidate.supportingText,
28852
+ candidate,
28711
28853
  apply: (view, _completion, from, to) => {
28712
- const token = mentionToken(candidate);
28854
+ const text = insert(candidate);
28713
28855
  view.dispatch({
28714
28856
  changes: {
28715
28857
  from,
28716
28858
  to,
28717
- insert: `${token} `
28859
+ insert: text
28718
28860
  },
28719
- selection: { anchor: from + token.length + 1 }
28861
+ selection: { anchor: from + text.length },
28862
+ userEvent: "input.complete"
28720
28863
  });
28721
- onInsert({
28722
- id: candidate.id,
28723
- label: candidate.label,
28864
+ onChoose({
28865
+ typeahead,
28866
+ trigger: match.trigger,
28867
+ candidate,
28724
28868
  from,
28725
- to: from + token.length
28869
+ to: from + text.length
28726
28870
  });
28727
28871
  }
28728
- }))
28872
+ });
28873
+ });
28874
+ if (!options.length) return null;
28875
+ return {
28876
+ from: match.from,
28877
+ filter: false,
28878
+ options
28729
28879
  };
28730
28880
  };
28731
28881
  }
@@ -28739,6 +28889,8 @@ var popupTheme = EditorView.theme({
28739
28889
  ".cm-tooltip.cm-tooltip-autocomplete > ul": {
28740
28890
  fontFamily: "var(--primitives-font-family-body)",
28741
28891
  fontSize: "var(--primitives-font-size-100)",
28892
+ minWidth: "var(--primitives-area-280)",
28893
+ boxSizing: "border-box",
28742
28894
  maxHeight: "14em",
28743
28895
  margin: "0",
28744
28896
  padding: "var(--primitives-space-8)"
@@ -28754,6 +28906,32 @@ var popupTheme = EditorView.theme({
28754
28906
  color: "var(--semantics-content-color)",
28755
28907
  cursor: "default"
28756
28908
  },
28909
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li > :is(nldd-avatar, nldd-icon, .cm-nldd-symbol)": { flex: "none" },
28910
+ ".cm-nldd-symbol": {
28911
+ display: "inline-flex",
28912
+ justifyContent: "center",
28913
+ inlineSize: "20px"
28914
+ },
28915
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li.cm-nldd-row-avatar": {
28916
+ display: "grid",
28917
+ gridTemplateColumns: "auto minmax(0, 1fr)",
28918
+ alignContent: "center",
28919
+ rowGap: "0",
28920
+ minHeight: "var(--semantics-controls-md-min-size)"
28921
+ },
28922
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li.cm-nldd-row-avatar > nldd-avatar": {
28923
+ gridRow: "span 2",
28924
+ alignSelf: "center"
28925
+ },
28926
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li.cm-nldd-row-avatar > .cm-completionLabel": {
28927
+ gridColumn: "2",
28928
+ font: "var(--primitives-font-body-md-regular-tight)"
28929
+ },
28930
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li.cm-nldd-row-avatar > .cm-completionDetail": {
28931
+ gridColumn: "2",
28932
+ marginLeft: "0",
28933
+ font: "var(--primitives-font-body-sm-regular-tight)"
28934
+ },
28757
28935
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
28758
28936
  backgroundColor: "var(--components-menu-item-is-highlighted-background-color)",
28759
28937
  color: "var(--components-menu-item-is-highlighted-content-color)"
@@ -28771,22 +28949,26 @@ var popupTheme = EditorView.theme({
28771
28949
  "@media (pointer: coarse)": { ".cm-tooltip.cm-tooltip-autocomplete > ul > li": { minHeight: "var(--semantics-controls-md-min-size)" } }
28772
28950
  });
28773
28951
  /**
28774
- * @-mention typeahead. `getSource` is called with the text typed after `@` and
28775
- * returns (a promise of) candidates; selecting one inserts the token and calls
28776
- * `onInsert`. Without a source it is inert.
28952
+ * The typeaheads. `getTypeaheads` is read on every keystroke, so the editor can
28953
+ * hand over its current lists; choosing a candidate writes what the list's
28954
+ * `insert` says and calls `onChoose`. Without lists it is inert.
28777
28955
  */
28778
- var reopenOnDelete = EditorView.updateListener.of((update) => {
28779
- if (!update.docChanged || completionStatus(update.state) !== null) return;
28780
- if (!update.transactions.some((tr) => tr.isUserEvent("delete"))) return;
28781
- const { head } = update.state.selection.main;
28782
- const before = update.state.sliceDoc(update.state.doc.lineAt(head).from, head);
28783
- if (MENTION_QUERY.test(before)) startCompletion(update.view);
28784
- });
28785
- function mentions(getSource, onInsert) {
28956
+ function typeaheads(getTypeaheads, onChoose) {
28957
+ const reopenOnDelete = EditorView.updateListener.of((update) => {
28958
+ if (!update.docChanged || completionStatus(update.state) !== null) return;
28959
+ if (!update.transactions.some((tr) => tr.isUserEvent("delete"))) return;
28960
+ const triggers = getTypeaheads().map((t) => t.trigger);
28961
+ if (typeaheadQueryAt(update.state, update.state.selection.main.head, triggers)) startCompletion(update.view);
28962
+ });
28786
28963
  return [
28787
28964
  autocompletion({
28788
- override: [completionSource(getSource, onInsert)],
28789
- icons: false
28965
+ override: [completionSource(getTypeaheads, onChoose)],
28966
+ icons: false,
28967
+ addToOptions: [{
28968
+ position: 20,
28969
+ render: renderLead
28970
+ }],
28971
+ optionClass: rowClass
28790
28972
  }),
28791
28973
  reopenOnDelete,
28792
28974
  popupTheme
@@ -28845,15 +29027,22 @@ var LinkOpenWidget = class extends WidgetType {
28845
29027
  ignoreEvent() {
28846
29028
  return true;
28847
29029
  }
28848
- /** Keep the caret at the badge's right edge text-height, not the badge's own box
28849
- * height (which is taller and would flip with the cursor's arrival direction). */
28850
- coordsAt(dom) {
29030
+ /** The caret at the link's end has two places to be, and `side` says which: on
29031
+ * the link's side of the badge, or after the badge. Both at text height, not
29032
+ * the badge's own box (taller, and it would flip with the arrival direction).
29033
+ * The cursor layer treats the badge as a character with a stop on either
29034
+ * side, whether the link is a bare URL (text typed on the link's side extends
29035
+ * it) or a `[text](url)` link (text typed there lands after the link, and
29036
+ * so after its badge). Ignoring `side` drew both after the badge, so deleting
29037
+ * the last character of a URL looked like editing outside it. */
29038
+ coordsAt(dom, _pos, side) {
28851
29039
  const badge = dom.getBoundingClientRect();
28852
29040
  const box = textCaretBox(dom) ?? {
28853
29041
  top: badge.top,
28854
29042
  bottom: badge.bottom
28855
29043
  };
28856
- const x = badge.right + parseFloat(getComputedStyle(dom).marginRight);
29044
+ const cs = getComputedStyle(dom);
29045
+ const x = side < 0 ? badge.left - parseFloat(cs.marginLeft) : badge.right + parseFloat(cs.marginRight);
28857
29046
  return {
28858
29047
  left: x,
28859
29048
  right: x,
@@ -28973,6 +29162,243 @@ function linkOpenBadge(label) {
28973
29162
  }
28974
29163
  }, { decorations: (plugin) => plugin.decorations });
28975
29164
  }
29165
+ /** Whether a link with a badge ends at `pos`: a `[text](url)` link or a bare
29166
+ * URL, with a destination the badge accepts. The same tests as `buildBadges`,
29167
+ * so a mention (a link too, but without a badge) is not one. */
29168
+ function linkEndsAt(state, pos) {
29169
+ let refs = null;
29170
+ return enclosingNode(state, pos, -1, (node) => {
29171
+ if (node.to !== pos) return false;
29172
+ if (node.name === "Link") return hrefOf(state, node, refs ?? (refs = referenceDefs(state))) !== null;
29173
+ return node.name === "URL" && !inLinkContext(node) && bareHref(state, node) !== null;
29174
+ }) !== null;
29175
+ }
29176
+ /** Whether a bare URL with a badge ends at `pos`: the link that text typed at
29177
+ * its end extends, unlike a `[text](url)` link, which ends at its `)`. */
29178
+ function bareUrlEndsAt(state, pos) {
29179
+ return enclosingNode(state, pos, -1, (node) => node.name === "URL" && node.to === pos && !inLinkContext(node) && bareHref(state, node) !== null) !== null;
29180
+ }
29181
+ //#endregion
29182
+ //#region src/components/inputs/text-editor/text-editor.emphasis.ts
29183
+ /** The emphasis nodes that live by the flanking rules, with the marker the
29184
+ * editor writes for them. Inline code is not among them: a code span keeps its
29185
+ * whitespace and never fails to close. */
29186
+ var EMPHASIS_MARKERS = /* @__PURE__ */ new Map([
29187
+ ["StrongEmphasis", "**"],
29188
+ ["Emphasis", "*"],
29189
+ ["Strikethrough", "~~"]
29190
+ ]);
29191
+ /** The blocks that hold inline content. A held emphasis must stay within one:
29192
+ * a paragraph break inside it (Enter twice) ends the run for good. */
29193
+ var INLINE_BLOCKS = /* @__PURE__ */ new Set([
29194
+ "Paragraph",
29195
+ "ATXHeading1",
29196
+ "ATXHeading2",
29197
+ "ATXHeading3",
29198
+ "ATXHeading4",
29199
+ "ATXHeading5",
29200
+ "ATXHeading6",
29201
+ "SetextHeading1",
29202
+ "SetextHeading2",
29203
+ "TableCell"
29204
+ ]);
29205
+ /** What may sit between the end of a run's text and its closing marker for the
29206
+ * marker to be moved back to the text: whitespace, and after a line break the
29207
+ * scaffolding Enter writes for the next list item, task or quote line. Text
29208
+ * there means the break fell inside the run, and that is left alone. */
29209
+ var TRAIL_RE = /^\s*(?:\n\s*(?:(?:[-*+]|\d+[.)]|>|\[[ xX]\])\s*)*)*$/;
29210
+ /** Starts a hold on markers just written around an empty selection: `****`
29211
+ * parses as nothing, so there is no node to remember, but what is typed
29212
+ * between them is meant to be bold. */
29213
+ var holdEmphasis = StateEffect.define({ map: (value, mapping) => ({
29214
+ ...value,
29215
+ from: mapping.mapPos(value.from, 1),
29216
+ to: mapping.mapPos(value.to, -1)
29217
+ }) });
29218
+ function emphasisAround(state, pos, side) {
29219
+ return enclosingNode(state, pos, side, (node) => EMPHASIS_MARKERS.has(node.name));
29220
+ }
29221
+ function inlineBlockAt(state, pos) {
29222
+ return enclosingNamed(state, pos, 1, INLINE_BLOCKS);
29223
+ }
29224
+ /** The innermost emphasis the main selection is inside of on both of its sides,
29225
+ * as the tree parses it now. Just before the opening marker or just after the
29226
+ * closing one is outside: text typed there lands outside the emphasis. */
29227
+ function liveEmphasis(state) {
29228
+ const { from, to } = state.selection.main;
29229
+ const left = emphasisAround(state, from, -1);
29230
+ const right = left && emphasisAround(state, to, 1);
29231
+ if (!left || !right || left.from !== right.from || left.to !== right.to) return null;
29232
+ const len = EMPHASIS_MARKERS.get(left.name).length;
29233
+ return {
29234
+ from: left.from,
29235
+ to: left.to,
29236
+ name: left.name,
29237
+ marker: state.sliceDoc(left.from, left.from + len),
29238
+ live: true
29239
+ };
29240
+ }
29241
+ /** Whether a remembered emphasis is still worth holding: the selection is between
29242
+ * its markers, both markers are still there, no paragraph break cut it, and
29243
+ * the parser has not made something else of its start. (Typing `**` inside
29244
+ * bold splits it, and then the tree's own reading should show.) */
29245
+ function holds(state, held) {
29246
+ const len = held.marker.length;
29247
+ const { from, to } = state.selection.main;
29248
+ if (from < held.from + len || to > held.to - len) return false;
29249
+ const text = state.sliceDoc(held.from, held.to);
29250
+ if (text.length < 2 * len || !text.startsWith(held.marker) || !text.endsWith(held.marker)) return false;
29251
+ const block = inlineBlockAt(state, held.from);
29252
+ if (!block || block.to < held.to) return false;
29253
+ const parsed = emphasisAround(state, held.from, 1);
29254
+ return !(parsed && parsed.name === held.name && parsed.from === held.from);
29255
+ }
29256
+ function mapHeld(held, through) {
29257
+ return {
29258
+ ...held,
29259
+ from: through.changes.mapPos(held.from, 1),
29260
+ to: through.changes.mapPos(held.to, -1)
29261
+ };
29262
+ }
29263
+ /** The emphasis the caret is in: the tree's own while it parses, and the held
29264
+ * one, mapped along through edits, once it does not. Null outside any. */
29265
+ var heldEmphasisField = StateField.define({
29266
+ create: (state) => liveEmphasis(state),
29267
+ update(prev, tr) {
29268
+ if (prev) {
29269
+ const held = mapHeld({
29270
+ ...prev,
29271
+ live: false
29272
+ }, tr);
29273
+ if (holds(tr.state, held)) return held;
29274
+ }
29275
+ const live = liveEmphasis(tr.state);
29276
+ if (live) return live;
29277
+ const seed = tr.effects.find((effect) => effect.is(holdEmphasis));
29278
+ return seed ? {
29279
+ ...seed.value,
29280
+ live: false
29281
+ } : null;
29282
+ }
29283
+ });
29284
+ /** The whitespace at the inside edges of `held`, or null when there is nothing
29285
+ * to move: no whitespace at the edges, nothing but whitespace inside, or the
29286
+ * markers are gone. The text ends where the opening block's text does: past
29287
+ * a paragraph break the rest of the run can never be inside the emphasis, so
29288
+ * it counts as trail, provided it is scaffolding and not text. */
29289
+ function repairFor(state, held) {
29290
+ const len = held.marker.length;
29291
+ const text = state.sliceDoc(held.from, held.to);
29292
+ if (text.length < 2 * len || !text.startsWith(held.marker) || !text.endsWith(held.marker)) return null;
29293
+ const inner = text.slice(len, text.length - len);
29294
+ const lead = /^\s*/.exec(inner)[0].length;
29295
+ const block = inlineBlockAt(state, held.from);
29296
+ const inBlock = state.sliceDoc(held.from + len, Math.min(held.to - len, block ? block.to : held.to - len));
29297
+ const contentEnd = held.from + len + inBlock.replace(/\s+$/, "").length;
29298
+ if (contentEnd <= held.from + len + lead) return null;
29299
+ const trail = held.to - len - contentEnd;
29300
+ if (!lead && !trail) return null;
29301
+ if (!TRAIL_RE.test(state.sliceDoc(contentEnd, held.to - len))) return null;
29302
+ return {
29303
+ from: held.from,
29304
+ to: held.to,
29305
+ len,
29306
+ lead,
29307
+ trail
29308
+ };
29309
+ }
29310
+ /** Moves the lead in front of the opening marker and the trail past the closing
29311
+ * one. Moving the whitespace rather than the markers keeps an annotation on
29312
+ * the text on the text. */
29313
+ function repairChanges(state, r) {
29314
+ const changes = [];
29315
+ if (r.lead) changes.push({
29316
+ from: r.from,
29317
+ insert: state.sliceDoc(r.from + r.len, r.from + r.len + r.lead)
29318
+ }, {
29319
+ from: r.from + r.len,
29320
+ to: r.from + r.len + r.lead
29321
+ });
29322
+ if (r.trail) changes.push({
29323
+ from: r.to - r.len - r.trail,
29324
+ to: r.to - r.len
29325
+ }, {
29326
+ from: r.to,
29327
+ insert: state.sliceDoc(r.to - r.len - r.trail, r.to - r.len)
29328
+ });
29329
+ return changes;
29330
+ }
29331
+ /** Where a position lands after the repair. The markers and the whitespace swap
29332
+ * places, and a caret in the whitespace goes with it past the marker: after
29333
+ * the space it typed, outside the run. With `stay` it stays with the text,
29334
+ * inside the markers. That is what a blur wants: a toolbar click blurs the
29335
+ * editor before its command runs, and the command must find the caret where
29336
+ * the typing was. */
29337
+ function mapRepair(pos, r, stay) {
29338
+ const contentStart = r.from + r.len + r.lead;
29339
+ const contentEnd = r.to - r.len - r.trail;
29340
+ if (r.lead) {
29341
+ if (pos > r.from && pos < r.from + r.len) return pos + r.lead;
29342
+ if (pos >= r.from + r.len && pos < contentStart) return stay ? contentStart : pos - r.len;
29343
+ }
29344
+ if (r.trail) {
29345
+ if (pos > contentEnd && pos <= r.to - r.len) return stay ? contentEnd : pos + r.len;
29346
+ if (pos > r.to - r.len && pos < r.to) return pos - r.trail;
29347
+ }
29348
+ return pos;
29349
+ }
29350
+ function repairSpec(state, held, stay) {
29351
+ const r = repairFor(state, held);
29352
+ if (!r) return null;
29353
+ const sel = state.selection;
29354
+ return {
29355
+ changes: repairChanges(state, r),
29356
+ selection: EditorSelection.create(sel.ranges.map((range) => EditorSelection.range(mapRepair(range.anchor, r, stay), mapRepair(range.head, r, stay))), sel.mainIndex),
29357
+ userEvent: "input.emphasis"
29358
+ };
29359
+ }
29360
+ /** The changes that move edge whitespace out of `held`, for a command that
29361
+ * steps out of the run itself. Empty when there is nothing to move. */
29362
+ function repairChangesFor(state, held) {
29363
+ const r = repairFor(state, held);
29364
+ return r ? repairChanges(state, r) : [];
29365
+ }
29366
+ /** Ends a hold at once, for a blur. The display must be right while the editor
29367
+ * is unfocused, and the value it commits must be markdown that renders the way
29368
+ * it looked. */
29369
+ function repairHeldEmphasis(view) {
29370
+ const held = view.state.field(heldEmphasisField, false);
29371
+ if (!held || held.live || view.state.readOnly) return;
29372
+ const spec = repairSpec(view.state, held, true);
29373
+ if (spec) view.dispatch(spec);
29374
+ }
29375
+ var heldEmphasis = [heldEmphasisField, ViewPlugin.fromClass(class {
29376
+ constructor(view) {
29377
+ this.view = view;
29378
+ this.pending = null;
29379
+ this.destroyed = false;
29380
+ }
29381
+ update(update) {
29382
+ if (this.pending) this.pending = mapHeld(this.pending, update);
29383
+ const before = update.startState.field(heldEmphasisField);
29384
+ if (!before) return;
29385
+ const gone = mapHeld(before, update);
29386
+ const after = update.state.field(heldEmphasisField);
29387
+ if (after && after.name === gone.name && after.from === gone.from && after.to === gone.to) return;
29388
+ this.pending = gone;
29389
+ queueMicrotask(() => this.flush());
29390
+ }
29391
+ flush() {
29392
+ const held = this.pending;
29393
+ this.pending = null;
29394
+ if (!held || this.destroyed || this.view.state.readOnly) return;
29395
+ const spec = repairSpec(this.view.state, held, false);
29396
+ if (spec) this.view.dispatch(spec);
29397
+ }
29398
+ destroy() {
29399
+ this.destroyed = true;
29400
+ }
29401
+ })];
28976
29402
  //#endregion
28977
29403
  //#region src/components/inputs/text-editor/text-editor.markdown.ts
28978
29404
  var NODE_CLASS = {
@@ -29105,6 +29531,12 @@ function buildMarkDecorations(view) {
29105
29531
  else if (MARK_NODES.has(node.name)) ranges.push(dimDeco.range(node.from, node.to));
29106
29532
  }
29107
29533
  });
29534
+ const held = view.state.field(heldEmphasisField, false);
29535
+ if (held && !held.live) {
29536
+ const len = held.marker.length;
29537
+ ranges.push(classDeco(NODE_CLASS[held.name]).range(held.from, held.to));
29538
+ ranges.push(dimDeco.range(held.from, held.from + len), dimDeco.range(held.to - len, held.to));
29539
+ }
29108
29540
  return Decoration.set(ranges, true);
29109
29541
  }
29110
29542
  var markDecorationPlugin = ViewPlugin.fromClass(class {
@@ -29123,20 +29555,22 @@ function hangingLineDeco(length) {
29123
29555
  }
29124
29556
  /** Whether `pos` sits inside a fenced or indented code block — where a leading
29125
29557
  * `-`/`1.`/`>` is literal code, not a list or quote marker. */
29558
+ var CODE_BLOCK_NODES = /* @__PURE__ */ new Set(["FencedCode", "CodeBlock"]);
29126
29559
  function inCodeBlock(state, pos) {
29127
- for (let n = syntaxTree(state).resolveInner(pos, 1); n; n = n.parent) if (n.name === "FencedCode" || n.name === "CodeBlock") return true;
29128
- return false;
29560
+ return enclosingNamed(state, pos, 1, CODE_BLOCK_NODES) !== null;
29129
29561
  }
29130
29562
  function buildHangingIndent(state) {
29131
29563
  const ranges = [];
29132
- for (let i = 1; i <= state.doc.lines; i++) {
29133
- const line = state.doc.line(i);
29134
- const match = line.text.match(HANGING_RE);
29135
- if (match && match[1].length && !inCodeBlock(state, line.from)) {
29564
+ let from = 0;
29565
+ for (const iter = state.doc.iterLines(); !iter.next().done;) {
29566
+ const text = iter.value;
29567
+ const match = HANGING_RE.exec(text);
29568
+ if (match && match[1].length && !inCodeBlock(state, from)) {
29136
29569
  const length = match[1].length;
29137
- ranges.push(hangingLineDeco(length).range(line.from));
29138
- ranges.push(prefixMonoDeco.range(line.from, line.from + length));
29570
+ ranges.push(hangingLineDeco(length).range(from));
29571
+ ranges.push(prefixMonoDeco.range(from, from + length));
29139
29572
  }
29573
+ from += text.length + 1;
29140
29574
  }
29141
29575
  return Decoration.set(ranges, true);
29142
29576
  }
@@ -29191,9 +29625,28 @@ function buildMentionChips(state) {
29191
29625
  } });
29192
29626
  return Decoration.set(ranges, true);
29193
29627
  }
29628
+ /** Whether moving the selection from `before` to `after` changes which mention a
29629
+ * selection covers. Only then is the chip set worth rebuilding: walking the
29630
+ * whole syntax tree on every arrow key, to find that nothing looks different,
29631
+ * is the expensive way to do nothing. */
29632
+ function coverageChanged(chips, before, after) {
29633
+ let changed = false;
29634
+ chips.between(0, 1e9, (from, to) => {
29635
+ const covers = (sel) => sel.from !== sel.to && sel.from <= from && sel.to >= to;
29636
+ if (covers(before) !== covers(after)) {
29637
+ changed = true;
29638
+ return false;
29639
+ }
29640
+ });
29641
+ return changed;
29642
+ }
29194
29643
  var mentionChipField = StateField.define({
29195
29644
  create: (state) => buildMentionChips(state),
29196
- update: (value, tr) => tr.docChanged || !tr.startState.selection.eq(tr.state.selection) ? buildMentionChips(tr.state) : value,
29645
+ update: (value, tr) => {
29646
+ if (tr.docChanged) return buildMentionChips(tr.state);
29647
+ if (tr.startState.selection.eq(tr.state.selection)) return value;
29648
+ return coverageChanged(value, tr.startState.selection.main, tr.state.selection.main) ? buildMentionChips(tr.state) : value;
29649
+ },
29197
29650
  provide: (field) => EditorView.decorations.from(field)
29198
29651
  });
29199
29652
  var mentionAtomicRanges = EditorView.atomicRanges.of((view) => view.state.field(mentionChipField, false) ?? Decoration.none);
@@ -29201,21 +29654,21 @@ var mentionAtomicRanges = EditorView.atomicRanges.of((view) => view.state.field(
29201
29654
  var markdownEditing = [
29202
29655
  markdown({ extensions: GFM }),
29203
29656
  markDecorationPlugin,
29657
+ heldEmphasis,
29204
29658
  hangingIndentField,
29205
29659
  Prec.highest(mentionChipField),
29206
29660
  mentionAtomicRanges
29207
29661
  ];
29208
29662
  function mentionLinkAt(state, pos, side) {
29209
- for (let node = syntaxTree(state).resolveInner(pos, side); node; node = node.parent) {
29210
- if (node.name !== "Link") continue;
29663
+ return enclosingNode(state, pos, side, (node) => {
29664
+ if (node.name !== "Link") return false;
29211
29665
  let url = null;
29212
29666
  for (let child = node.firstChild; child; child = child.nextSibling) if (child.name === "URL") {
29213
29667
  url = child;
29214
29668
  break;
29215
29669
  }
29216
- if (url && state.sliceDoc(url.from, url.to).startsWith("user:")) return node;
29217
- }
29218
- return null;
29670
+ return url !== null && state.sliceDoc(url.from, url.to).startsWith("user:");
29671
+ });
29219
29672
  }
29220
29673
  /** The mention whose collapsed token contains `pos` (used to select it on click). */
29221
29674
  function mentionRangeAt(state, pos) {
@@ -29286,54 +29739,40 @@ function desiredSentinels(groups, cleanText, canPlace = canPlaceSentinel) {
29286
29739
  }
29287
29740
  return out;
29288
29741
  }
29289
- /** Build the desired doc text: `cleanText` with a sentinel inserted at each desired
29290
- * position (multiplicity honoured for touching groups). */
29291
- function buildDesiredDoc(cleanText, desired) {
29742
+ /** How many sentinels each clean position wants (touching groups want two). */
29743
+ function desiredCounts(desired) {
29292
29744
  const counts = /* @__PURE__ */ new Map();
29293
29745
  for (const d of desired) counts.set(d.pos, (counts.get(d.pos) ?? 0) + 1);
29294
- let out = "";
29295
- for (let i = 0; i <= cleanText.length; i++) {
29296
- const n = counts.get(i) ?? 0;
29297
- if (n) out += "⁠".repeat(n);
29298
- if (i < cleanText.length) out += cleanText[i];
29299
- }
29300
- return out;
29746
+ return counts;
29301
29747
  }
29302
29748
  /** Minimal changes (in current-doc coords) to turn `doc` into a document that holds
29303
29749
  * exactly the sentinels the groups want — touching only sentinel characters, never
29304
- * the real text. `doc` and the target share the same clean text, so a two-pointer
29305
- * walk yields sentinel-only inserts/deletes. */
29750
+ * the real text. One walk over `doc`, comparing the sentinel run at each clean
29751
+ * position with the number wanted there. Building the target document first would
29752
+ * cost a second copy of the text on every edit. */
29306
29753
  function reconcileSentinels(doc, groups, canPlace = canPlaceSentinel) {
29307
- const cleanText = stripSentinels(doc);
29308
- const desired = buildDesiredDoc(cleanText, desiredSentinels(groups, cleanText, canPlace));
29754
+ const want = desiredCounts(desiredSentinels(groups, groups.length ? stripSentinels(doc) : "", canPlace));
29309
29755
  const changes = [];
29310
- let i = 0;
29311
- let j = 0;
29312
- while (i < doc.length || j < desired.length) {
29313
- const dc = i < doc.length ? doc[i] : "";
29314
- const ec = j < desired.length ? desired[j] : "";
29315
- if (dc === ec) {
29316
- i++;
29317
- j++;
29318
- } else if (dc === "⁠") {
29319
- changes.push({
29320
- from: i,
29321
- to: i + 1
29322
- });
29323
- i++;
29324
- } else if (ec === "⁠") {
29325
- changes.push({
29326
- from: i,
29327
- to: i,
29328
- insert: "⁠"
29329
- });
29330
- j++;
29331
- } else {
29332
- i++;
29333
- j++;
29334
- }
29756
+ let pos = 0;
29757
+ let clean = 0;
29758
+ for (;;) {
29759
+ let have = 0;
29760
+ while (pos + have < doc.length && doc[pos + have] === "⁠") have++;
29761
+ const need = want.get(clean) ?? 0;
29762
+ if (have > need) changes.push({
29763
+ from: pos + need,
29764
+ to: pos + have
29765
+ });
29766
+ else if (need > have) changes.push({
29767
+ from: pos + have,
29768
+ to: pos + have,
29769
+ insert: "⁠".repeat(need - have)
29770
+ });
29771
+ pos += have;
29772
+ if (pos >= doc.length) return changes;
29773
+ pos++;
29774
+ clean++;
29335
29775
  }
29336
- return changes;
29337
29776
  }
29338
29777
  var LINE_START_MARKER = /^(\s*)(#{1,6}\s|[-*+]\s|\d+[.)]\s|>|`{3,}|~{3,})/;
29339
29778
  /** Default placement guard. Blocks the sentinel where U+2060 would break the
@@ -29341,13 +29780,13 @@ var LINE_START_MARKER = /^(\s*)(#{1,6}\s|[-*+]\s|\d+[.)]\s|>|`{3,}|~{3,})/;
29341
29780
  * strikethrough run (`~~...~~`). Blocked edges fall back to a single caret stop. */
29342
29781
  function canPlaceSentinel(cleanText, pos, _kind) {
29343
29782
  const lineStart = cleanText.lastIndexOf("\n", pos - 1) + 1;
29344
- const marker = cleanText.slice(lineStart).match(LINE_START_MARKER);
29783
+ const lineEnd = cleanText.indexOf("\n", pos) === -1 ? cleanText.length : cleanText.indexOf("\n", pos);
29784
+ const lineText = cleanText.slice(lineStart, lineEnd);
29785
+ const marker = lineText.match(LINE_START_MARKER);
29345
29786
  if (marker) {
29346
29787
  if (pos < lineStart + marker[0].length) return false;
29347
29788
  }
29348
- const lineText = cleanText.slice(lineStart, cleanText.indexOf("\n", pos) === -1 ? void 0 : cleanText.indexOf("\n", pos));
29349
- const rel = pos - lineStart;
29350
- if ((lineText.slice(0, rel).match(/~~/g) ?? []).length % 2 === 1) return false;
29789
+ if ((lineText.slice(0, pos - lineStart).match(/~~/g) ?? []).length % 2 === 1) return false;
29351
29790
  return true;
29352
29791
  }
29353
29792
  //#endregion
@@ -29444,7 +29883,10 @@ var AnnotationBadge = class extends WidgetType {
29444
29883
  top: badge.top,
29445
29884
  bottom: badge.bottom
29446
29885
  };
29447
- const x = pos <= 0 ? badge.left - parseFloat(cs.marginLeft) : badge.right + parseFloat(cs.marginRight);
29886
+ const tint = dom.closest(".cm-annotation");
29887
+ const rects = tint ? tint.getClientRects() : null;
29888
+ const lastLine = rects && rects.length ? rects[rects.length - 1] : null;
29889
+ const x = pos <= 0 ? badge.left - parseFloat(cs.marginLeft) : lastLine ? lastLine.right : badge.right + parseFloat(cs.marginRight);
29448
29890
  return {
29449
29891
  left: x,
29450
29892
  right: x,
@@ -29508,7 +29950,12 @@ function resolveGroup(group, doc, sents) {
29508
29950
  endSent: textTo < doc.length && doc[textTo] === "⁠" ? textTo : null
29509
29951
  };
29510
29952
  }
29953
+ var NOTHING = {
29954
+ deco: Decoration.none,
29955
+ atomic: Decoration.none
29956
+ };
29511
29957
  function buildAll(state, label) {
29958
+ if (!state.field(annotationField).length) return NOTHING;
29512
29959
  const doc = state.doc.toString();
29513
29960
  const sel = state.selection.main;
29514
29961
  const sents = sentinelPositions(doc);
@@ -29560,6 +30007,7 @@ function makeAnnotationRender(label) {
29560
30007
  return StateField.define({
29561
30008
  create: (state) => buildAll(state, label),
29562
30009
  update: (value, tr) => {
30010
+ if (value === NOTHING && !tr.state.field(annotationField).length) return value;
29563
30011
  if (tr.docChanged || !tr.startState.selection.eq(tr.state.selection) || tr.effects.some((e) => e.is(setAnnotations))) return buildAll(tr.state, label);
29564
30012
  return value;
29565
30013
  },
@@ -29572,6 +30020,7 @@ function makeAnnotationRender(label) {
29572
30020
  var annotationSentinelFilter = EditorState.transactionFilter.of((tr) => {
29573
30021
  const set = tr.effects.find((e) => e.is(setAnnotations));
29574
30022
  if (!tr.docChanged && !set) return tr;
30023
+ if (!(set || tr.effects.some((e) => e.is(pasteAnnotations) || e.is(annotationMove))) && !(tr.startState.field(annotationField, false) ?? []).length) return tr;
29575
30024
  const postUserDoc = tr.newDoc.toString();
29576
30025
  const cleanLen = stripSentinels(postUserDoc).length;
29577
30026
  let anns;
@@ -29698,8 +30147,26 @@ function renumberOrderedLists(doc) {
29698
30147
  }
29699
30148
  /** Renumbers ordered lists in the same transaction as the edit that disturbed them,
29700
30149
  * so it's one atomic (single-undo) step and the caret/selection maps through it. */
30150
+ /** Whether this change can have disturbed any numbering. It can when it adds or
30151
+ * removes a line break, or when a line it touches is (or was) an ordered item.
30152
+ * Typing inside a paragraph cannot, and that is the common case: without this
30153
+ * test every keystroke read the whole document out and walked every line. */
30154
+ function mayRenumber(tr) {
30155
+ let may = false;
30156
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
30157
+ if (may) return;
30158
+ const before = tr.startState.doc;
30159
+ if (inserted.lines > 1 || before.lineAt(fromA).number !== before.lineAt(toA).number) {
30160
+ may = true;
30161
+ return;
30162
+ }
30163
+ const ordered = (text) => ORDERED_RE.test(text.slice(BQ_PREFIX_RE.exec(text)[0].length));
30164
+ if (ordered(before.lineAt(fromA).text) || ordered(tr.newDoc.lineAt(fromB).text) || ordered(tr.newDoc.lineAt(toB).text)) may = true;
30165
+ });
30166
+ return may;
30167
+ }
29701
30168
  var orderedListRenumber = EditorState.transactionFilter.of((tr) => {
29702
- if (!tr.docChanged) return tr;
30169
+ if (!tr.docChanged || !mayRenumber(tr)) return tr;
29703
30170
  const changes = renumberOrderedLists(tr.newDoc.toString());
29704
30171
  if (changes.length === 0) return tr;
29705
30172
  return [tr, {
@@ -29921,6 +30388,205 @@ var dragToMove = [
29921
30388
  dragMovePlugin
29922
30389
  ];
29923
30390
  //#endregion
30391
+ //#region src/components/inputs/text-editor/text-editor.cursor.ts
30392
+ /** Below this the two edges of one position are the same place drawn twice, and
30393
+ * only glyph overhang and pixel rounding tell them apart. A widget is a full
30394
+ * icon wide and a line wrap is a line away, so neither comes near it. */
30395
+ var GLYPH_NOISE_PX = 2;
30396
+ /** The side the main caret is drawn on at `head`, when that is not for the
30397
+ * selection's own assoc to say. Set by a step-over (its assoc does not survive
30398
+ * the transaction: a later filter rebuilds the selection without it), by a
30399
+ * click (CodeMirror reads the side of a click from the line, not from the
30400
+ * pointer, and would land left of a badge you clicked right of), and by an
30401
+ * edit: typing and a backward delete leave the caret at the end of what was
30402
+ * just edited, so on the side that text is, the left; a forward delete leaves
30403
+ * it where the removed text began, the right. */
30404
+ var sideHint = /* @__PURE__ */ new WeakMap();
30405
+ /** The side a step-over asks for, carried as an annotation. */
30406
+ var caretSide = Annotation.define();
30407
+ /** Where the pointer last went down or dragged, to read the side of a click. */
30408
+ var lastPointer = /* @__PURE__ */ new WeakMap();
30409
+ function edges(view, head) {
30410
+ const before = view.coordsAtPos(head, -1);
30411
+ const after = view.coordsAtPos(head, 1);
30412
+ return before && after ? {
30413
+ before,
30414
+ after
30415
+ } : null;
30416
+ }
30417
+ /** A line wrap puts the two sides a whole line apart. A few pixels is not that:
30418
+ * a syntax marker is set in monospace, whose glyph box sits a little higher or
30419
+ * lower than the body text's, and Safari rounds each edge to a whole pixel. */
30420
+ var WRAP_PX = 8;
30421
+ function onOneLine(e) {
30422
+ return Math.abs(e.before.top - e.after.top) < WRAP_PX;
30423
+ }
30424
+ /** The side the caret is drawn on at `head`: 1 unless the two sides are
30425
+ * genuinely apart and a hint, or else the arrival direction, says "before". */
30426
+ function drawnAssoc(view, head, assoc) {
30427
+ const e = edges(view, head);
30428
+ if (e && onOneLine(e) && Math.abs(e.before.left - e.after.left) <= GLYPH_NOISE_PX) return 1;
30429
+ const hint = sideHint.get(view);
30430
+ if (hint && hint.head === head) {
30431
+ if (hint.side !== "pointer") return hint.side;
30432
+ const p = lastPointer.get(view);
30433
+ if (p && e) return Math.abs(p.x - e.before.left) <= Math.abs(p.x - e.after.left) ? -1 : 1;
30434
+ }
30435
+ if (assoc) return assoc < 0 ? -1 : 1;
30436
+ return 1;
30437
+ }
30438
+ var pointerTracker = EditorView.domEventHandlers({
30439
+ mousedown(event, view) {
30440
+ lastPointer.set(view, {
30441
+ x: event.clientX,
30442
+ y: event.clientY
30443
+ });
30444
+ return false;
30445
+ },
30446
+ mousemove(event, view) {
30447
+ if (event.buttons) lastPointer.set(view, {
30448
+ x: event.clientX,
30449
+ y: event.clientY
30450
+ });
30451
+ return false;
30452
+ },
30453
+ touchstart(event, view) {
30454
+ const touch = event.touches[0];
30455
+ if (touch) lastPointer.set(view, {
30456
+ x: touch.clientX,
30457
+ y: touch.clientY
30458
+ });
30459
+ return false;
30460
+ }
30461
+ });
30462
+ /** Puts the caret on `side` of the link badge it stands at, without moving it in
30463
+ * the document. Nothing to do (so the key falls through to its usual command)
30464
+ * when there is no badge here, or the caret is on that side already. Only a
30465
+ * link badge qualifies: a mention token or an annotation badge keeps its own
30466
+ * Backspace and arrow behavior. The badge's two sides are not asked to share a
30467
+ * line: the marker before it may measure a rounded pixel off in Safari, and a
30468
+ * wrap can fall between the two. */
30469
+ function stepOver(side) {
30470
+ return (view) => {
30471
+ const { main } = view.state.selection;
30472
+ if (!main.empty || !linkEndsAt(view.state, main.head) || !edges(view, main.head)) return false;
30473
+ if (drawnAssoc(view, main.head, main.assoc) === side) return false;
30474
+ view.dispatch({
30475
+ selection: EditorSelection.cursor(main.head, side),
30476
+ annotations: caretSide.of(side),
30477
+ userEvent: "select"
30478
+ });
30479
+ return true;
30480
+ };
30481
+ }
30482
+ var stepOverKeymap = Prec.high(keymap.of([
30483
+ {
30484
+ key: "ArrowLeft",
30485
+ run: stepOver(-1)
30486
+ },
30487
+ {
30488
+ key: "ArrowRight",
30489
+ run: stepOver(1)
30490
+ },
30491
+ {
30492
+ key: "Backspace",
30493
+ run: stepOver(-1)
30494
+ },
30495
+ {
30496
+ key: "Delete",
30497
+ run: stepOver(1)
30498
+ }
30499
+ ]));
30500
+ /** Characters that end a bare URL by themselves (GFM: whitespace, `<`, and the
30501
+ * punctuation it leaves off the end), so text starting with one lands after
30502
+ * the badge on its own. Anything else would join the URL. */
30503
+ var ENDS_URL = /^[\s<?!.,:*_~)]/;
30504
+ /** Text typed right of the badge of a bare URL lands right of the badge. There
30505
+ * is no boundary in markdown to put it behind: `www.apple.comx` is one URL. A
30506
+ * space is that boundary, so the editor writes it before the text. */
30507
+ var typePastBadge = EditorView.inputHandler.of((view, from, to, text) => {
30508
+ if (from !== to || !text || view.composing || ENDS_URL.test(text)) return false;
30509
+ const { main } = view.state.selection;
30510
+ if (!main.empty || main.head !== from || !bareUrlEndsAt(view.state, from) || !edges(view, from)) return false;
30511
+ if (drawnAssoc(view, from, main.assoc) !== 1) return false;
30512
+ view.dispatch({
30513
+ changes: {
30514
+ from,
30515
+ insert: ` ${text}`
30516
+ },
30517
+ selection: { anchor: from + 1 + text.length },
30518
+ userEvent: "input.type",
30519
+ scrollIntoView: true
30520
+ });
30521
+ return true;
30522
+ });
30523
+ var stableCursor = [
30524
+ layer({
30525
+ above: true,
30526
+ class: "cm-nldd-cursorLayer",
30527
+ markers(view) {
30528
+ const { state } = view;
30529
+ const conf = getDrawSelectionConfig(state);
30530
+ const out = [];
30531
+ for (const r of state.selection.ranges) {
30532
+ if (!r.empty && !conf.drawRangeCursor) continue;
30533
+ const className = r === state.selection.main ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
30534
+ const cursor = EditorSelection.cursor(r.head, drawnAssoc(view, r.head, r.assoc));
30535
+ out.push(...RectangleMarker.forRange(view, className, cursor));
30536
+ }
30537
+ return out;
30538
+ },
30539
+ update(update, dom) {
30540
+ if (update.docChanged || update.selectionSet) {
30541
+ const { view } = update;
30542
+ const head = update.state.selection.main.head;
30543
+ const kept = sideHint.get(view);
30544
+ let hint = kept ? {
30545
+ ...kept,
30546
+ head: update.changes.mapPos(kept.head)
30547
+ } : null;
30548
+ const oldHead = update.startState.selection.main.head;
30549
+ for (const tr of update.transactions) {
30550
+ const asked = tr.annotation(caretSide);
30551
+ if (asked) hint = {
30552
+ head,
30553
+ side: asked
30554
+ };
30555
+ else if (tr.isUserEvent("select.pointer")) hint = {
30556
+ head,
30557
+ side: "pointer"
30558
+ };
30559
+ else if (tr.isUserEvent("input") || tr.isUserEvent("delete.backward")) hint = {
30560
+ head,
30561
+ side: linkEndsAt(update.state, head) && !linkEndsAt(update.startState, oldHead) ? 1 : -1
30562
+ };
30563
+ else if (tr.isUserEvent("delete.forward")) hint = {
30564
+ head,
30565
+ side: 1
30566
+ };
30567
+ else if (tr.selection) hint = null;
30568
+ }
30569
+ if (hint) sideHint.set(view, hint);
30570
+ else sideHint.delete(view);
30571
+ }
30572
+ if (update.transactions.some((tr) => tr.selection)) dom.style.animationName = dom.style.animationName === "cm-blink" ? "cm-blink2" : "cm-blink";
30573
+ return update.docChanged || update.selectionSet;
30574
+ },
30575
+ mount(dom, view) {
30576
+ dom.style.animationDuration = `${getDrawSelectionConfig(view.state).cursorBlinkRate}ms`;
30577
+ }
30578
+ }),
30579
+ pointerTracker,
30580
+ stepOverKeymap,
30581
+ typePastBadge,
30582
+ EditorView.theme({
30583
+ ".cm-cursorLayer": { display: "none" },
30584
+ ".cm-nldd-cursorLayer": { pointerEvents: "none" },
30585
+ "&.cm-focused > .cm-scroller > .cm-nldd-cursorLayer": { animation: "steps(1) cm-blink 1.2s infinite" },
30586
+ "&.cm-focused > .cm-scroller > .cm-nldd-cursorLayer .cm-cursor": { display: "block" }
30587
+ })
30588
+ ];
30589
+ //#endregion
29924
30590
  //#region src/components/inputs/text-editor/text-editor.commands.ts
29925
30591
  var EMPTY_FORMATS = {
29926
30592
  bold: false,
@@ -29931,12 +30597,12 @@ var EMPTY_FORMATS = {
29931
30597
  link: false,
29932
30598
  bulletList: false,
29933
30599
  orderedList: false,
30600
+ taskList: false,
29934
30601
  quote: false,
29935
30602
  heading: 0
29936
30603
  };
29937
30604
  function enclosing(view, pos, nodeName, side = 1) {
29938
- for (let n = syntaxTree(view.state).resolveInner(pos, side); n; n = n.parent) if (n.name === nodeName) return n;
29939
- return null;
30605
+ return enclosingNamed(view.state, pos, side, nodeName);
29940
30606
  }
29941
30607
  /** An inline format is "active" for the caret only when the node encloses it on
29942
30608
  * *both* sides. At the outer edge (just before the opening marker or just after
@@ -29947,31 +30613,67 @@ function enclosingInline(view, pos, nodeName) {
29947
30613
  const right = enclosing(view, pos, nodeName, 1);
29948
30614
  return left && right && left.from === right.from && left.to === right.to ? right : null;
29949
30615
  }
29950
- /** Wrap (or, if already wrapped, unwrap) each selection range with `marker`. */
30616
+ /** Wrap each selection range with `marker`, or unwrap it when it is already
30617
+ * wrapped. A caret at the very end of the wrapped text steps out of the run
30618
+ * instead: bold, type, bold ends with the caret after the bold, ready for the
30619
+ * next word, not with the bold undone. */
29951
30620
  function toggleInlineWrap(view, marker, nodeName) {
29952
30621
  if (view.state.readOnly) return;
30622
+ const { state } = view;
29953
30623
  const len = marker.length;
29954
- view.dispatch(view.state.changeByRange((range) => {
29955
- const wrap = enclosing(view, range.from, nodeName);
29956
- if (wrap) return {
29957
- changes: [{
30624
+ const held = state.field(heldEmphasisField, false);
30625
+ view.dispatch(state.changeByRange((range) => {
30626
+ const text = state.sliceDoc(range.from, range.to);
30627
+ const lead = /^\s*/.exec(text)[0].length;
30628
+ if (!range.empty && lead === text.length) return { range };
30629
+ const from = range.from + lead;
30630
+ const to = range.to - /\s*$/.exec(text)[0].length;
30631
+ const wrap = enclosing(view, from, nodeName) ?? (held && held.name === nodeName && from >= held.from && to <= held.to ? held : null);
30632
+ if (wrap) {
30633
+ const content = state.sliceDoc(wrap.from + len, wrap.to - len);
30634
+ if (range.empty && from === wrap.to - len && content.trim()) {
30635
+ const run = {
30636
+ from: wrap.from,
30637
+ to: wrap.to,
30638
+ name: nodeName,
30639
+ marker: state.sliceDoc(wrap.from, wrap.from + len),
30640
+ live: false
30641
+ };
30642
+ const changes = EMPHASIS_MARKERS.has(nodeName) ? repairChangesFor(state, run) : [];
30643
+ let pos = wrap.to;
30644
+ while (/[ \t]/.test(state.sliceDoc(pos, pos + 1))) pos++;
30645
+ return {
30646
+ changes,
30647
+ range: EditorSelection.cursor(pos)
30648
+ };
30649
+ }
30650
+ const changes = state.changes([{
29958
30651
  from: wrap.from,
29959
30652
  to: wrap.from + len
29960
30653
  }, {
29961
30654
  from: wrap.to - len,
29962
30655
  to: wrap.to
29963
- }],
29964
- range: EditorSelection.range(Math.max(wrap.from, range.from - len), Math.max(wrap.from, range.to - len))
29965
- };
30656
+ }]);
30657
+ return {
30658
+ changes,
30659
+ range: EditorSelection.range(changes.mapPos(range.anchor), changes.mapPos(range.head))
30660
+ };
30661
+ }
29966
30662
  return {
29967
30663
  changes: [{
29968
- from: range.from,
30664
+ from,
29969
30665
  insert: marker
29970
30666
  }, {
29971
- from: range.to,
30667
+ from: to,
29972
30668
  insert: marker
29973
30669
  }],
29974
- range: EditorSelection.range(range.from + len, range.to + len)
30670
+ range: EditorSelection.range(from + len, to + len),
30671
+ effects: range.empty && range === state.selection.main && EMPHASIS_MARKERS.has(nodeName) ? holdEmphasis.of({
30672
+ from,
30673
+ to: from + 2 * len,
30674
+ name: nodeName,
30675
+ marker
30676
+ }) : void 0
29975
30677
  };
29976
30678
  }));
29977
30679
  view.focus();
@@ -29993,20 +30695,51 @@ function minimalLineChange(line, next) {
29993
30695
  insert: next.slice(p, next.length - s)
29994
30696
  };
29995
30697
  }
29996
- function mapSelectedLines(view, transform) {
29997
- if (view.state.readOnly) return;
30698
+ /** Dispatch line-level changes with the selection carried along. A caret that
30699
+ * sits exactly where a marker is inserted (column 0 of an empty line, say) is
30700
+ * mapped to *after* the insertion, so what is typed next lands in the item and
30701
+ * not in front of its marker. Plain mapping would leave it before. */
30702
+ function dispatchLineChanges(view, changes) {
30703
+ if (changes.length) {
30704
+ const { state } = view;
30705
+ const set = state.changes(changes);
30706
+ const sel = state.selection.main;
30707
+ view.dispatch({
30708
+ changes: set,
30709
+ selection: {
30710
+ anchor: set.mapPos(sel.anchor, 1),
30711
+ head: set.mapPos(sel.head, 1)
30712
+ }
30713
+ });
30714
+ }
30715
+ view.focus();
30716
+ }
30717
+ /** The selected line range, and whether it is a single blank line. The line
30718
+ * commands skip blank lines because they separate items, but when the blank
30719
+ * line is the only one selected (an empty document, a fresh paragraph) it is
30720
+ * the whole target and must take the marker. */
30721
+ function selectedLines(view) {
29998
30722
  const { state } = view;
29999
30723
  const { from, to } = state.selection.main;
30000
30724
  const first = state.doc.lineAt(from).number;
30001
30725
  const last = state.doc.lineAt(to).number;
30726
+ return {
30727
+ first,
30728
+ last,
30729
+ loneBlank: first === last && state.doc.line(first).text.trim() === ""
30730
+ };
30731
+ }
30732
+ function mapSelectedLines(view, transform) {
30733
+ if (view.state.readOnly) return;
30734
+ const { state } = view;
30735
+ const { first, last } = selectedLines(view);
30002
30736
  const changes = [];
30003
30737
  for (let i = first; i <= last; i++) {
30004
30738
  const line = state.doc.line(i);
30005
30739
  const change = minimalLineChange(line, transform(line.text));
30006
30740
  if (change) changes.push(change);
30007
30741
  }
30008
- if (changes.length) view.dispatch({ changes });
30009
- view.focus();
30742
+ dispatchLineChanges(view, changes);
30010
30743
  }
30011
30744
  function everySelectedLine(view, predicate) {
30012
30745
  const { state } = view;
@@ -30036,39 +30769,56 @@ function setHeading(view, level) {
30036
30769
  }
30037
30770
  function toggleBulletList(view) {
30038
30771
  const re = /^(\s*)[-*+]\s+/;
30039
- const allBulleted = everySelectedLine(view, (t) => re.test(t) || t.trim() === "");
30772
+ const { loneBlank } = selectedLines(view);
30773
+ const allBulleted = !loneBlank && everySelectedLine(view, (t) => re.test(t) || t.trim() === "");
30040
30774
  mapSelectedLines(view, (t) => {
30041
- if (t.trim() === "") return t;
30775
+ if (t.trim() === "" && !loneBlank) return t;
30042
30776
  if (allBulleted) return t.replace(re, "$1");
30043
30777
  return re.test(t) ? t : t.replace(/^(\s*)/, "$1- ");
30044
30778
  });
30045
30779
  }
30046
- var LIST_STRIP_RE = /^(\s*)(?:[-*+]|\d+[.)])\s+/;
30780
+ var TASK_RE = /^(\s*)[-*+]\s+\[[ xX]\]\s+/;
30781
+ /** Turn the selected lines into GFM task items (`- [ ] `), or back into plain
30782
+ * bullets when they all are tasks already. A bullet or numbered item keeps its
30783
+ * indent and becomes a task; toggling off leaves a bullet rather than bare text,
30784
+ * so the list survives and only the box goes. Checking a box is not this
30785
+ * command's job. */
30786
+ function toggleTaskList(view) {
30787
+ const { loneBlank } = selectedLines(view);
30788
+ const allTasks = !loneBlank && everySelectedLine(view, (t) => TASK_RE.test(t) || t.trim() === "");
30789
+ mapSelectedLines(view, (t) => {
30790
+ if (t.trim() === "" && !loneBlank) return t;
30791
+ if (allTasks) return t.replace(TASK_RE, "$1- ");
30792
+ if (TASK_RE.test(t)) return t;
30793
+ return t.replace(LIST_STRIP_RE, "$1").replace(/^(\s*)/, "$1- [ ] ");
30794
+ });
30795
+ }
30796
+ var LIST_STRIP_RE = /^(\s*)(?:[-*+]|\d+[.)])(?:\s+|$)/;
30797
+ var TASK_BOX_RE = /^(\s*)\[[ xX]\]\s+/;
30047
30798
  /** Set the selected lines to a list of `type`, replacing any existing list
30048
30799
  * marker; `'none'` strips it. Ordered items are numbered within the selection.
30049
30800
  * Unlike the toggles, this cleanly switches between list types (for a picker). */
30050
30801
  function setList(view, type) {
30051
30802
  if (view.state.readOnly) return;
30052
30803
  const { state } = view;
30053
- const { from, to } = state.selection.main;
30054
- const first = state.doc.lineAt(from).number;
30055
- const last = state.doc.lineAt(to).number;
30804
+ const { first, last, loneBlank } = selectedLines(view);
30056
30805
  const changes = [];
30057
30806
  let number = 0;
30058
30807
  for (let i = first; i <= last; i++) {
30059
30808
  const line = state.doc.line(i);
30060
- const stripped = line.text.replace(LIST_STRIP_RE, type === "none" ? "" : "$1");
30809
+ const withoutMarker = line.text.replace(LIST_STRIP_RE, type === "none" ? "" : "$1");
30810
+ const wasItem = withoutMarker !== line.text;
30811
+ const stripped = wasItem ? withoutMarker.replace(TASK_BOX_RE, type === "none" ? "" : "$1") : withoutMarker;
30061
30812
  let next = stripped;
30062
- if (type !== "none" && stripped.trim() !== "") {
30813
+ if (type !== "none" && (stripped.trim() !== "" || wasItem || loneBlank)) {
30063
30814
  number += 1;
30064
- const marker = type === "bullet" ? "- " : `${number}. `;
30815
+ const marker = type === "bullet" ? "- " : type === "task" ? "- [ ] " : `${number}. `;
30065
30816
  next = stripped.replace(/^(\s*)/, `$1${marker}`);
30066
30817
  }
30067
30818
  const change = minimalLineChange(line, next);
30068
30819
  if (change) changes.push(change);
30069
30820
  }
30070
- if (changes.length) view.dispatch({ changes });
30071
- view.focus();
30821
+ dispatchLineChanges(view, changes);
30072
30822
  }
30073
30823
  /** Nest the selected list item(s) under the nearest preceding item at the same
30074
30824
  * level, indenting only as far as that parent's content column. That keeps the
@@ -30270,7 +31020,8 @@ function toggleCodeBlock(view) {
30270
31020
  function readActiveFormats(view) {
30271
31021
  const { state } = view;
30272
31022
  const pos = state.selection.main.head;
30273
- const has = (name) => enclosingInline(view, pos, name) !== null;
31023
+ const held = state.field(heldEmphasisField, false);
31024
+ const has = (name) => enclosingInline(view, pos, name) !== null || held?.name === name;
30274
31025
  const lineText = state.doc.lineAt(pos).text;
30275
31026
  const lineStart = state.doc.lineAt(pos).from;
30276
31027
  const headingMatch = lineText.match(/^(#{1,6})\s/);
@@ -30284,6 +31035,7 @@ function readActiveFormats(view) {
30284
31035
  link: realLinkAt(view, pos) !== null,
30285
31036
  bulletList: !codeBlock && /^\s*[-*+]\s/.test(lineText),
30286
31037
  orderedList: !codeBlock && /^\s*\d+[.)]\s/.test(lineText),
31038
+ taskList: !codeBlock && TASK_RE.test(lineText),
30287
31039
  quote: enclosing(view, lineStart, "Blockquote") !== null,
30288
31040
  heading: codeBlock || !headingMatch ? 0 : headingMatch[1].length
30289
31041
  };
@@ -30296,13 +31048,16 @@ var textEditorStyles = css`
30296
31048
  /* # Host */
30297
31049
 
30298
31050
  :host {
30299
- --__caret-width: var(--primitives-border-width-regular);
31051
+ --_caret-width: var(--primitives-border-width-regular);
31052
+ --_caret-ring-width: var(--primitives-border-width-thin);
30300
31053
  --_corner-radius: 0;
30301
31054
  --_background-color: transparent;
30302
31055
  --_highlight-border-color: transparent;
30303
31056
  --_highlight-border-shadow: none;
30304
31057
  --_padding-block: 0px;
30305
- --_padding-inline: 0px;
31058
+ /* Room for the caret's own ink at the first and last position of a line:
31059
+ its width plus its ring. Without it the edge carets are clipped. */
31060
+ --_padding-inline: calc(var(--_caret-width) + var(--_caret-ring-width));
30306
31061
  --_content-color: var(--semantics-content-color);
30307
31062
  --_text-font: var(--primitives-font-body-md-regular-snug);
30308
31063
  --_code-font: var(--primitives-font-monospace-md-regular-snug);
@@ -30439,16 +31194,14 @@ var textEditorStyles = css`
30439
31194
  surfaces in light and dark (accent-600 fell just short on white), so the
30440
31195
  caret stays clearly visible without washing out. */
30441
31196
  border-left-color: var(--primitives-color-accent-700);
30442
- border-left-width: var(--__caret-width);
30443
- /* A thin surface-colored halo, like the drop cursor, so the caret stays legible
31197
+ border-left-width: var(--_caret-width);
31198
+ /* CodeMirror centers its own 1.2px caret with a -0.6px margin. Ours is wider,
31199
+ so recenter it on the insertion point from its own width. */
31200
+ margin-left: calc(var(--_caret-width) / -2);
31201
+ /* A thin surface-colored ring, like the drop cursor, so the caret stays legible
30444
31202
  even over a tinted token (annotation, inline code) where the accent alone can
30445
31203
  blend in. Blinks with the caret (opacity covers the shadow too). */
30446
- box-shadow: 0 0 0 var(--primitives-border-width-thin) var(--semantics-surfaces-base-background-color);
30447
- /* CodeMirror draws the caret ~0.6px left of the text position. At a line start
30448
- with no inline padding (the simple variant) that overhangs the scroller's
30449
- overflow edge, so the left of the 2px gets clipped and the caret looks thinner
30450
- there than mid-line. Nudge it right so the full width stays inside the content. */
30451
- transform: translateX(0.6px);
31204
+ box-shadow: 0 0 0 var(--_caret-ring-width) var(--semantics-surfaces-base-background-color);
30452
31205
  }
30453
31206
 
30454
31207
  :host .cm-scroller {
@@ -30912,12 +31665,20 @@ var nlddTextEditorTranslations = {
30912
31665
  *
30913
31666
  * Headless: there is no built-in toolbar. A consumer drives formatting via the
30914
31667
  * command methods (toggleBold/toggleItalic/toggleInlineCode/toggleStrikethrough/
30915
- * toggleHeading/toggleBulletList/toggleQuote/toggleLink/runCommand to toggle, and
31668
+ * toggleHeading/toggleBulletList/toggleTaskList/toggleQuote/toggleLink/runCommand
31669
+ * to toggle, and
30916
31670
  * setHeading/setList for picker-style "set" semantics), reads the active formats
30917
31671
  * with getState(), listens to the nldd-text-editor-state event to render toggle
30918
31672
  * states, and forwards padding clicks with focusFromPoint(). Cmd/Ctrl+B/I/E/K are
30919
- * bound out of the box. Commands keep focus on the editor. An @-mention typeahead
30920
- * (mentionSource) collapses to an atomic token, and a W3C-style annotation overlay
31673
+ * bound out of the box. Commands keep focus on the editor. An inline toggle wraps
31674
+ * the selected text (whitespace at its edges stays outside the markers), unwraps
31675
+ * a run the caret is in, and with the caret at the very end of a run steps out
31676
+ * of it. Bold that a typed space has broken (`**woord **`) keeps its styling
31677
+ * while the caret is inside; the space moves outside the markers when the caret
31678
+ * leaves. insertAtCursor(text) puts text at the caret and replaceRange(from, to,
31679
+ * text) writes at the same clean offsets getSelection() reads. An @-mention typeahead
31680
+ * (mentionSource) collapses to an atomic token, further typeahead lists on their
31681
+ * own trigger (typeaheads) write what you tell them to, and a W3C-style annotation overlay
30921
31682
  * (annotations) marks ranges with a dashed underline, light tint and a count badge
30922
31683
  * without touching the underlying text.
30923
31684
  *
@@ -30937,6 +31698,7 @@ var nlddTextEditorTranslations = {
30937
31698
  * @attr {string} accessible-label - Accessible label forwarded to the editor. Set automatically by nldd-form-field.
30938
31699
  *
30939
31700
  * @prop {MentionSource} mentionSource - Consumer-supplied @-mention candidate source (property only). Without it, @-typeahead is inert.
31701
+ * @prop {Typeahead[]} typeaheads - Your own typeahead lists next to the @-mention (property only): each a trigger character (`#`, `:`, `/`), a `source` that returns candidates for the text typed after it, and an optional `insert` that decides what a choice writes (by default the trigger, the text and a space). Lists on one trigger are merged in order. A candidate is `{ id, text, supportingText? }` and can carry an `avatar` (its row then takes two lines, the supporting text under the text), an `icon` or a `symbol` for its row.
30940
31702
  * @attr {boolean} annotatable - Enable the annotation overlay (off by default). Annotations only render when this is set.
30941
31703
  * @prop {Annotation[]} annotations - Consumer-supplied annotation overlay (property only). Anchored by offset and mapped through edits; the text stays clean. Requires `annotatable`. Assign a NEW array to apply changes (Lit dirty-checks by identity, so in-place mutation like `.push()` won't re-render): `editor.annotations = [...editor.annotations, next]`.
30942
31704
  * @attr {object} translations - Override the editor's assistive-tech strings (the open-in-new-tab link badge and the annotation count badge). Unset keys fall back to Dutch.
@@ -30945,7 +31707,8 @@ var nlddTextEditorTranslations = {
30945
31707
  * @fires input - When the content changes (detail: { value })
30946
31708
  * @fires change - When the content is committed on blur (detail: { value })
30947
31709
  * @fires nldd-text-editor-state - When the selection or content changes (detail: TextEditorState), for toolbar toggle state
30948
- * @fires nldd-text-editor-mention - When an @-mention is inserted (detail: MentionInsertedDetail with id, label, from, to)
31710
+ * @fires nldd-text-editor-mention - When an @-mention is inserted (detail: MentionInsertedDetail with id, text, from, to; clean offsets, like getSelection())
31711
+ * @fires nldd-text-editor-typeahead - When a candidate from one of the `typeaheads` is chosen (detail: TypeaheadChosenDetail with trigger, candidate, from, to; clean offsets)
30949
31712
  * @fires nldd-text-editor-annotation-click - When an annotation's count badge is clicked (detail: { ids: string[], rect: DOMRect }); rect is the badge's viewport box so a consumer can anchor its own note UI to it
30950
31713
  */
30951
31714
  var _NLDDTextEditor;
@@ -30967,6 +31730,12 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30967
31730
  this.resize = "auto";
30968
31731
  this.variant = "simple";
30969
31732
  this.accessibleLabel = "";
31733
+ this.typeaheads = [];
31734
+ this._mentionTypeahead = {
31735
+ trigger: "@",
31736
+ source: (query) => this.mentionSource?.(query) ?? [],
31737
+ insert: mentionInsert
31738
+ };
30970
31739
  this.annotatable = false;
30971
31740
  this.annotations = [];
30972
31741
  this.translations = {};
@@ -30977,6 +31746,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30977
31746
  this._historyCompartment = new Compartment();
30978
31747
  this.invalid = false;
30979
31748
  this._cutBuffer = null;
31749
+ this._lastState = null;
30980
31750
  this._onScrollerPointerDown = (event) => {
30981
31751
  if (event.target !== this.view?.scrollDOM) return;
30982
31752
  if (this.view.state.readOnly) return;
@@ -31029,7 +31799,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31029
31799
  return [
31030
31800
  nlddCodeMirrorTheme,
31031
31801
  markdownEditing,
31032
- mentions(() => this.mentionSource, (detail) => this._emitMention(detail)),
31802
+ typeaheads(() => this._typeaheadLists(), (choice) => this._onTypeaheadChosen(choice)),
31033
31803
  Prec.low(orderedListRenumber),
31034
31804
  annotations((count, quote) => this._t("components.text-editor.annotation-count-label", {
31035
31805
  count,
@@ -31039,6 +31809,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31039
31809
  Prec.highest(linkOpenBadge((url) => this._t("components.text-editor.open-in-new-tab-label", { url }))),
31040
31810
  this._historyCompartment.of(history()),
31041
31811
  drawSelection(),
31812
+ stableCursor,
31042
31813
  dropCursor(),
31043
31814
  dragToMove,
31044
31815
  Prec.highest(keymap.of([{
@@ -31082,6 +31853,20 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31082
31853
  key: "Delete",
31083
31854
  run: (view) => this._selectMentionBeforeDelete(view, 1)
31084
31855
  },
31856
+ {
31857
+ key: "Mod-]",
31858
+ run: (view) => {
31859
+ indentListItems(view);
31860
+ return true;
31861
+ }
31862
+ },
31863
+ {
31864
+ key: "Mod-[",
31865
+ run: (view) => {
31866
+ outdentListItems(view);
31867
+ return true;
31868
+ }
31869
+ },
31085
31870
  ...defaultKeymap,
31086
31871
  ...historyKeymap
31087
31872
  ]),
@@ -31097,7 +31882,8 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31097
31882
  focus: () => {
31098
31883
  this._valueAtFocus = this.value;
31099
31884
  },
31100
- blur: () => {
31885
+ blur: (_event, view) => {
31886
+ repairHeldEmphasis(view);
31101
31887
  if (this.value !== this._valueAtFocus) this._emitChange();
31102
31888
  },
31103
31889
  mousedown: (event, view) => {
@@ -31154,7 +31940,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31154
31940
  firstUpdated() {
31155
31941
  this._initialValue = this.value;
31156
31942
  this.style.setProperty("--_rows", String(this.rows));
31157
- this.mountEditor(this.value);
31943
+ this.mountEditor(stripSentinels(this.value));
31158
31944
  this.onEditorMounted();
31159
31945
  }
31160
31946
  onEditorMounted() {
@@ -31170,7 +31956,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31170
31956
  if (changed.has("rows")) this.style.setProperty("--_rows", String(this.rows));
31171
31957
  if (this.view) {
31172
31958
  if (changed.has("value")) {
31173
- if (stripSentinels(this.doc) !== this.value) this.setDoc(this.value);
31959
+ if (stripSentinels(this.doc) !== this.value) this.setDoc(stripSentinels(this.value));
31174
31960
  this.commitFormValue();
31175
31961
  }
31176
31962
  if (changed.has("disabled") || changed.has("readonly")) this.reconfigure(this._editableCompartment, this._editableExtension());
@@ -31221,8 +32007,14 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31221
32007
  toggleBulletList() {
31222
32008
  if (this.view) toggleBulletList(this.view);
31223
32009
  }
32010
+ /** Turn the selected lines into GFM task items (`- [ ] `), or back into
32011
+ * bullets when they all are tasks. Checking a box is not a command. */
32012
+ toggleTaskList() {
32013
+ if (this.view) toggleTaskList(this.view);
32014
+ }
31224
32015
  /** Set the list type, replacing any existing list ('none' strips it) — for an
31225
- * exclusive list picker. */
32016
+ * exclusive list picker. A task keeps its checkbox only within its own type:
32017
+ * switching to bullet or ordered takes the box along with the marker. */
31226
32018
  setList(type) {
31227
32019
  if (this.view) setList(this.view, type);
31228
32020
  }
@@ -31331,6 +32123,45 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31331
32123
  this._pasteText(stripSentinels(raw));
31332
32124
  this.view?.focus();
31333
32125
  }
32126
+ /** Puts `text` at the caret, in place of the selection when there is one, and
32127
+ * leaves the caret after it, with focus on the editor. Plain text: markdown
32128
+ * in it stays as written and is styled like anything typed. Does nothing on
32129
+ * a read-only editor. */
32130
+ insertAtCursor(text) {
32131
+ if (!this.view || this.view.state.readOnly) return;
32132
+ const clean = stripSentinels(text);
32133
+ if (clean) this.view.dispatch({
32134
+ ...this.view.state.replaceSelection(clean),
32135
+ userEvent: "input"
32136
+ });
32137
+ this.view.focus();
32138
+ }
32139
+ /** Replaces the text between two offsets with `text`, and leaves the caret
32140
+ * after it. The offsets are CLEAN, the same coordinates `getSelection()` and
32141
+ * the annotations speak, so a consumer can act on what it read there. Both
32142
+ * are clamped into the text, and `to` below `from` counts as an insert at
32143
+ * `from`. Does nothing on a read-only editor. */
32144
+ replaceRange(from, to, text) {
32145
+ if (!this.view || this.view.state.readOnly) return;
32146
+ const doc = this.view.state.doc.toString();
32147
+ const sentinels = sentinelPositions(doc);
32148
+ const cleanLength = doc.length - sentinels.length;
32149
+ const start = Math.max(0, Math.min(from, cleanLength));
32150
+ const end = Math.max(start, Math.min(to, cleanLength));
32151
+ const insert = stripSentinels(text);
32152
+ const docFrom = cleanToDoc(sentinels, start);
32153
+ const docTo = cleanToDoc(sentinels, end);
32154
+ this.view.dispatch({
32155
+ changes: {
32156
+ from: docFrom,
32157
+ to: docTo,
32158
+ insert
32159
+ },
32160
+ selection: { anchor: docFrom + insert.length },
32161
+ userEvent: "input"
32162
+ });
32163
+ this.view.focus();
32164
+ }
31334
32165
  /** Insert `text` at the selection, re-attaching the cut annotations when it matches
31335
32166
  * a cut made in this editor (a move, one-shot). Shared by the toolbar's paste()
31336
32167
  * and the native Cmd/Ctrl+V handler. */
@@ -31340,7 +32171,10 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31340
32171
  const sameText = (a, b) => a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
31341
32172
  const carry = !!(buffer && sameText(buffer.text, text) && buffer.anns.length > 0);
31342
32173
  const at = docToClean(this.view.state.doc.toString(), this.view.state.selection.main.from);
31343
- const spec = this.view.state.replaceSelection(text);
32174
+ const spec = {
32175
+ ...this.view.state.replaceSelection(text),
32176
+ userEvent: "input.paste"
32177
+ };
31344
32178
  this.view.dispatch(carry ? {
31345
32179
  ...spec,
31346
32180
  effects: pasteAnnotations.of({
@@ -31350,10 +32184,20 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31350
32184
  } : spec);
31351
32185
  if (carry) this._cutBuffer = null;
31352
32186
  }
31353
- /** Escape hatch: run a command by name (bold, italic, inlineCode,
31354
- * strikethrough, bulletList, quote, heading [payload: level], link
31355
- * [payload: href], copy, cut, paste). */
32187
+ /** Escape hatch: run a command by name, so a toolbar can carry the name in its
32188
+ * markup instead of a method reference. Every method above is reachable.
32189
+ *
32190
+ * Toggles (no payload): `bold`, `italic`, `inlineCode`, `strikethrough`,
32191
+ * `bulletList`, `taskList`, `quote`, `codeBlock`, `heading` (payload: the
32192
+ * level, default 1), `link` (payload: the href).
32193
+ * Setters: `setHeading` (payload: the level, 0 for a paragraph), `setList`
32194
+ * (payload: 'none' | 'bullet' | 'ordered' | 'task').
32195
+ * The rest: `indent`, `outdent`, `undo`, `redo`, `copy`, `cut`, `paste`.
32196
+ *
32197
+ * An unknown name warns rather than doing nothing quietly: a typo in a
32198
+ * toolbar is otherwise invisible. */
31356
32199
  runCommand(name, payload) {
32200
+ const level = (fallback) => typeof payload === "number" ? payload : fallback;
31357
32201
  switch (name) {
31358
32202
  case "bold":
31359
32203
  this.toggleBold();
@@ -31370,22 +32214,49 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31370
32214
  case "bulletList":
31371
32215
  this.toggleBulletList();
31372
32216
  break;
32217
+ case "taskList":
32218
+ this.toggleTaskList();
32219
+ break;
31373
32220
  case "quote":
31374
32221
  this.toggleQuote();
31375
32222
  break;
32223
+ case "codeBlock":
32224
+ this.toggleCodeBlock();
32225
+ break;
31376
32226
  case "heading":
31377
- this.toggleHeading(typeof payload === "number" ? payload : 1);
32227
+ this.toggleHeading(level(1));
32228
+ break;
32229
+ case "setHeading":
32230
+ this.setHeading(level(0));
32231
+ break;
32232
+ case "setList":
32233
+ this.setList(typeof payload === "string" ? payload : "none");
31378
32234
  break;
31379
32235
  case "link":
31380
32236
  this.toggleLink(typeof payload === "string" ? payload : "");
31381
32237
  break;
32238
+ case "indent":
32239
+ this.indent();
32240
+ break;
32241
+ case "outdent":
32242
+ this.outdent();
32243
+ break;
32244
+ case "undo":
32245
+ this.undo();
32246
+ break;
32247
+ case "redo":
32248
+ this.redo();
32249
+ break;
31382
32250
  case "copy":
31383
32251
  this.copy();
31384
32252
  break;
31385
32253
  case "cut":
31386
32254
  this.cut();
31387
32255
  break;
31388
- case "paste": this.paste();
32256
+ case "paste":
32257
+ this.paste();
32258
+ break;
32259
+ default: console.warn(`<nldd-text-editor>: runCommand('${name}') is not a command.`);
31389
32260
  }
31390
32261
  }
31391
32262
  /** The formats active at the current selection (drives a toolbar's state). */
@@ -31451,8 +32322,43 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31451
32322
  }));
31452
32323
  }
31453
32324
  _emitState() {
32325
+ const state = this.getState();
32326
+ const last = this._lastState;
32327
+ if (last && last.empty === state.empty && last.canIndent === state.canIndent && last.canOutdent === state.canOutdent && last.canUndo === state.canUndo && last.canRedo === state.canRedo && Object.keys(state.active).every((key) => last.active[key] === state.active[key])) return;
32328
+ this._lastState = state;
31454
32329
  this.dispatchEvent(new CustomEvent("nldd-text-editor-state", {
31455
- detail: this.getState(),
32330
+ detail: state,
32331
+ bubbles: true,
32332
+ composed: true
32333
+ }));
32334
+ }
32335
+ _typeaheadLists() {
32336
+ return this.mentionSource ? [this._mentionTypeahead, ...this.typeaheads] : this.typeaheads;
32337
+ }
32338
+ /** A choice from any list. Offsets in the events are clean, like
32339
+ * getSelection() and the annotations: the document carries sentinels the
32340
+ * value never shows. */
32341
+ _onTypeaheadChosen(choice) {
32342
+ if (!this.view) return;
32343
+ const doc = this.view.state.doc.toString();
32344
+ const from = docToClean(doc, choice.from);
32345
+ if (choice.typeahead === this._mentionTypeahead) {
32346
+ const { id, text } = choice.candidate;
32347
+ this._emitMention({
32348
+ id,
32349
+ text,
32350
+ from,
32351
+ to: from + mentionToken(choice.candidate).length
32352
+ });
32353
+ return;
32354
+ }
32355
+ this.dispatchEvent(new CustomEvent("nldd-text-editor-typeahead", {
32356
+ detail: {
32357
+ trigger: choice.trigger,
32358
+ candidate: choice.candidate,
32359
+ from,
32360
+ to: docToClean(doc, choice.to)
32361
+ },
31456
32362
  bubbles: true,
31457
32363
  composed: true
31458
32364
  }));
@@ -31552,6 +32458,7 @@ __decorate([property({
31552
32458
  attribute: "accessible-label"
31553
32459
  })], NLDDTextEditor.prototype, "accessibleLabel", void 0);
31554
32460
  __decorate([property({ attribute: false })], NLDDTextEditor.prototype, "mentionSource", void 0);
32461
+ __decorate([property({ attribute: false })], NLDDTextEditor.prototype, "typeaheads", void 0);
31555
32462
  __decorate([property({
31556
32463
  type: Boolean,
31557
32464
  reflect: true
@@ -40160,10 +41067,6 @@ var sheetStyles = css`
40160
41067
  background: var(--semantics-overlays-backdrop-color);
40161
41068
  }
40162
41069
 
40163
- :host([modeless]) .sheet::backdrop {
40164
- background: transparent;
40165
- }
40166
-
40167
41070
  :host([placement="left"]) .sheet {
40168
41071
  @media (min-width: ${mdMin$4}) {
40169
41072
  inset: var(--semantics-overlays-inset) auto var(--semantics-overlays-inset) var(--semantics-overlays-inset);
@@ -40243,7 +41146,7 @@ function sheetTemplate(component) {
40243
41146
  return html`
40244
41147
  <dialog class="sheet"
40245
41148
  aria-label=${component.accessibleLabel}
40246
- aria-modal=${component.modeless ? nothing : "true"}
41149
+ aria-modal="true"
40247
41150
  @pointerdown=${component._handleDialogPointerDown}
40248
41151
  @click=${component._handleDialogClick}
40249
41152
  @cancel=${component._handleCancel}
@@ -40356,7 +41259,6 @@ function openWhenRendered(host, getDialog, open) {
40356
41259
  *
40357
41260
  * @attr {string} placement - Sheet position: 'left' | 'right' | 'bottom' (default: 'right')
40358
41261
  * @attr {string} height - Custom height for bottom sheets (and for any sheet on sm viewports, where all placements collapse to bottom). Accepts: `'full'` (default — viewport minus top-inset, identical to omitting the attribute), `'fit-content'` (collapse to content size), or any CSS length/percentage (e.g. `'50dvh'`, `'480px'`, `'50%'`). Always clamped to `100dvh - top-inset` so the sheet can't extend past the dismiss-tap area. No effect on side sheets at md+.
40359
- * @attr {boolean} modeless - Non-modal (no backdrop or focus lock); the sheet is modal by default
40360
41262
  * @attr {string} accessible-label - Accessible name for the dialog, forwarded as aria-label (default: 'Venster')
40361
41263
  * @attr {string} width - Custom width for side sheets (left/right) as a CSS length (e.g. '480px', '32rem'). Applied from the md breakpoint up; ignored on sm (bottom sheet) and for `placement="bottom"`. Clamped to `100vw - 2 * inset` so the sheet always fits.
40362
41264
  *
@@ -40379,7 +41281,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40379
41281
  super(..._args);
40380
41282
  this.placement = "right";
40381
41283
  this.height = "";
40382
- this.modeless = false;
40383
41284
  this.accessibleLabel = "Venster";
40384
41285
  this.width = "";
40385
41286
  this._hasWarnedLabel = false;
@@ -40430,8 +41331,7 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40430
41331
  this._cancelPendingOpen?.();
40431
41332
  this._cancelPendingOpen = null;
40432
41333
  this._closeEmitted = false;
40433
- if (this.modeless) dialog.show();
40434
- else dialog.showModal();
41334
+ dialog.showModal();
40435
41335
  this._manageFocus();
40436
41336
  this.dispatchEvent(new CustomEvent("open", {
40437
41337
  bubbles: true,
@@ -40470,7 +41370,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40470
41370
  this._pointerDownOnBackdrop = e.target === this._dialog;
40471
41371
  }
40472
41372
  _handleDialogClick(e) {
40473
- if (this.modeless) return;
40474
41373
  if (e.target === this._dialog && this._pointerDownOnBackdrop) this.hide();
40475
41374
  }
40476
41375
  _emitClose() {
@@ -40503,10 +41402,6 @@ __decorate([property({
40503
41402
  type: String,
40504
41403
  reflect: true
40505
41404
  })], NLDDSheet.prototype, "height", void 0);
40506
- __decorate([property({
40507
- type: Boolean,
40508
- reflect: true
40509
- })], NLDDSheet.prototype, "modeless", void 0);
40510
41405
  __decorate([property({
40511
41406
  type: String,
40512
41407
  attribute: "accessible-label"
@@ -41707,7 +42602,7 @@ function windowTemplate(component) {
41707
42602
  return html`
41708
42603
  <dialog class="window"
41709
42604
  aria-label=${component._resolvedAccessibleLabel}
41710
- aria-modal=${component.modeless ? nothing : "true"}
42605
+ aria-modal="true"
41711
42606
  @pointerdown=${component._handleDialogPointerDown}
41712
42607
  @click=${component._handleDialogClick}
41713
42608
  @cancel=${component._handleCancel}
@@ -41727,15 +42622,14 @@ var nlddWindowTranslations = { "components.window.accessible-label": "Venster" }
41727
42622
  /**
41728
42623
  * Nederlandse Digitale Dienst Window Component (Lit + TypeScript)
41729
42624
  *
41730
- * A floating window based on the native <dialog> element. Can be shown modal or
41731
- * modeless. Positionable through CSS values.
42625
+ * A floating window based on the native <dialog> element, positionable through
42626
+ * CSS values. Always modal.
41732
42627
  *
41733
42628
  * No header of its own: consumers use nldd-page with a sticky header inside for
41734
42629
  * a title bar.
41735
42630
  *
41736
42631
  * @element nldd-window
41737
42632
  *
41738
- * @attr {boolean} modeless - Modeless (no backdrop or focus trap); the window is modal by default
41739
42633
  * @attr {boolean} no-light-dismiss - A click on the backdrop does not close the window. For windows where dismissing by accident costs work: a wizard, a form with filled-in fields. Escape and the dismiss button keep working.
41740
42634
  * @attr {string} accessible-label - (required) Accessible name (aria-label). Falls back to the i18n default ('Venster') when unset. Always pass a unique, descriptive name per window.
41741
42635
  * @attr {object} translations - Override translation keys; unset keys fall back to the Dutch default.
@@ -41760,7 +42654,6 @@ var _NLDDWindow;
41760
42654
  var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41761
42655
  constructor(..._args) {
41762
42656
  super(..._args);
41763
- this.modeless = false;
41764
42657
  this.noLightDismiss = false;
41765
42658
  this.accessibleLabel = "";
41766
42659
  this.translations = {};
@@ -41779,7 +42672,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41779
42672
  this._pointerDownOnBackdrop = this._isOnBackdrop(e);
41780
42673
  };
41781
42674
  this._handleDialogClick = (e) => {
41782
- if (this.modeless || this.noLightDismiss) return;
42675
+ if (this.noLightDismiss) return;
41783
42676
  if (this._isOnBackdrop(e) && this._pointerDownOnBackdrop) this.hide();
41784
42677
  };
41785
42678
  this._handleCancel = (e) => {
@@ -41833,8 +42726,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41833
42726
  const dialog = this._dialog;
41834
42727
  if (!dialog) return;
41835
42728
  this._closeEmitted = false;
41836
- if (this.modeless) dialog.show();
41837
- else dialog.showModal();
42729
+ dialog.showModal();
41838
42730
  this._applyPositionStyles();
41839
42731
  this._manageFocus();
41840
42732
  this.dispatchEvent(new CustomEvent("open", {
@@ -41896,10 +42788,6 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41896
42788
  return windowTemplate(this);
41897
42789
  }
41898
42790
  }, _NLDDWindow.styles = windowStyles, _NLDDWindow);
41899
- __decorate([property({
41900
- type: Boolean,
41901
- reflect: true
41902
- })], NLDDWindow.prototype, "modeless", void 0);
41903
42791
  __decorate([property({
41904
42792
  type: Boolean,
41905
42793
  reflect: true,
@@ -43436,7 +44324,7 @@ var template$8 = function() {
43436
44324
  *
43437
44325
  * @element nldd-title-cell
43438
44326
  * @attr {1|2|3|4|5|6} size - Visual size of the title (default: 5)
43439
- * @attr {'default' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Text color variant (default: 'default'). `secondary` demotes the title to match the muted overline/supporting-text. `accent`, `success`, `warning` and `critical` tint all three regions so the cell reads as a coherent state.
44327
+ * @attr {'content' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Text color variant (default: 'content'). `secondary` demotes the title to match the muted overline/supporting-text. `accent`, `success`, `warning` and `critical` tint all three regions so the cell reads as a coherent state.
43440
44328
  * @attr {string} width - 'full' | 'fit-content' | CSS length (e.g. '200px', '20rem'). Default: 'full'
43441
44329
  * @attr {string} min-width - Minimum width as CSS length (e.g. '80px', '5rem')
43442
44330
  * @attr {string} max-width - Maximum width as CSS length (e.g. '300px', '20rem')
@@ -43473,7 +44361,7 @@ var NLDDTitleCell = (_NLDDTitleCell = class NLDDTitleCell extends VisibilityMixi
43473
44361
  constructor(..._args) {
43474
44362
  super(..._args);
43475
44363
  this.size = 5;
43476
- this.color = "default";
44364
+ this.color = "content";
43477
44365
  this.width = "full";
43478
44366
  this.horizontalAlignment = "left";
43479
44367
  this.verticalAlignment = "center";
@@ -43531,7 +44419,7 @@ __decorate([property({
43531
44419
  })], NLDDTitleCell.prototype, "size", void 0);
43532
44420
  __decorate([property({
43533
44421
  reflect: true,
43534
- converter: reflectNonDefault("default")
44422
+ converter: reflectNonDefault("content")
43535
44423
  })], NLDDTitleCell.prototype, "color", void 0);
43536
44424
  __decorate([property({
43537
44425
  reflect: true,
@@ -45437,7 +46325,7 @@ var nlddLinkTranslations = { "components.link.opens-in-new-tab-label": "Opent in
45437
46325
  * @attr {string} href - Link target
45438
46326
  * @attr {string} target - Link target (e.g. '_blank'); adjusts rel automatically. With '_blank' the link adds a visually hidden "Opent in nieuw tabblad" announcement for screen readers (WCAG 2.1 SC 3.2.2).
45439
46327
  * @attr {string} rel - Link rel attribute; defaults to 'noopener noreferrer' with target='_blank'
45440
- * @attr {string} size - Text size: 'xs' | 'sm' | 'md' | 'lg' | 'inherit'. Empty = inherit.
46328
+ * @attr {string} size - Text size: 'inherit' (the default) follows the surrounding text and lays the link out inline, so it wraps in running prose. 'xs' | 'sm' | 'md' | 'lg' pin a size and switch to inline-flex, which baseline-aligns a start or end icon with an explicit gap.
45441
46329
  * @attr {string} text - Link text (alternative to the default slot)
45442
46330
  * @attr {string} start-icon - Icon before the text
45443
46331
  * @attr {string} end-icon - Icon after the text
@@ -45457,6 +46345,7 @@ var NLDDLink = (_NLDDLink = class NLDDLink extends withTranslations(LitElement,
45457
46345
  this.href = void 0;
45458
46346
  this.target = void 0;
45459
46347
  this.rel = void 0;
46348
+ this.size = "inherit";
45460
46349
  this.text = "";
45461
46350
  this.startIcon = "";
45462
46351
  this.endIcon = "";
@@ -45490,8 +46379,8 @@ __decorate([property({
45490
46379
  __decorate([property({ type: String })], NLDDLink.prototype, "target", void 0);
45491
46380
  __decorate([property({ type: String })], NLDDLink.prototype, "rel", void 0);
45492
46381
  __decorate([property({
45493
- type: String,
45494
- reflect: true
46382
+ reflect: true,
46383
+ converter: reflectNonDefault("inherit")
45495
46384
  })], NLDDLink.prototype, "size", void 0);
45496
46385
  __decorate([property({
45497
46386
  reflect: true,
@@ -46447,7 +47336,7 @@ var NLDDTopNavigationBar = (_NLDDTopNavigationBar = class NLDDTopNavigationBar e
46447
47336
  Promise.resolve().then(() => list_item_exports),
46448
47337
  Promise.resolve().then(() => text_cell_exports),
46449
47338
  Promise.resolve().then(() => icon_cell_exports),
46450
- import("./icon-Cd4BloIM.js").then((n) => n.n)
47339
+ import("./icon-C_zwJZ_g.js").then((n) => n.n)
46451
47340
  ]);
46452
47341
  }
46453
47342
  _createGlobalMenuSheet() {
@@ -47748,7 +48637,7 @@ function documentTabBarItemTemplate(component) {
47748
48637
  @click=${component._handleClick}
47749
48638
  >${tabContent}</button>`;
47750
48639
  return html`
47751
- <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "default" : "never"}>
48640
+ <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "delay" : "never"}>
47752
48641
  <div class="document-tab-bar__item">
47753
48642
  ${tab}
47754
48643
  <button class="document-tab-bar__item-dismiss-button"
@@ -49097,6 +49986,7 @@ function modalDialogTemplate(component) {
49097
49986
  icon=${component.icon || nothing}
49098
49987
  text=${component.text || nothing}
49099
49988
  supporting-text=${component.supportingText || nothing}
49989
+ horizontal-alignment=${component.horizontalAlignment || nothing}
49100
49990
  heading-level="2"
49101
49991
  >
49102
49992
  <slot></slot>
@@ -49119,6 +50009,7 @@ function modalDialogTemplate(component) {
49119
50009
  * @attr {string} icon - Forwarded to nldd-inline-dialog; absent when not set
49120
50010
  * @attr {string} text - Forwarded to nldd-inline-dialog; main text
49121
50011
  * @attr {string} supporting-text - Forwarded to nldd-inline-dialog; supporting text
50012
+ * @attr {'left'|'center'} horizontal-alignment - Forwarded to nldd-inline-dialog. Unset derives it there: slotted content aligns left, a bare message stays centered.
49122
50013
  * @attr {string} accessible-label - Accessible name for the dialog (aria-label); falls back to text
49123
50014
  *
49124
50015
  * @slot - Optional custom content, forwarded to nldd-inline-dialog
@@ -49142,6 +50033,7 @@ var NLDDModalDialog = (_NLDDModalDialog = class NLDDModalDialog extends LitEleme
49142
50033
  this.icon = "";
49143
50034
  this.text = "";
49144
50035
  this.supportingText = "";
50036
+ this.horizontalAlignment = "";
49145
50037
  this.accessibleLabel = "";
49146
50038
  this._closing = false;
49147
50039
  this._cancelPendingOpen = null;
@@ -49242,6 +50134,11 @@ __decorate([property({
49242
50134
  attribute: "supporting-text",
49243
50135
  converter: reflectNonDefault("")
49244
50136
  })], NLDDModalDialog.prototype, "supportingText", void 0);
50137
+ __decorate([property({
50138
+ reflect: true,
50139
+ attribute: "horizontal-alignment",
50140
+ converter: reflectNonDefault("")
50141
+ })], NLDDModalDialog.prototype, "horizontalAlignment", void 0);
49245
50142
  __decorate([property({
49246
50143
  type: String,
49247
50144
  attribute: "accessible-label"