@nldd/design-system 0.8.86 → 0.8.88

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 (179) hide show
  1. package/custom-elements.json +662 -144
  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 +1263 -273
  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/page.d.ts +23 -5
  101. package/dist/components/layout/page/page.d.ts.map +1 -1
  102. package/dist/components/layout/page/page.js +39 -34
  103. package/dist/components/layout/page/page.js.map +1 -1
  104. package/dist/components/layout/page/page.styles.d.ts.map +1 -1
  105. package/dist/components/layout/page/page.styles.js +2 -0
  106. package/dist/components/layout/page/page.styles.js.map +1 -1
  107. package/dist/components/layout/page-sections/page-section-controls.d.ts +0 -70
  108. package/dist/components/layout/page-sections/page-section-controls.d.ts.map +1 -1
  109. package/dist/components/layout/page-sections/page-section-controls.js +0 -3
  110. package/dist/components/layout/page-sections/page-section-controls.js.map +1 -1
  111. package/dist/components/layout/sheet/sheet.d.ts +3 -7
  112. package/dist/components/layout/sheet/sheet.d.ts.map +1 -1
  113. package/dist/components/layout/sheet/sheet.js +4 -18
  114. package/dist/components/layout/sheet/sheet.js.map +1 -1
  115. package/dist/components/layout/sheet/sheet.styles.d.ts.map +1 -1
  116. package/dist/components/layout/sheet/sheet.styles.js +0 -4
  117. package/dist/components/layout/sheet/sheet.styles.js.map +1 -1
  118. package/dist/components/layout/sheet/sheet.template.d.ts.map +1 -1
  119. package/dist/components/layout/sheet/sheet.template.js +5 -2
  120. package/dist/components/layout/sheet/sheet.template.js.map +1 -1
  121. package/dist/components/layout/window/window.d.ts +2 -4
  122. package/dist/components/layout/window/window.d.ts.map +1 -1
  123. package/dist/components/layout/window/window.js +4 -14
  124. package/dist/components/layout/window/window.js.map +1 -1
  125. package/dist/components/layout/window/window.template.d.ts.map +1 -1
  126. package/dist/components/layout/window/window.template.js +5 -2
  127. package/dist/components/layout/window/window.template.js.map +1 -1
  128. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.d.ts +2 -2
  129. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.js +3 -3
  130. package/dist/components/lists-and-tables/cells/text-cell/text-cell.d.ts +2 -2
  131. package/dist/components/lists-and-tables/cells/text-cell/text-cell.js +3 -3
  132. package/dist/components/lists-and-tables/cells/title-cell/title-cell.d.ts +2 -2
  133. package/dist/components/lists-and-tables/cells/title-cell/title-cell.js +3 -3
  134. package/dist/components/lists-and-tables/list/list.d.ts +4 -2
  135. package/dist/components/lists-and-tables/list/list.d.ts.map +1 -1
  136. package/dist/components/lists-and-tables/list/list.js +23 -5
  137. package/dist/components/lists-and-tables/list/list.js.map +1 -1
  138. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js +1 -1
  139. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js.map +1 -1
  140. package/dist/components/navigation/link/link.d.ts +2 -2
  141. package/dist/components/navigation/link/link.d.ts.map +1 -1
  142. package/dist/components/navigation/link/link.js +3 -2
  143. package/dist/components/navigation/link/link.js.map +1 -1
  144. package/dist/components/navigation/menu-bar/menu-bar.d.ts +4 -2
  145. package/dist/components/navigation/menu-bar/menu-bar.d.ts.map +1 -1
  146. package/dist/components/navigation/menu-bar/menu-bar.js +4 -2
  147. package/dist/components/navigation/menu-bar/menu-bar.js.map +1 -1
  148. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts +3 -3
  149. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts.map +1 -1
  150. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js +4 -4
  151. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js.map +1 -1
  152. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts +8 -0
  153. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts.map +1 -1
  154. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js +14 -0
  155. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js.map +1 -1
  156. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.d.ts.map +1 -1
  157. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js +22 -1
  158. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js.map +1 -1
  159. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.d.ts.map +1 -1
  160. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js +6 -2
  161. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js.map +1 -1
  162. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts +3 -1
  163. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts.map +1 -1
  164. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js +5 -0
  165. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js.map +1 -1
  166. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.d.ts.map +1 -1
  167. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js +4 -0
  168. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js.map +1 -1
  169. package/dist/components/status-and-feedback/notification/notification-region.d.ts.map +1 -1
  170. package/dist/components/status-and-feedback/notification/notification-region.js +74 -1
  171. package/dist/components/status-and-feedback/notification/notification-region.js.map +1 -1
  172. package/dist/components/status-and-feedback/notification/notification.d.ts +11 -0
  173. package/dist/components/status-and-feedback/notification/notification.d.ts.map +1 -1
  174. package/dist/components/status-and-feedback/notification/notification.js +13 -0
  175. package/dist/components/status-and-feedback/notification/notification.js.map +1 -1
  176. package/dist/favicon.svg +8 -2
  177. package/dist/nldd.min.js +536 -496
  178. package/dist/types/vue.d.ts +33 -25
  179. 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
@@ -37491,6 +38398,7 @@ var pageStyles = css`
37491
38398
  --_outer-inset-bottom: var(--context-inset-bottom, 0px);
37492
38399
  --_header-height: 0px;
37493
38400
  --_footer-height: 0px;
38401
+ --_header-full-height: 0px;
37494
38402
  /* Set from JS while the page owns the scroller. initial, not a length, so
37495
38403
  the fallback stands when the document scrolls instead. */
37496
38404
  --_scroll-height: initial;
@@ -37644,6 +38552,7 @@ var pageStyles = css`
37644
38552
  :host(:not([data-scroll="root"])) .page__scroll {
37645
38553
  --context-inset-top: var(--_header-height);
37646
38554
  --context-inset-bottom: var(--_footer-height);
38555
+ padding-top: var(--_header-full-height);
37647
38556
  }
37648
38557
 
37649
38558
  :host([sticky-header]) .page__scroll {
@@ -37719,8 +38628,9 @@ function pageTemplate(component) {
37719
38628
  * A page layout with optional sticky header and footer.
37720
38629
  * Without sticky-header, the host is the scroll container and the header
37721
38630
  * is in normal flow. With sticky-header, the header becomes absolute and
37722
- * .page__scroll takes over scrolling. A ResizeObserver on the header
37723
- * sets padding-top on the scroll wrapper (only when not scrolled).
38631
+ * .page__scroll takes over scrolling, padded by the measured header height.
38632
+ * That padding freezes once you scroll, so a collapsing bar cannot drag the
38633
+ * content up under the cursor.
37724
38634
  *
37725
38635
  * In root-scroll mode (--context-scroll-mode: root, derived upstream by
37726
38636
  * nldd-app-view) the page stops owning a scroller: the document scrolls and the
@@ -37758,8 +38668,8 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37758
38668
  this._scrollMode = "nested";
37759
38669
  this._scrollTarget = null;
37760
38670
  this._scrollProvider = null;
37761
- this._headerObserver = null;
37762
38671
  this._insetObserver = null;
38672
+ this._headerFullHeight = 0;
37763
38673
  this._mainSlot = null;
37764
38674
  this._resizeRaf = 0;
37765
38675
  this._onResize = () => {
@@ -37834,7 +38744,6 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37834
38744
  this._scrollProvider = null;
37835
38745
  if (this._resizeRaf) cancelAnimationFrame(this._resizeRaf);
37836
38746
  this._teardownScrollListener();
37837
- this._teardownHeaderObserver();
37838
38747
  this._teardownInsetObserver();
37839
38748
  this._teardownMainSlotListener();
37840
38749
  }
@@ -37862,10 +38771,8 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37862
38771
  }
37863
38772
  _configureScroll() {
37864
38773
  this._teardownScrollListener();
37865
- this._teardownHeaderObserver();
37866
- if (this._scrollEl) this._scrollEl.style.paddingTop = "";
37867
38774
  this._setupScrollListener();
37868
- if (this.stickyHeader && !this._isRoot) this._setupHeaderObserver();
38775
+ this._headerFullHeight = 0;
37869
38776
  this._setupInsetObserver();
37870
38777
  this._onScroll();
37871
38778
  }
@@ -37877,6 +38784,11 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37877
38784
  * its anchor, and a consumer that had written the number down would be wrong
37878
38785
  * from that moment on. A bar that is not sticky scrolls away and adds
37879
38786
  * nothing, so it stays at zero.
38787
+ *
38788
+ * It also measures the padding the scroll wrapper reserves for an absolute
38789
+ * sticky header. One observer owns both, because two of them writing styles
38790
+ * that resize what the other watches is what leaves ResizeObserver
38791
+ * notifications undelivered.
37880
38792
  */
37881
38793
  _setupInsetObserver() {
37882
38794
  this._teardownInsetObserver();
@@ -37884,10 +38796,11 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37884
38796
  const footer = this._footerEl;
37885
38797
  if (!header || !footer) return;
37886
38798
  const publish = () => {
37887
- this.style.setProperty("--_header-height", `${this.stickyHeader ? header.offsetHeight : 0}px`);
37888
- this.style.setProperty("--_footer-height", `${this.stickyFooter ? footer.offsetHeight : 0}px`);
38799
+ this._publish("--_header-height", `${this.stickyHeader ? header.offsetHeight : 0}px`);
38800
+ this._publish("--_footer-height", `${this.stickyFooter ? footer.offsetHeight : 0}px`);
38801
+ this._publish("--_header-full-height", `${this._measureHeaderFullHeight(header)}px`);
37889
38802
  if (this._isRoot) this.style.removeProperty("--_scroll-height");
37890
- else this.style.setProperty("--_scroll-height", `${this._scrollEl?.clientHeight ?? 0}px`);
38803
+ else this._publish("--_scroll-height", `${this._scrollEl?.clientHeight ?? 0}px`);
37891
38804
  };
37892
38805
  publish();
37893
38806
  this._insetObserver = new ResizeObserver(publish);
@@ -37901,6 +38814,27 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37901
38814
  this._insetObserver = null;
37902
38815
  }
37903
38816
  }
38817
+ /**
38818
+ * Writes a published variable only when it changes. These values size the
38819
+ * elements the observer watches, so a rewrite feeds the callback its own
38820
+ * next notification, which is what Chromium reports as "ResizeObserver
38821
+ * loop completed with undelivered notifications".
38822
+ */
38823
+ _publish(name, value) {
38824
+ if (this.style.getPropertyValue(name) === value) return;
38825
+ this.style.setProperty(name, value);
38826
+ }
38827
+ /**
38828
+ * The header at its full height: the last measurement taken at scroll top.
38829
+ * A top title bar shrinks as you pass its anchor, and the space reserved for
38830
+ * it must not shrink along, or the content it starts under is dragged up
38831
+ * from under the cursor. Which mode reserves that space is the CSS's call.
38832
+ */
38833
+ _measureHeaderFullHeight(header) {
38834
+ if (!this.stickyHeader) return 0;
38835
+ if (this.scrollTarget.scrollTop === 0) this._headerFullHeight = header.offsetHeight;
38836
+ return this._headerFullHeight;
38837
+ }
37904
38838
  _setupScrollListener() {
37905
38839
  const target = this.scrollEventTarget;
37906
38840
  target.addEventListener("scroll", this._onScroll, { passive: true });
@@ -37912,23 +38846,6 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37912
38846
  this._scrollTarget = null;
37913
38847
  }
37914
38848
  }
37915
- _setupHeaderObserver() {
37916
- this._teardownHeaderObserver();
37917
- const header = this._headerEl;
37918
- const scroll = this._scrollEl;
37919
- if (!header || !scroll) return;
37920
- this._headerObserver = new ResizeObserver(() => {
37921
- if (scroll.scrollTop > 0) return;
37922
- scroll.style.paddingTop = `${header.offsetHeight}px`;
37923
- });
37924
- this._headerObserver.observe(header);
37925
- }
37926
- _teardownHeaderObserver() {
37927
- if (this._headerObserver) {
37928
- this._headerObserver.disconnect();
37929
- this._headerObserver = null;
37930
- }
37931
- }
37932
38849
  _setupMainSlotListener() {
37933
38850
  this._teardownMainSlotListener();
37934
38851
  const slot = this.shadowRoot?.querySelector("slot:not([name])") ?? null;
@@ -40160,10 +41077,6 @@ var sheetStyles = css`
40160
41077
  background: var(--semantics-overlays-backdrop-color);
40161
41078
  }
40162
41079
 
40163
- :host([modeless]) .sheet::backdrop {
40164
- background: transparent;
40165
- }
40166
-
40167
41080
  :host([placement="left"]) .sheet {
40168
41081
  @media (min-width: ${mdMin$4}) {
40169
41082
  inset: var(--semantics-overlays-inset) auto var(--semantics-overlays-inset) var(--semantics-overlays-inset);
@@ -40243,7 +41156,7 @@ function sheetTemplate(component) {
40243
41156
  return html`
40244
41157
  <dialog class="sheet"
40245
41158
  aria-label=${component.accessibleLabel}
40246
- aria-modal=${component.modeless ? nothing : "true"}
41159
+ aria-modal="true"
40247
41160
  @pointerdown=${component._handleDialogPointerDown}
40248
41161
  @click=${component._handleDialogClick}
40249
41162
  @cancel=${component._handleCancel}
@@ -40252,6 +41165,9 @@ function sheetTemplate(component) {
40252
41165
  <div class="sheet__body">
40253
41166
  <slot></slot>
40254
41167
  </div>
41168
+ <!-- Inside the dialog, so it escapes the inertness a modal imposes on
41169
+ everything outside it. Plumbing, not consumer API. -->
41170
+ <slot name="notifications"></slot>
40255
41171
  </dialog>
40256
41172
  `;
40257
41173
  }
@@ -40356,7 +41272,6 @@ function openWhenRendered(host, getDialog, open) {
40356
41272
  *
40357
41273
  * @attr {string} placement - Sheet position: 'left' | 'right' | 'bottom' (default: 'right')
40358
41274
  * @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
41275
  * @attr {string} accessible-label - Accessible name for the dialog, forwarded as aria-label (default: 'Venster')
40361
41276
  * @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
41277
  *
@@ -40379,7 +41294,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40379
41294
  super(..._args);
40380
41295
  this.placement = "right";
40381
41296
  this.height = "";
40382
- this.modeless = false;
40383
41297
  this.accessibleLabel = "Venster";
40384
41298
  this.width = "";
40385
41299
  this._hasWarnedLabel = false;
@@ -40430,8 +41344,7 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40430
41344
  this._cancelPendingOpen?.();
40431
41345
  this._cancelPendingOpen = null;
40432
41346
  this._closeEmitted = false;
40433
- if (this.modeless) dialog.show();
40434
- else dialog.showModal();
41347
+ dialog.showModal();
40435
41348
  this._manageFocus();
40436
41349
  this.dispatchEvent(new CustomEvent("open", {
40437
41350
  bubbles: true,
@@ -40470,7 +41383,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40470
41383
  this._pointerDownOnBackdrop = e.target === this._dialog;
40471
41384
  }
40472
41385
  _handleDialogClick(e) {
40473
- if (this.modeless) return;
40474
41386
  if (e.target === this._dialog && this._pointerDownOnBackdrop) this.hide();
40475
41387
  }
40476
41388
  _emitClose() {
@@ -40503,10 +41415,6 @@ __decorate([property({
40503
41415
  type: String,
40504
41416
  reflect: true
40505
41417
  })], NLDDSheet.prototype, "height", void 0);
40506
- __decorate([property({
40507
- type: Boolean,
40508
- reflect: true
40509
- })], NLDDSheet.prototype, "modeless", void 0);
40510
41418
  __decorate([property({
40511
41419
  type: String,
40512
41420
  attribute: "accessible-label"
@@ -41707,7 +42615,7 @@ function windowTemplate(component) {
41707
42615
  return html`
41708
42616
  <dialog class="window"
41709
42617
  aria-label=${component._resolvedAccessibleLabel}
41710
- aria-modal=${component.modeless ? nothing : "true"}
42618
+ aria-modal="true"
41711
42619
  @pointerdown=${component._handleDialogPointerDown}
41712
42620
  @click=${component._handleDialogClick}
41713
42621
  @cancel=${component._handleCancel}
@@ -41716,6 +42624,9 @@ function windowTemplate(component) {
41716
42624
  <div class="window__body">
41717
42625
  <slot></slot>
41718
42626
  </div>
42627
+ <!-- Inside the dialog, so it escapes the inertness a modal imposes on
42628
+ everything outside it. Plumbing, not consumer API. -->
42629
+ <slot name="notifications"></slot>
41719
42630
  </dialog>
41720
42631
  `;
41721
42632
  }
@@ -41727,15 +42638,14 @@ var nlddWindowTranslations = { "components.window.accessible-label": "Venster" }
41727
42638
  /**
41728
42639
  * Nederlandse Digitale Dienst Window Component (Lit + TypeScript)
41729
42640
  *
41730
- * A floating window based on the native <dialog> element. Can be shown modal or
41731
- * modeless. Positionable through CSS values.
42641
+ * A floating window based on the native <dialog> element, positionable through
42642
+ * CSS values. Always modal.
41732
42643
  *
41733
42644
  * No header of its own: consumers use nldd-page with a sticky header inside for
41734
42645
  * a title bar.
41735
42646
  *
41736
42647
  * @element nldd-window
41737
42648
  *
41738
- * @attr {boolean} modeless - Modeless (no backdrop or focus trap); the window is modal by default
41739
42649
  * @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
42650
  * @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
42651
  * @attr {object} translations - Override translation keys; unset keys fall back to the Dutch default.
@@ -41760,7 +42670,6 @@ var _NLDDWindow;
41760
42670
  var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41761
42671
  constructor(..._args) {
41762
42672
  super(..._args);
41763
- this.modeless = false;
41764
42673
  this.noLightDismiss = false;
41765
42674
  this.accessibleLabel = "";
41766
42675
  this.translations = {};
@@ -41779,7 +42688,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41779
42688
  this._pointerDownOnBackdrop = this._isOnBackdrop(e);
41780
42689
  };
41781
42690
  this._handleDialogClick = (e) => {
41782
- if (this.modeless || this.noLightDismiss) return;
42691
+ if (this.noLightDismiss) return;
41783
42692
  if (this._isOnBackdrop(e) && this._pointerDownOnBackdrop) this.hide();
41784
42693
  };
41785
42694
  this._handleCancel = (e) => {
@@ -41833,8 +42742,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41833
42742
  const dialog = this._dialog;
41834
42743
  if (!dialog) return;
41835
42744
  this._closeEmitted = false;
41836
- if (this.modeless) dialog.show();
41837
- else dialog.showModal();
42745
+ dialog.showModal();
41838
42746
  this._applyPositionStyles();
41839
42747
  this._manageFocus();
41840
42748
  this.dispatchEvent(new CustomEvent("open", {
@@ -41896,10 +42804,6 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41896
42804
  return windowTemplate(this);
41897
42805
  }
41898
42806
  }, _NLDDWindow.styles = windowStyles, _NLDDWindow);
41899
- __decorate([property({
41900
- type: Boolean,
41901
- reflect: true
41902
- })], NLDDWindow.prototype, "modeless", void 0);
41903
42807
  __decorate([property({
41904
42808
  type: Boolean,
41905
42809
  reflect: true,
@@ -43436,7 +44340,7 @@ var template$8 = function() {
43436
44340
  *
43437
44341
  * @element nldd-title-cell
43438
44342
  * @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.
44343
+ * @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
44344
  * @attr {string} width - 'full' | 'fit-content' | CSS length (e.g. '200px', '20rem'). Default: 'full'
43441
44345
  * @attr {string} min-width - Minimum width as CSS length (e.g. '80px', '5rem')
43442
44346
  * @attr {string} max-width - Maximum width as CSS length (e.g. '300px', '20rem')
@@ -43473,7 +44377,7 @@ var NLDDTitleCell = (_NLDDTitleCell = class NLDDTitleCell extends VisibilityMixi
43473
44377
  constructor(..._args) {
43474
44378
  super(..._args);
43475
44379
  this.size = 5;
43476
- this.color = "default";
44380
+ this.color = "content";
43477
44381
  this.width = "full";
43478
44382
  this.horizontalAlignment = "left";
43479
44383
  this.verticalAlignment = "center";
@@ -43531,7 +44435,7 @@ __decorate([property({
43531
44435
  })], NLDDTitleCell.prototype, "size", void 0);
43532
44436
  __decorate([property({
43533
44437
  reflect: true,
43534
- converter: reflectNonDefault("default")
44438
+ converter: reflectNonDefault("content")
43535
44439
  })], NLDDTitleCell.prototype, "color", void 0);
43536
44440
  __decorate([property({
43537
44441
  reflect: true,
@@ -45109,8 +46013,10 @@ var NLDDMenuBar = (_NLDDMenuBar = class NLDDMenuBar extends withTranslations(Lit
45109
46013
  }
45110
46014
  /**
45111
46015
  * Calculate which slotted items overflow and hide them behind an overflow button.
45112
- * Note: not unit-tested — JSDOM lacks layout support (offsetWidth, clientWidth).
45113
- * Covered by visual/E2E testing via Storybook stories.
46016
+ * Tested both ways in menu-bar.test.ts: with real layout for the whole path, and
46017
+ * with mocked widths for the arithmetic. The space reserved for the button needs
46018
+ * the mocks — the button is a shrinkable flex item, so the rendered layout
46019
+ * absorbs whatever the arithmetic leaves over and always looks right.
45114
46020
  */
45115
46021
  _updateOverflow() {
45116
46022
  const overflowButton = this._overflowButton;
@@ -45437,7 +46343,7 @@ var nlddLinkTranslations = { "components.link.opens-in-new-tab-label": "Opent in
45437
46343
  * @attr {string} href - Link target
45438
46344
  * @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
46345
  * @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.
46346
+ * @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
46347
  * @attr {string} text - Link text (alternative to the default slot)
45442
46348
  * @attr {string} start-icon - Icon before the text
45443
46349
  * @attr {string} end-icon - Icon after the text
@@ -45457,6 +46363,7 @@ var NLDDLink = (_NLDDLink = class NLDDLink extends withTranslations(LitElement,
45457
46363
  this.href = void 0;
45458
46364
  this.target = void 0;
45459
46365
  this.rel = void 0;
46366
+ this.size = "inherit";
45460
46367
  this.text = "";
45461
46368
  this.startIcon = "";
45462
46369
  this.endIcon = "";
@@ -45490,8 +46397,8 @@ __decorate([property({
45490
46397
  __decorate([property({ type: String })], NLDDLink.prototype, "target", void 0);
45491
46398
  __decorate([property({ type: String })], NLDDLink.prototype, "rel", void 0);
45492
46399
  __decorate([property({
45493
- type: String,
45494
- reflect: true
46400
+ reflect: true,
46401
+ converter: reflectNonDefault("inherit")
45495
46402
  })], NLDDLink.prototype, "size", void 0);
45496
46403
  __decorate([property({
45497
46404
  reflect: true,
@@ -46447,7 +47354,7 @@ var NLDDTopNavigationBar = (_NLDDTopNavigationBar = class NLDDTopNavigationBar e
46447
47354
  Promise.resolve().then(() => list_item_exports),
46448
47355
  Promise.resolve().then(() => text_cell_exports),
46449
47356
  Promise.resolve().then(() => icon_cell_exports),
46450
- import("./icon-Cd4BloIM.js").then((n) => n.n)
47357
+ import("./icon-C_zwJZ_g.js").then((n) => n.n)
46451
47358
  ]);
46452
47359
  }
46453
47360
  _createGlobalMenuSheet() {
@@ -47748,7 +48655,7 @@ function documentTabBarItemTemplate(component) {
47748
48655
  @click=${component._handleClick}
47749
48656
  >${tabContent}</button>`;
47750
48657
  return html`
47751
- <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "default" : "never"}>
48658
+ <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "delay" : "never"}>
47752
48659
  <div class="document-tab-bar__item">
47753
48660
  ${tab}
47754
48661
  <button class="document-tab-bar__item-dismiss-button"
@@ -49097,11 +50004,15 @@ function modalDialogTemplate(component) {
49097
50004
  icon=${component.icon || nothing}
49098
50005
  text=${component.text || nothing}
49099
50006
  supporting-text=${component.supportingText || nothing}
50007
+ horizontal-alignment=${component.horizontalAlignment || nothing}
49100
50008
  heading-level="2"
49101
50009
  >
49102
50010
  <slot></slot>
49103
50011
  <slot slot="actions" name="actions"></slot>
49104
50012
  </nldd-inline-dialog>
50013
+ <!-- Inside the dialog, so it escapes the inertness a modal imposes on
50014
+ everything outside it. Plumbing, not consumer API. -->
50015
+ <slot name="notifications"></slot>
49105
50016
  </dialog>
49106
50017
  `;
49107
50018
  }
@@ -49119,6 +50030,7 @@ function modalDialogTemplate(component) {
49119
50030
  * @attr {string} icon - Forwarded to nldd-inline-dialog; absent when not set
49120
50031
  * @attr {string} text - Forwarded to nldd-inline-dialog; main text
49121
50032
  * @attr {string} supporting-text - Forwarded to nldd-inline-dialog; supporting text
50033
+ * @attr {'left'|'center'} horizontal-alignment - Forwarded to nldd-inline-dialog. Unset derives it there: slotted content aligns left, a bare message stays centered.
49122
50034
  * @attr {string} accessible-label - Accessible name for the dialog (aria-label); falls back to text
49123
50035
  *
49124
50036
  * @slot - Optional custom content, forwarded to nldd-inline-dialog
@@ -49142,6 +50054,7 @@ var NLDDModalDialog = (_NLDDModalDialog = class NLDDModalDialog extends LitEleme
49142
50054
  this.icon = "";
49143
50055
  this.text = "";
49144
50056
  this.supportingText = "";
50057
+ this.horizontalAlignment = "";
49145
50058
  this.accessibleLabel = "";
49146
50059
  this._closing = false;
49147
50060
  this._cancelPendingOpen = null;
@@ -49242,6 +50155,11 @@ __decorate([property({
49242
50155
  attribute: "supporting-text",
49243
50156
  converter: reflectNonDefault("")
49244
50157
  })], NLDDModalDialog.prototype, "supportingText", void 0);
50158
+ __decorate([property({
50159
+ reflect: true,
50160
+ attribute: "horizontal-alignment",
50161
+ converter: reflectNonDefault("")
50162
+ })], NLDDModalDialog.prototype, "horizontalAlignment", void 0);
49245
50163
  __decorate([property({
49246
50164
  type: String,
49247
50165
  attribute: "accessible-label"
@@ -49909,6 +50827,63 @@ var REGION_ID = "nldd-notification-region";
49909
50827
  var MAX_DEPTH = 2;
49910
50828
  var frontObserver = null;
49911
50829
  var expanded = false;
50830
+ /**
50831
+ * A modal overlay paints in the browser's top layer, above the whole document
50832
+ * and out of reach of any z-index, and while one is open everything outside it
50833
+ * is inert: a region left on the body is not merely behind the sheet, it is
50834
+ * unclickable even once you put it in the top layer yourself. So the region
50835
+ * travels. It lives in the topmost open overlay and sinks back as they close,
50836
+ * which is also what makes a notification raised before the sheet opened come
50837
+ * along rather than stay behind it.
50838
+ *
50839
+ * Innermost last. Entries are verified on read rather than trusted, so an
50840
+ * overlay torn out of the DOM without closing cannot strand the region.
50841
+ */
50842
+ var overlays = [];
50843
+ var watchingOverlays = false;
50844
+ function modalOf(host) {
50845
+ const dialog = host.shadowRoot?.querySelector("dialog") ?? null;
50846
+ return dialog?.matches(":modal") ? dialog : null;
50847
+ }
50848
+ function regionHost() {
50849
+ while (overlays.length) {
50850
+ const top = overlays[overlays.length - 1];
50851
+ if (top.isConnected && modalOf(top)) return top;
50852
+ overlays.pop();
50853
+ }
50854
+ return document.body;
50855
+ }
50856
+ function placeRegion(region) {
50857
+ const host = regionHost();
50858
+ if (region.parentElement === host) return;
50859
+ if (host === document.body) region.removeAttribute("slot");
50860
+ else region.setAttribute("slot", "notifications");
50861
+ const carried = notifications(region);
50862
+ carried.forEach((item) => item._setMoving?.(true));
50863
+ host.appendChild(region);
50864
+ carried.forEach((item) => item._setMoving?.(false));
50865
+ }
50866
+ function relocateRegion() {
50867
+ const region = document.getElementById(REGION_ID);
50868
+ if (region) placeRegion(region);
50869
+ }
50870
+ function watchOverlays() {
50871
+ if (watchingOverlays) return;
50872
+ watchingOverlays = true;
50873
+ document.addEventListener("open", (event) => {
50874
+ const host = event.target;
50875
+ if (!host?.shadowRoot || !modalOf(host)) return;
50876
+ if (!overlays.includes(host)) overlays.push(host);
50877
+ relocateRegion();
50878
+ });
50879
+ document.addEventListener("close", (event) => {
50880
+ const host = event.target;
50881
+ const at = host ? overlays.indexOf(host) : -1;
50882
+ if (at === -1) return;
50883
+ overlays.splice(at, 1);
50884
+ relocateRegion();
50885
+ }, true);
50886
+ }
49912
50887
  function notifications(region) {
49913
50888
  return Array.from(region.querySelectorAll(":scope > nldd-notification"));
49914
50889
  }
@@ -49941,7 +50916,7 @@ function ensureRegion(label) {
49941
50916
  region.setAttribute("role", "region");
49942
50917
  region.setAttribute("aria-label", label);
49943
50918
  region.appendChild(makeExpander(region));
49944
- document.body.appendChild(region);
50919
+ placeRegion(region);
49945
50920
  return region;
49946
50921
  }
49947
50922
  /**
@@ -49971,7 +50946,9 @@ function makeExpander(region) {
49971
50946
  /** Newest in front: what just happened is what you want to read, and the older
49972
50947
  * ones slide back and downwards behind it. */
49973
50948
  function joinRegion(notification, label) {
50949
+ watchOverlays();
49974
50950
  const region = ensureRegion(label);
50951
+ placeRegion(region);
49975
50952
  notification.style.pointerEvents = "auto";
49976
50953
  region.prepend(notification);
49977
50954
  syncStack(region);
@@ -50106,6 +51083,13 @@ var nlddNotificationTranslations = {
50106
51083
  * Nothing about the position is settable, so notifications from anywhere in an
50107
51084
  * application land in the same place and stack in the same order.
50108
51085
  *
51086
+ * A modal overlay is the one thing that moves it. An nldd-sheet, nldd-window or
51087
+ * nldd-modal-dialog paints in the browser's top layer, above the whole page and
51088
+ * out of reach of any z-index, and while one is open everything outside it is
51089
+ * inert. So the region goes in: into the topmost overlay that is open, and back
51090
+ * down as they close. A notification already on screen travels with it, and one
51091
+ * raised from inside a sheet is readable and reachable where it is raised.
51092
+ *
50109
51093
  * ## The stack
50110
51094
  * More than one is a deck, not a list: the front one is readable and the older
50111
51095
  * ones peek out below it, so a burst of messages takes the room of roughly one.
@@ -50258,6 +51242,12 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
50258
51242
  if (changed.has("variant")) this.setAttribute("role", this.variant === "critical" ? "alert" : "status");
50259
51243
  if (changed.has("duration")) this._remainingDuration = this.duration;
50260
51244
  }
51245
+ /** Called by the region while it carries this notification to another
51246
+ * parent. A move disconnects the element, and without this the
51247
+ * notification would read its own disconnect as having been dismissed. */
51248
+ _setMoving(moving) {
51249
+ this._moving = moving;
51250
+ }
50261
51251
  /** Called by the region. Starting the clock here rather than on connect is
50262
51252
  * the whole point: a notification three deep in the deck is not counting. */
50263
51253
  _setFront(isFront) {