@nldd/design-system 0.8.85 → 0.8.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/custom-elements.json +1011 -355
  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 +1546 -371
  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/collection/collection.d.ts +15 -8
  101. package/dist/components/layout/collection/collection.d.ts.map +1 -1
  102. package/dist/components/layout/collection/collection.js +38 -13
  103. package/dist/components/layout/collection/collection.js.map +1 -1
  104. package/dist/components/layout/collection/collection.styles.d.ts.map +1 -1
  105. package/dist/components/layout/collection/collection.styles.js +36 -23
  106. package/dist/components/layout/collection/collection.styles.js.map +1 -1
  107. package/dist/components/layout/container/container.d.ts +11 -3
  108. package/dist/components/layout/container/container.d.ts.map +1 -1
  109. package/dist/components/layout/container/container.js +30 -17
  110. package/dist/components/layout/container/container.js.map +1 -1
  111. package/dist/components/layout/container/container.styles.d.ts.map +1 -1
  112. package/dist/components/layout/container/container.styles.js +19 -21
  113. package/dist/components/layout/container/container.styles.js.map +1 -1
  114. package/dist/components/layout/page/page.d.ts +23 -2
  115. package/dist/components/layout/page/page.d.ts.map +1 -1
  116. package/dist/components/layout/page/page.js +56 -2
  117. package/dist/components/layout/page/page.js.map +1 -1
  118. package/dist/components/layout/page/page.styles.d.ts.map +1 -1
  119. package/dist/components/layout/page/page.styles.js +33 -8
  120. package/dist/components/layout/page/page.styles.js.map +1 -1
  121. package/dist/components/layout/page-sections/page-section-controls.d.ts +0 -70
  122. package/dist/components/layout/page-sections/page-section-controls.d.ts.map +1 -1
  123. package/dist/components/layout/page-sections/page-section-controls.js +0 -3
  124. package/dist/components/layout/page-sections/page-section-controls.js.map +1 -1
  125. package/dist/components/layout/page-sections/sidebar-section/sidebar-section.styles.d.ts.map +1 -1
  126. package/dist/components/layout/page-sections/sidebar-section/sidebar-section.styles.js +7 -5
  127. package/dist/components/layout/page-sections/sidebar-section/sidebar-section.styles.js.map +1 -1
  128. package/dist/components/layout/sheet/sheet.d.ts +3 -7
  129. package/dist/components/layout/sheet/sheet.d.ts.map +1 -1
  130. package/dist/components/layout/sheet/sheet.js +4 -18
  131. package/dist/components/layout/sheet/sheet.js.map +1 -1
  132. package/dist/components/layout/sheet/sheet.styles.d.ts.map +1 -1
  133. package/dist/components/layout/sheet/sheet.styles.js +2 -6
  134. package/dist/components/layout/sheet/sheet.styles.js.map +1 -1
  135. package/dist/components/layout/sheet/sheet.template.js +2 -2
  136. package/dist/components/layout/sheet/sheet.template.js.map +1 -1
  137. package/dist/components/layout/split-views/bar-split-view/bar-split-view.d.ts +1 -1
  138. package/dist/components/layout/split-views/bar-split-view/bar-split-view.d.ts.map +1 -1
  139. package/dist/components/layout/split-views/bar-split-view/bar-split-view.js +13 -13
  140. package/dist/components/layout/split-views/bar-split-view/bar-split-view.js.map +1 -1
  141. package/dist/components/layout/split-views/bar-split-view/bar-split-view.styles.js +1 -1
  142. package/dist/components/layout/window/window.d.ts +2 -4
  143. package/dist/components/layout/window/window.d.ts.map +1 -1
  144. package/dist/components/layout/window/window.js +4 -14
  145. package/dist/components/layout/window/window.js.map +1 -1
  146. package/dist/components/layout/window/window.template.d.ts.map +1 -1
  147. package/dist/components/layout/window/window.template.js +2 -2
  148. package/dist/components/layout/window/window.template.js.map +1 -1
  149. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.d.ts +2 -2
  150. package/dist/components/lists-and-tables/cells/icon-cell/icon-cell.js +3 -3
  151. package/dist/components/lists-and-tables/cells/text-cell/text-cell.d.ts +2 -2
  152. package/dist/components/lists-and-tables/cells/text-cell/text-cell.js +3 -3
  153. package/dist/components/lists-and-tables/cells/title-cell/title-cell.d.ts +2 -2
  154. package/dist/components/lists-and-tables/cells/title-cell/title-cell.js +3 -3
  155. package/dist/components/lists-and-tables/list/list.d.ts +17 -5
  156. package/dist/components/lists-and-tables/list/list.d.ts.map +1 -1
  157. package/dist/components/lists-and-tables/list/list.js +62 -8
  158. package/dist/components/lists-and-tables/list/list.js.map +1 -1
  159. package/dist/components/lists-and-tables/list/list.styles.d.ts.map +1 -1
  160. package/dist/components/lists-and-tables/list/list.styles.js +8 -1
  161. package/dist/components/lists-and-tables/list/list.styles.js.map +1 -1
  162. package/dist/components/lists-and-tables/list/list.template.d.ts +4 -1
  163. package/dist/components/lists-and-tables/list/list.template.d.ts.map +1 -1
  164. package/dist/components/lists-and-tables/list/list.template.js +31 -12
  165. package/dist/components/lists-and-tables/list/list.template.js.map +1 -1
  166. package/dist/components/lists-and-tables/list-item/list-item.d.ts +8 -3
  167. package/dist/components/lists-and-tables/list-item/list-item.d.ts.map +1 -1
  168. package/dist/components/lists-and-tables/list-item/list-item.js +35 -9
  169. package/dist/components/lists-and-tables/list-item/list-item.js.map +1 -1
  170. package/dist/components/lists-and-tables/list-item/list-item.template.d.ts +1 -1
  171. package/dist/components/lists-and-tables/list-item/list-item.template.d.ts.map +1 -1
  172. package/dist/components/lists-and-tables/list-item/list-item.template.js +18 -1
  173. package/dist/components/lists-and-tables/list-item/list-item.template.js.map +1 -1
  174. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js +1 -1
  175. package/dist/components/navigation/document-tab-bar/document-tab-bar.template.js.map +1 -1
  176. package/dist/components/navigation/link/link.d.ts +2 -2
  177. package/dist/components/navigation/link/link.d.ts.map +1 -1
  178. package/dist/components/navigation/link/link.js +3 -2
  179. package/dist/components/navigation/link/link.js.map +1 -1
  180. package/dist/components/navigation/top-navigation-bar/top-navigation-bar.styles.d.ts.map +1 -1
  181. package/dist/components/navigation/top-navigation-bar/top-navigation-bar.styles.js +50 -12
  182. package/dist/components/navigation/top-navigation-bar/top-navigation-bar.styles.js.map +1 -1
  183. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts +3 -3
  184. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.d.ts.map +1 -1
  185. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js +4 -4
  186. package/dist/components/status-and-feedback/activity-indicator/activity-indicator.js.map +1 -1
  187. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts +8 -0
  188. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.d.ts.map +1 -1
  189. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js +14 -0
  190. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.js.map +1 -1
  191. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.d.ts.map +1 -1
  192. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js +22 -1
  193. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.styles.js.map +1 -1
  194. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.d.ts.map +1 -1
  195. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js +6 -2
  196. package/dist/components/status-and-feedback/inline-dialog/inline-dialog.template.js.map +1 -1
  197. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts +3 -1
  198. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.d.ts.map +1 -1
  199. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js +5 -0
  200. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.js.map +1 -1
  201. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.styles.js +3 -3
  202. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.d.ts.map +1 -1
  203. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js +1 -0
  204. package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js.map +1 -1
  205. package/dist/components/status-and-feedback/progress-circle/progress-circle.styles.d.ts.map +1 -1
  206. package/dist/components/status-and-feedback/progress-circle/progress-circle.styles.js +15 -0
  207. package/dist/components/status-and-feedback/progress-circle/progress-circle.styles.js.map +1 -1
  208. package/dist/components/status-and-feedback/progress-circle/progress-circle.template.d.ts.map +1 -1
  209. package/dist/components/status-and-feedback/progress-circle/progress-circle.template.js +4 -3
  210. package/dist/components/status-and-feedback/progress-circle/progress-circle.template.js.map +1 -1
  211. package/dist/components/visibility-mixin-BU1vIMW7.js +161 -0
  212. package/dist/favicon.svg +10 -0
  213. package/dist/nldd.min.js +834 -676
  214. package/dist/touch-icon.png +0 -0
  215. package/dist/types/vue.d.ts +55 -33
  216. package/dist/utilities/spacing-scale.d.ts +26 -0
  217. package/dist/utilities/spacing-scale.d.ts.map +1 -0
  218. package/dist/utilities/spacing-scale.js +54 -0
  219. package/dist/utilities/spacing-scale.js.map +1 -0
  220. package/dist/utilities/visibility-mixin.d.ts +11 -8
  221. package/dist/utilities/visibility-mixin.d.ts.map +1 -1
  222. package/dist/utilities/visibility-mixin.js +58 -45
  223. package/dist/utilities/visibility-mixin.js.map +1 -1
  224. package/dist/utilities/visibility-mixin.styles.d.ts +14 -0
  225. package/dist/utilities/visibility-mixin.styles.d.ts.map +1 -0
  226. package/dist/utilities/visibility-mixin.styles.js +52 -0
  227. package/dist/utilities/visibility-mixin.styles.js.map +1 -0
  228. package/package.json +5 -2
  229. package/dist/components/visibility-mixin-vbfhyo7f.js +0 -104
@@ -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
- import { t as VisibilityMixin } from "./visibility-mixin-vbfhyo7f.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";
10
+ import { t as VisibilityMixin } from "./visibility-mixin-BU1vIMW7.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,
@@ -7468,10 +7506,10 @@ var containerStyles = css`
7468
7506
  --_justify-content: initial;
7469
7507
  --_justify-items: initial;
7470
7508
  --_align-items: initial;
7471
- --_gap: 0;
7472
- --_sm-gap: var(--_gap);
7473
- --_md-gap: var(--_gap);
7474
- --_lg-gap: var(--_gap);
7509
+ --_sm-gap: 0;
7510
+ --_md-gap: 0;
7511
+ --_lg-gap: 0;
7512
+ --_gap: var(--_sm-gap);
7475
7513
  --_padding-top: 0;
7476
7514
  --_padding-right: 0;
7477
7515
  --_padding-bottom: 0;
@@ -7492,19 +7530,17 @@ var containerStyles = css`
7492
7530
  --_slot-sm-order: var(--_slot-order);
7493
7531
  --_slot-md-order: var(--_slot-order);
7494
7532
  --_slot-lg-order: var(--_slot-order);
7495
- /* One resolved gap for every layout to read. Multicol splits the gap
7496
- over two properties (column-gap plus a margin on the items), so a
7497
- per-breakpoint declaration would have to be repeated in three
7498
- rules; swapping the var keeps that in one place. */
7499
- --_resolved-gap: var(--_gap);
7500
-
7501
- @media (max-width: ${smMax$17}) { --_resolved-gap: var(--_sm-gap); }
7502
- @media (min-width: ${mdMin$20}) and (max-width: ${mdMax$13}) { --_resolved-gap: var(--_md-gap); }
7503
- @media (min-width: ${lgMin$14}) { --_resolved-gap: var(--_lg-gap); }
7504
-
7505
- @container layout-container (max-width: ${smMax$17}) { --_resolved-gap: var(--_sm-gap); }
7506
- @container layout-container (min-width: ${mdMin$20}) and (max-width: ${mdMax$13}) { --_resolved-gap: var(--_md-gap); }
7507
- @container layout-container (min-width: ${lgMin$14}) { --_resolved-gap: var(--_lg-gap); }
7533
+ /* Two sets, because a container inside a layout-container follows that
7534
+ container and anywhere else the viewport. Multicol reads the gap in three
7535
+ rules, so the value is swapped here rather than declared in each. The
7536
+ bare --_gap is what stands when neither set matches. */
7537
+ @media (max-width: ${smMax$17}) { --_gap: var(--_sm-gap); }
7538
+ @media (min-width: ${mdMin$20}) and (max-width: ${mdMax$13}) { --_gap: var(--_md-gap); }
7539
+ @media (min-width: ${lgMin$14}) { --_gap: var(--_lg-gap); }
7540
+
7541
+ @container layout-container (max-width: ${smMax$17}) { --_gap: var(--_sm-gap); }
7542
+ @container layout-container (min-width: ${mdMin$20}) and (max-width: ${mdMax$13}) { --_gap: var(--_md-gap); }
7543
+ @container layout-container (min-width: ${lgMin$14}) { --_gap: var(--_lg-gap); }
7508
7544
 
7509
7545
  display: block;
7510
7546
  width: var(--_width);
@@ -7587,7 +7623,7 @@ var containerStyles = css`
7587
7623
  justify-content: var(--_justify-content);
7588
7624
  justify-items: var(--_justify-items);
7589
7625
  align-items: var(--_align-items);
7590
- gap: var(--_resolved-gap);
7626
+ gap: var(--_gap);
7591
7627
  }
7592
7628
 
7593
7629
  :host([layout="row"]) .container__inner {
@@ -7610,7 +7646,7 @@ var containerStyles = css`
7610
7646
  :host([layout="columns"]) .container__inner {
7611
7647
  display: block;
7612
7648
  columns: var(--_min-column-width);
7613
- column-gap: var(--_resolved-gap);
7649
+ column-gap: var(--_gap);
7614
7650
  }
7615
7651
 
7616
7652
  :host([layout="columns"]) ::slotted(*) {
@@ -7623,14 +7659,14 @@ var containerStyles = css`
7623
7659
  :host([layout="lanes"]) .container__inner {
7624
7660
  display: block;
7625
7661
  columns: var(--_min-column-width);
7626
- column-gap: var(--_resolved-gap);
7662
+ column-gap: var(--_gap);
7627
7663
  }
7628
7664
 
7629
7665
  :host([layout="lanes"]) ::slotted(*) {
7630
7666
  break-inside: avoid;
7631
7667
  /* multicol has no row-gap; item margin supplies the vertical gap. The
7632
7668
  native branch resets this (grid-lanes gap covers both axes). */
7633
- margin-bottom: var(--_resolved-gap);
7669
+ margin-bottom: var(--_gap);
7634
7670
  }
7635
7671
 
7636
7672
  @supports (display: grid-lanes) {
@@ -7754,6 +7790,57 @@ function containerTemplate(component) {
7754
7790
  `;
7755
7791
  }
7756
7792
  //#endregion
7793
+ //#region src/utilities/spacing-scale.ts
7794
+ /**
7795
+ * The spacing scale, for the attributes that take a step of it.
7796
+ *
7797
+ * Spacing is rhythm: the point of a scale is that the space between things is
7798
+ * the same handful of numbers on every page, so a value beside it is not a
7799
+ * finer choice but a break in the pattern. These attributes take a step and
7800
+ * nothing else, and this is the one place that turns a step into its token.
7801
+ *
7802
+ * Which also settles what the attribute means. `gap="16"` in a component that
7803
+ * expected a CSS length wrote `--_gap: 16`, which is not one: the declaration
7804
+ * fell away and the gap became zero with nothing said. Anything this cannot
7805
+ * place is refused and named in dev instead, a length included — writing an
7806
+ * unplaceable value is that same silent zero.
7807
+ */
7808
+ /** Every step there is. `'0'` is a value, not the absence of one. */
7809
+ var SPACING_SCALE = [
7810
+ "0",
7811
+ "2",
7812
+ "4",
7813
+ "6",
7814
+ "8",
7815
+ "10",
7816
+ "12",
7817
+ "16",
7818
+ "20",
7819
+ "24",
7820
+ "28",
7821
+ "32",
7822
+ "40",
7823
+ "44",
7824
+ "48",
7825
+ "56",
7826
+ "64",
7827
+ "80",
7828
+ "96"
7829
+ ];
7830
+ /**
7831
+ * A step of the scale as the token it stands for.
7832
+ *
7833
+ * Returns null when there is nothing to set, so a caller can remove the
7834
+ * property rather than write an empty one. `element` and `attribute` are only
7835
+ * there to name the offender in a dev warning.
7836
+ */
7837
+ function spacingToValue(value, element, attribute) {
7838
+ if (value === void 0 || value === "") return null;
7839
+ const step = String(value).trim();
7840
+ if (SPACING_SCALE.includes(step)) return step === "0" ? "0" : `var(--primitives-space-${step})`;
7841
+ return null;
7842
+ }
7843
+ //#endregion
7757
7844
  //#region src/components/layout/container/container.ts
7758
7845
  /**
7759
7846
  * Nederlandse Digitale Dienst Container Component (Lit + TypeScript)
@@ -7804,8 +7891,11 @@ function containerTemplate(component) {
7804
7891
  * independent of the surrounding page width.
7805
7892
  *
7806
7893
  * `layout="lanes"` packs items into balanced columns using native CSS grid
7807
- * lanes where supported, falling back to CSS multicol (column-order)
7808
- * elsewhere. CSS-only, no JS. Honours `gap` on both axes and `column-count`.
7894
+ * lanes where supported, falling back to CSS multicol (column-order) elsewhere.
7895
+ * CSS-only, no JS. Honours `gap` on both axes and `column-count`. Note that
7896
+ * nldd-collection's lanes falls back to its own grid rather than to multicol:
7897
+ * that component pages, and multicol redistributes the whole set every time
7898
+ * load-more adds to it.
7809
7899
  *
7810
7900
  * @element nldd-container
7811
7901
  *
@@ -7877,10 +7967,11 @@ var ORDER_ATTRS = [
7877
7967
  "md-order",
7878
7968
  "lg-order"
7879
7969
  ];
7880
- function sizeToValue(size) {
7881
- if (size === void 0) return null;
7882
- if (size === "0") return "0";
7883
- return `var(--primitives-space-${size})`;
7970
+ /** A step as its token. `attribute` only names the offender in a dev warning,
7971
+ * so it has to be the attribute the value was written on: with a dozen padding
7972
+ * attributes, "padding/gap is not a step" does not say which one to fix. */
7973
+ function sizeToValue(size, attribute) {
7974
+ return spacingToValue(size, "nldd-container", attribute);
7884
7975
  }
7885
7976
  var NLDDContainer = (_NLDDContainer = class NLDDContainer extends LitElement {
7886
7977
  constructor(..._args) {
@@ -7983,10 +8074,10 @@ var NLDDContainer = (_NLDDContainer = class NLDDContainer extends LitElement {
7983
8074
  setProp("--_align-items", horizontal);
7984
8075
  setProp("--_justify-items", null);
7985
8076
  }
7986
- setProp("--_gap", sizeToValue(this.gap));
7987
- setProp("--_sm-gap", sizeToValue(this.smGap));
7988
- setProp("--_md-gap", sizeToValue(this.mdGap));
7989
- setProp("--_lg-gap", sizeToValue(this.lgGap));
8077
+ const plainGap = sizeToValue(this.gap, "gap");
8078
+ setProp("--_sm-gap", sizeToValue(this.smGap, "sm-gap") ?? plainGap);
8079
+ setProp("--_md-gap", sizeToValue(this.mdGap, "md-gap") ?? plainGap);
8080
+ setProp("--_lg-gap", sizeToValue(this.lgGap, "lg-gap") ?? plainGap);
7990
8081
  for (const scope of [
7991
8082
  "",
7992
8083
  "sm",
@@ -7995,16 +8086,25 @@ var NLDDContainer = (_NLDDContainer = class NLDDContainer extends LitElement {
7995
8086
  ]) {
7996
8087
  const [top, right, bottom, left] = this.resolvePadding(scope);
7997
8088
  const prefix = scope ? `${scope}-` : "";
7998
- setProp(`--_${prefix}padding-top`, sizeToValue(top));
7999
- setProp(`--_${prefix}padding-right`, sizeToValue(right));
8000
- setProp(`--_${prefix}padding-bottom`, sizeToValue(bottom));
8001
- setProp(`--_${prefix}padding-left`, sizeToValue(left));
8089
+ setProp(`--_${prefix}padding-top`, sizeToValue(top?.size, top?.attribute ?? "padding"));
8090
+ setProp(`--_${prefix}padding-right`, sizeToValue(right?.size, right?.attribute ?? "padding"));
8091
+ setProp(`--_${prefix}padding-bottom`, sizeToValue(bottom?.size, bottom?.attribute ?? "padding"));
8092
+ setProp(`--_${prefix}padding-left`, sizeToValue(left?.size, left?.attribute ?? "padding"));
8002
8093
  }
8003
8094
  }
8095
+ /** The four sides, each with the attribute it came from: `padding-top` wins
8096
+ * over `padding-block`, which wins over `padding`, and a warning about a bad
8097
+ * value has to name the one that was actually written. */
8004
8098
  resolvePadding(scope) {
8005
8099
  const get = (key) => {
8006
8100
  const prop = scope ? `${scope}${key}` : key.charAt(0).toLowerCase() + key.slice(1);
8007
- return this[prop];
8101
+ const size = this[prop];
8102
+ if (size === void 0) return void 0;
8103
+ const name = key.replace(/([A-Z])/g, (match, letter, index) => (index ? "-" : "") + letter.toLowerCase());
8104
+ return {
8105
+ size,
8106
+ attribute: scope ? `${scope}-${name}` : name
8107
+ };
8008
8108
  };
8009
8109
  const all = get("Padding");
8010
8110
  const inline = get("PaddingInline");
@@ -8247,7 +8347,10 @@ var listStyles = css`
8247
8347
  box-sizing: border-box;
8248
8348
  }
8249
8349
 
8250
- /* Nothing in it and nothing said about it: not a thing on the page. */
8350
+ /* Nothing in it, nothing said about it and no controls of its own: not a
8351
+ thing on the page, so a parent that spaces its children keeps no room for
8352
+ it either. The surface is already gone with .list__main; this takes the
8353
+ box out of the flow. */
8251
8354
  :host(.is-blank) {
8252
8355
  display: none;
8253
8356
  }
@@ -8330,6 +8433,10 @@ var listStyles = css`
8330
8433
  display: none;
8331
8434
  }
8332
8435
 
8436
+ .list__search-bar[hidden] {
8437
+ display: none;
8438
+ }
8439
+
8333
8440
  .list__main {
8334
8441
  display: flex;
8335
8442
  flex-direction: column;
@@ -8528,18 +8635,22 @@ var listStyles = css`
8528
8635
  `;
8529
8636
  //#endregion
8530
8637
  //#region src/components/lists-and-tables/list/list.template.ts
8531
- var template$14 = ({ itemsLabel, hasToolbar, type, isEmpty, listbox }) => {
8638
+ var template$14 = ({ itemsLabel, hasToolbar, type, isEmpty, hasItems, hasEmptyState, hasNoResultsState, listbox }) => {
8532
8639
  const isNavigation = type === "navigation";
8533
8640
  const isListbox = type === "listbox";
8534
8641
  const skipItemsLabel = isNavigation || isListbox;
8535
- const itemsRole = isListbox ? "listbox" : type === "tree" ? "tree" : "list";
8536
- const showEmpty = isEmpty && !(isListbox && listbox.searchValue === "");
8537
- const showMain = !isEmpty || showEmpty;
8642
+ const itemsRole = isListbox ? "listbox" : type === "tree" ? "tree" : type === "radiogroup" ? "radiogroup" : "list";
8643
+ const isNoResults = isEmpty && hasItems;
8644
+ const noResultsFallsBack = isNoResults && !hasNoResultsState && hasEmptyState;
8645
+ const showNoResults = isNoResults && hasNoResultsState && !(isListbox && listbox.searchValue === "");
8646
+ const showEmpty = !isNoResults && isEmpty && hasEmptyState || noResultsFallsBack && !(isListbox && listbox.searchValue === "");
8647
+ const showMain = !isEmpty || showEmpty || showNoResults;
8648
+ const showControls = hasItems;
8538
8649
  return html`
8539
8650
  <div class="list">
8540
8651
  <div class="list__header">
8541
8652
  ${isListbox ? html`
8542
- <div class="list__search-bar">
8653
+ <div class="list__search-bar" ?hidden=${!showControls}>
8543
8654
  <div class="list__search-field">
8544
8655
  <label class="list__search-field-label">
8545
8656
  <div class="list__search-field-icon"
@@ -8583,7 +8694,7 @@ var template$14 = ({ itemsLabel, hasToolbar, type, isEmpty, listbox }) => {
8583
8694
  </div>
8584
8695
  </div>
8585
8696
  ` : nothing}
8586
- <div class="list__toolbar" ?hidden=${!hasToolbar}>
8697
+ <div class="list__toolbar" ?hidden=${!hasToolbar || !showControls}>
8587
8698
  <slot name="toolbar"></slot>
8588
8699
  </div>
8589
8700
  </div>
@@ -8597,9 +8708,10 @@ var template$14 = ({ itemsLabel, hasToolbar, type, isEmpty, listbox }) => {
8597
8708
  <slot></slot>
8598
8709
  </div>
8599
8710
  <div class="list__empty"
8600
- ?hidden=${!showEmpty}
8711
+ ?hidden=${!showEmpty && !showNoResults}
8601
8712
  >
8602
- <slot name="empty"></slot>
8713
+ <slot name="empty" ?hidden=${!showEmpty}></slot>
8714
+ <slot name="no-results" ?hidden=${!showNoResults}></slot>
8603
8715
  </div>
8604
8716
  </div>
8605
8717
  </div>
@@ -8646,6 +8758,9 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
8646
8758
  this._mergedTranslations = { ...nlddListTranslations };
8647
8759
  this._hasToolbar = false;
8648
8760
  this._hasSearchBarEnd = false;
8761
+ this._hasEmptyState = false;
8762
+ this._hasNoResultsState = false;
8763
+ this._hasItems = false;
8649
8764
  this._isEmpty = false;
8650
8765
  this._searchValue = "";
8651
8766
  this._activeId = "";
@@ -8861,6 +8976,8 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
8861
8976
  toolbarSlot?.addEventListener("slotchange", () => queueMicrotask(() => {
8862
8977
  this._hasToolbar = toolbarSlot.assignedElements().length > 0;
8863
8978
  }));
8979
+ (this.shadowRoot?.querySelector("slot[name=\"empty\"]"))?.addEventListener("slotchange", () => queueMicrotask(() => this._updateEmpty()));
8980
+ (this.shadowRoot?.querySelector("slot[name=\"no-results\"]"))?.addEventListener("slotchange", () => queueMicrotask(() => this._updateEmpty()));
8864
8981
  this._itemsObserver = new MutationObserver((mutations) => {
8865
8982
  const isRow = (node) => node instanceof Element && node.tagName.toLowerCase() === "nldd-list-item";
8866
8983
  if (!mutations.some((m) => {
@@ -9006,9 +9123,12 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
9006
9123
  });
9007
9124
  }
9008
9125
  _updateEmpty() {
9009
- const items = this._getItems();
9126
+ const items = this.hasUpdated ? this._getItems() : Array.from(this.querySelectorAll(":scope > nldd-list-item"));
9010
9127
  this._isEmpty = items.length === 0 || items.every((item) => item.hasAttribute("hidden"));
9011
- this.classList.toggle("is-blank", this._isEmpty && !this.querySelector(":scope > [slot=\"empty\"]"));
9128
+ this._hasItems = items.length > 0;
9129
+ this._hasEmptyState = !!this.querySelector(":scope > [slot=\"empty\"]");
9130
+ this._hasNoResultsState = !!this.querySelector(":scope > [slot=\"no-results\"]");
9131
+ this.classList.toggle("is-blank", this._isEmpty && !this._hasItems && !this._hasEmptyState);
9012
9132
  this._warnSilentEmpty();
9013
9133
  }
9014
9134
  /**
@@ -9018,8 +9138,10 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
9018
9138
  * the app's to write and a house sentence was wrong more often than right.
9019
9139
  * That leaves a real risk of a blank area with nothing in the accessibility
9020
9140
  * tree either, so the list asks the person building it rather than
9021
- * answering for them. A listbox before you have typed is left alone: an
9022
- * 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.
9023
9145
  */
9024
9146
  _warnSilentEmpty() {}
9025
9147
  /** Every option needs a stable id for aria-activedescendant. Assign one to
@@ -9431,6 +9553,9 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
9431
9553
  hasToolbar: this._hasToolbar,
9432
9554
  type: this.type,
9433
9555
  isEmpty: this._isEmpty,
9556
+ hasItems: this._hasItems,
9557
+ hasEmptyState: this._hasEmptyState,
9558
+ hasNoResultsState: this._hasNoResultsState,
9434
9559
  listbox: {
9435
9560
  listboxId: this._listboxId,
9436
9561
  searchValue: this._searchValue,
@@ -9477,6 +9602,9 @@ __decorate([property({ type: Object })], NLDDList.prototype, "translations", voi
9477
9602
  __decorate([state()], NLDDList.prototype, "_mergedTranslations", void 0);
9478
9603
  __decorate([state()], NLDDList.prototype, "_hasToolbar", void 0);
9479
9604
  __decorate([state()], NLDDList.prototype, "_hasSearchBarEnd", void 0);
9605
+ __decorate([state()], NLDDList.prototype, "_hasEmptyState", void 0);
9606
+ __decorate([state()], NLDDList.prototype, "_hasNoResultsState", void 0);
9607
+ __decorate([state()], NLDDList.prototype, "_hasItems", void 0);
9480
9608
  __decorate([state()], NLDDList.prototype, "_isEmpty", void 0);
9481
9609
  __decorate([state()], NLDDList.prototype, "_searchValue", void 0);
9482
9610
  __decorate([state()], NLDDList.prototype, "_activeId", void 0);
@@ -9905,7 +10033,7 @@ var listItemStyles = css`
9905
10033
  //#region src/components/lists-and-tables/list-item/list-item.template.ts
9906
10034
  var content = html`<slot></slot>`;
9907
10035
  var divider = html`<div class="list-item__divider"></div>`;
9908
- var template$13 = (button, href, target, rel, opensInNewTabLabel, actionTabindex, isHighlighted = false, checkbox = false, checked = false, expanded, showChildren = false, hasCheckedSegment = false, disabled = false) => {
10036
+ var template$13 = (button, href, target, rel, opensInNewTabLabel, actionTabindex, isHighlighted = false, checkbox = false, radio = false, checked = false, expanded, showChildren = false, hasCheckedSegment = false, disabled = false) => {
9909
10037
  const ariaExpanded = expanded === void 0 ? nothing : String(expanded);
9910
10038
  const children = showChildren ? html`<div class="list-item__children"
9911
10039
  role="group"
@@ -9938,6 +10066,16 @@ var template$13 = (button, href, target, rel, opensInNewTabLabel, actionTabindex
9938
10066
  >${content}</button>
9939
10067
  ${divider}
9940
10068
  </div>${children}`;
10069
+ if (radio) return html`<div class=${blockClass}>
10070
+ <button class="list-item__action"
10071
+ type="button"
10072
+ role="radio"
10073
+ ?disabled=${disabled}
10074
+ aria-checked=${String(checked)}
10075
+ tabindex=${actionTabindex ?? nothing}
10076
+ >${content}</button>
10077
+ ${divider}
10078
+ </div>${children}`;
9941
10079
  if (button) return html`<div class=${blockClass}>
9942
10080
  <button class="list-item__action"
9943
10081
  type="button"
@@ -10018,6 +10156,7 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10018
10156
  this.current = false;
10019
10157
  this.button = false;
10020
10158
  this.checkbox = false;
10159
+ this.radio = false;
10021
10160
  this.checked = false;
10022
10161
  this.disabled = false;
10023
10162
  this.reorderable = false;
@@ -10050,6 +10189,14 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10050
10189
  composed: true
10051
10190
  }));
10052
10191
  }
10192
+ if (this.radio && !this.checkbox && !this.href && !this.checked && e.composedPath().includes(this._action)) {
10193
+ this.checked = true;
10194
+ this.dispatchEvent(new CustomEvent("change", {
10195
+ detail: { checked: true },
10196
+ bubbles: true,
10197
+ composed: true
10198
+ }));
10199
+ }
10053
10200
  };
10054
10201
  this._handleFocusIn = () => {
10055
10202
  this._action?.classList.toggle("is-pointer-focus", isPointerMode());
@@ -10171,7 +10318,7 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10171
10318
  */
10172
10319
  get _isRovingStop() {
10173
10320
  if (this.disabled) return false;
10174
- if (this.href || this.button || this.checkbox) return true;
10321
+ if (this.href || this.button || this.checkbox || this.radio) return true;
10175
10322
  if (this._ownDescendants("nldd-list-item-segment:not([disabled])").length > 0) return true;
10176
10323
  return this._rovingControls().some((control) => !control.hasAttribute("disabled"));
10177
10324
  }
@@ -10205,7 +10352,7 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10205
10352
  * inside `:host()`, and the row's own padding is what has to change — a
10206
10353
  * `::slotted()` rule could only reach the segment. */
10207
10354
  _updateInteractive() {
10208
- const interactive = !!this.href || this.button || this.checkbox || this._ownDescendants("nldd-list-item-segment").length > 0;
10355
+ const interactive = !!this.href || this.button || this.checkbox || this.radio || this._ownDescendants("nldd-list-item-segment").length > 0;
10209
10356
  this.classList.toggle("is-interactive", interactive);
10210
10357
  const ownChildren = Array.from(this.children).filter((el) => el.getAttribute("slot") !== "children");
10211
10358
  const isSegment = (el) => el?.tagName.toLowerCase() === "nldd-list-item-segment";
@@ -10262,8 +10409,8 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10262
10409
  });
10263
10410
  }
10264
10411
  updated(changed) {
10265
- if (changed.has("selected") || changed.has("current") || changed.has("button") || changed.has("checkbox") || changed.has("href") || changed.has("_parentType")) this._updateAriaState();
10266
- if (changed.has("button") || changed.has("checkbox") || changed.has("href")) this._updateInteractive();
10412
+ if (changed.has("selected") || changed.has("current") || changed.has("button") || changed.has("checkbox") || changed.has("radio") || changed.has("href") || changed.has("_parentType")) this._updateAriaState();
10413
+ if (changed.has("button") || changed.has("checkbox") || changed.has("radio") || changed.has("href")) this._updateInteractive();
10267
10414
  if (changed.has("expanded")) {
10268
10415
  this._relayExpanded();
10269
10416
  this._warnOnStrayExpanded();
@@ -10334,7 +10481,10 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10334
10481
  this.childRows.forEach(fn);
10335
10482
  }
10336
10483
  _updateAriaState() {
10337
- if (this._parentType === "tree") {
10484
+ if (this._parentType === "radiogroup") {
10485
+ this.setAttribute("role", "none");
10486
+ this.removeAttribute("aria-selected");
10487
+ } else if (this._parentType === "tree") {
10338
10488
  this.setAttribute("role", "treeitem");
10339
10489
  this.removeAttribute("aria-selected");
10340
10490
  } else if (this._isListboxOption) {
@@ -10577,7 +10727,7 @@ var NLDDListItem = (_NLDDListItem2 = class NLDDListItem extends withTranslations
10577
10727
  render() {
10578
10728
  const opensInNewTabLabel = this.href && this.target === "_blank" ? this._t("components.list-item.opens-in-new-tab-label") : void 0;
10579
10729
  const actionTabindex = this._isListboxOption ? "-1" : this._arrowNavigation ? this._rovingActive ? "0" : "-1" : void 0;
10580
- return template$13(this.button, this.href, this.target, this.rel, opensInNewTabLabel, actionTabindex, this._highlighted, this.checkbox, this.checked, this._resolvedExpanded, this._showChildren, this._hasCheckedSegment, this.disabled);
10730
+ return template$13(this.button, this.href, this.target, this.rel, opensInNewTabLabel, actionTabindex, this._highlighted, this.checkbox, this.radio, this.checked, this._resolvedExpanded, this._showChildren, this._hasCheckedSegment, this.disabled);
10581
10731
  }
10582
10732
  }, _NLDDListItem = _NLDDListItem2, _NLDDListItem2.styles = [listItemStyles], _NLDDListItem2.SIZED_CELLS = "nldd-text-cell, nldd-drag-handle-cell", _NLDDListItem2);
10583
10733
  __decorate([property({
@@ -10600,6 +10750,10 @@ __decorate([property({
10600
10750
  type: Boolean,
10601
10751
  reflect: true
10602
10752
  })], NLDDListItem.prototype, "checkbox", void 0);
10753
+ __decorate([property({
10754
+ type: Boolean,
10755
+ reflect: true
10756
+ })], NLDDListItem.prototype, "radio", void 0);
10603
10757
  __decorate([property({
10604
10758
  type: Boolean,
10605
10759
  reflect: true
@@ -11426,16 +11580,16 @@ var nlddCodeMirrorTheme = [baseTheme, syntaxHighlighting(highlightStyle)];
11426
11580
  //#endregion
11427
11581
  //#region src/utilities/codemirror/languages.ts
11428
11582
  var loaders = {
11429
- json: async () => (await import("./dist-CRsR6ckU.js")).json(),
11430
- yaml: async () => (await import("./dist-la_b3ZSq.js")).yaml(),
11431
- javascript: async () => (await import("./dist-CjaG8dOI.js").then((n) => n.t)).javascript(),
11432
- typescript: async () => (await import("./dist-CjaG8dOI.js").then((n) => n.t)).javascript({ typescript: true }),
11433
- css: async () => (await import("./dist-C09E9-dJ.js").then((n) => n.r)).css(),
11434
- html: async () => (await import("./dist-762GHPyH.js").then((n) => n.t)).html(),
11435
- xml: async () => (await import("./dist-BJI4-6iQ.js")).xml(),
11436
- python: async () => (await import("./dist-BB7TxeMK.js")).python(),
11437
- rust: async () => (await import("./dist-DZoFKUO-.js")).rust(),
11438
- 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(),
11439
11593
  markdown: async () => (await Promise.resolve().then(() => dist_exports)).markdown(),
11440
11594
  bash: async () => new LanguageSupport(StreamLanguage.define((await import("./shell-C61GCsJA.js")).shell)),
11441
11595
  toml: async () => new LanguageSupport(StreamLanguage.define((await import("./toml-C2HBbPgG.js")).toml)),
@@ -12720,11 +12874,12 @@ var nlddRichTextTranslations = { "components.rich-text.table-scroll-label": "Scr
12720
12874
  * `overflow-wrap: break-word` safety net on p/li is always on, independent of
12721
12875
  * this attribute, so long URLs and compounds break neatly instead of
12722
12876
  * overflowing even without a dictionary.
12723
- * @attr {string} color - 'inherit' lets all text follow the color of the
12724
- * surface (for colored areas such as the filled categories). Links stay
12725
- * underlined as an affordance; secondary text (figcaption) gets the same
12726
- * color at a lowered opacity. Known v1 gaps: inline code, mark, tables and hr
12727
- * 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.
12728
12883
  * @attr {object} translations - Override translation keys; unset keys fall back to Dutch
12729
12884
  */
12730
12885
  var MANAGED_LABEL_ATTR = "data-nldd-managed-label";
@@ -12734,7 +12889,7 @@ var NLDDRichText = class NLDDRichText extends LitElement {
12734
12889
  this.spacing = "snug";
12735
12890
  this.centered = false;
12736
12891
  this.hyphens = false;
12737
- this.color = "";
12892
+ this.color = "content";
12738
12893
  this.translations = {};
12739
12894
  }
12740
12895
  _t(key) {
@@ -12793,7 +12948,7 @@ __decorate([property({
12793
12948
  })], NLDDRichText.prototype, "hyphens", void 0);
12794
12949
  __decorate([property({
12795
12950
  reflect: true,
12796
- converter: reflectNonDefault("")
12951
+ converter: reflectNonDefault("content")
12797
12952
  })], NLDDRichText.prototype, "color", void 0);
12798
12953
  __decorate([property({ type: Object })], NLDDRichText.prototype, "translations", void 0);
12799
12954
  NLDDRichText = __decorate([customElement("nldd-rich-text")], NLDDRichText);
@@ -13306,7 +13461,7 @@ function template$10() {
13306
13461
  * @attr {string} size - Text size on the body scale: 'xxs' | 'xs' | 'sm' | 'md' | 'lg' (default: 'md')
13307
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.
13308
13463
  * @attr {string} line-height - Line height: 'flat' | 'tight' | 'snug' | 'loose' (default: 'snug')
13309
- * @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.
13310
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.
13311
13466
  *
13312
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.
@@ -13318,7 +13473,7 @@ var NLDDText = (_NLDDText = class NLDDText extends LitElement {
13318
13473
  this.size = "md";
13319
13474
  this.weight = "regular";
13320
13475
  this.lineHeight = "snug";
13321
- this.color = "default";
13476
+ this.color = "content";
13322
13477
  this.horizontalAlignment = "left";
13323
13478
  }
13324
13479
  render() {
@@ -13340,7 +13495,7 @@ __decorate([property({
13340
13495
  })], NLDDText.prototype, "lineHeight", void 0);
13341
13496
  __decorate([property({
13342
13497
  reflect: true,
13343
- converter: reflectNonDefault("default")
13498
+ converter: reflectNonDefault("content")
13344
13499
  })], NLDDText.prototype, "color", void 0);
13345
13500
  __decorate([property({
13346
13501
  reflect: true,
@@ -13629,10 +13784,10 @@ function titleTemplate() {
13629
13784
  * @element nldd-title
13630
13785
  *
13631
13786
  * @attr {number} size - Visual size of the title: 1–6 (default: 3)
13632
- * @attr {string} color - 'inherit' lets the title follow the text color of the
13633
- * surface (for colored areas such as the filled categories); overline and
13634
- * subtitle get the same color at a lowered opacity. Empty = the default
13635
- * 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.
13636
13791
  *
13637
13792
  * @slot overline - Optional overline above the title
13638
13793
  * @slot - Title text (use h1–h6 for semantics)
@@ -13644,7 +13799,7 @@ var NLDDTitle = (_NLDDTitle = class NLDDTitle extends LitElement {
13644
13799
  constructor(..._args) {
13645
13800
  super(..._args);
13646
13801
  this.size = 3;
13647
- this.color = "";
13802
+ this.color = "content";
13648
13803
  }
13649
13804
  render() {
13650
13805
  return titleTemplate();
@@ -13656,7 +13811,7 @@ __decorate([property({
13656
13811
  })], NLDDTitle.prototype, "size", void 0);
13657
13812
  __decorate([property({
13658
13813
  reflect: true,
13659
- converter: reflectNonDefault("")
13814
+ converter: reflectNonDefault("content")
13660
13815
  })], NLDDTitle.prototype, "color", void 0);
13661
13816
  NLDDTitle = __decorate([customElement("nldd-title")], NLDDTitle);
13662
13817
  //#endregion
@@ -17720,7 +17875,7 @@ var codeEditorStyles = css`
17720
17875
  /* # Host */
17721
17876
 
17722
17877
  :host {
17723
- --__caret-width: var(--primitives-border-width-regular);
17878
+ --_caret-width: var(--primitives-border-width-regular);
17724
17879
  --_corner-radius: 0;
17725
17880
  --_background-color: transparent;
17726
17881
  --_border-color: transparent;
@@ -17822,7 +17977,7 @@ var codeEditorStyles = css`
17822
17977
  CodeMirror's theme cursor color without depending on an attribute. */
17823
17978
  :host .cm-cursor.cm-cursor {
17824
17979
  border-left-color: var(--primitives-color-accent-600);
17825
- border-left-width: var(--__caret-width);
17980
+ border-left-width: var(--_caret-width);
17826
17981
  }
17827
17982
 
17828
17983
  /* Resize model — rows is the floor in every mode:
@@ -28554,8 +28709,65 @@ var pasteURLAsLink = /*@__PURE__*/ EditorView.domEventHandlers({ paste: (event,
28554
28709
  return true;
28555
28710
  } });
28556
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
28557
28725
  //#region src/components/inputs/text-editor/text-editor.mentions.ts
28558
- 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
+ }
28559
28771
  /** Href prefix that marks a markdown link as a mention. */
28560
28772
  var MENTION_HREF_PREFIX = "user:";
28561
28773
  /** The markdown-compatible token stored for a mention (degrades to a plain link).
@@ -28565,7 +28777,11 @@ var MENTION_HREF_PREFIX = "user:";
28565
28777
  * `]` or `)` would otherwise start a second, attacker-shaped link. The render
28566
28778
  * layer reverses both via `unescapeMentionLabel` / `decodeMentionId`. */
28567
28779
  function mentionToken(candidate) {
28568
- 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)} `;
28569
28785
  }
28570
28786
  /** Reverse `mentionToken`'s label escaping for display. */
28571
28787
  function unescapeMentionLabel(label) {
@@ -28580,38 +28796,86 @@ function decodeMentionId(id) {
28580
28796
  return id;
28581
28797
  }
28582
28798
  }
28583
- 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) {
28584
28839
  return async (context) => {
28585
- const match = context.matchBefore(/@[\w.-]*/);
28586
- if (!match || match.from === match.to && !context.explicit) return null;
28587
- const source = getSource();
28588
- if (!source) return null;
28589
- const candidates = await source(match.text.slice(1));
28590
- if (!candidates?.length) return null;
28591
- return {
28592
- from: match.from,
28593
- filter: false,
28594
- options: candidates.map((candidate) => ({
28595
- label: `@${candidate.label}`,
28596
- 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,
28597
28853
  apply: (view, _completion, from, to) => {
28598
- const token = mentionToken(candidate);
28854
+ const text = insert(candidate);
28599
28855
  view.dispatch({
28600
28856
  changes: {
28601
28857
  from,
28602
28858
  to,
28603
- insert: `${token} `
28859
+ insert: text
28604
28860
  },
28605
- selection: { anchor: from + token.length + 1 }
28861
+ selection: { anchor: from + text.length },
28862
+ userEvent: "input.complete"
28606
28863
  });
28607
- onInsert({
28608
- id: candidate.id,
28609
- label: candidate.label,
28864
+ onChoose({
28865
+ typeahead,
28866
+ trigger: match.trigger,
28867
+ candidate,
28610
28868
  from,
28611
- to: from + token.length
28869
+ to: from + text.length
28612
28870
  });
28613
28871
  }
28614
- }))
28872
+ });
28873
+ });
28874
+ if (!options.length) return null;
28875
+ return {
28876
+ from: match.from,
28877
+ filter: false,
28878
+ options
28615
28879
  };
28616
28880
  };
28617
28881
  }
@@ -28625,6 +28889,8 @@ var popupTheme = EditorView.theme({
28625
28889
  ".cm-tooltip.cm-tooltip-autocomplete > ul": {
28626
28890
  fontFamily: "var(--primitives-font-family-body)",
28627
28891
  fontSize: "var(--primitives-font-size-100)",
28892
+ minWidth: "var(--primitives-area-280)",
28893
+ boxSizing: "border-box",
28628
28894
  maxHeight: "14em",
28629
28895
  margin: "0",
28630
28896
  padding: "var(--primitives-space-8)"
@@ -28640,6 +28906,32 @@ var popupTheme = EditorView.theme({
28640
28906
  color: "var(--semantics-content-color)",
28641
28907
  cursor: "default"
28642
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
+ },
28643
28935
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
28644
28936
  backgroundColor: "var(--components-menu-item-is-highlighted-background-color)",
28645
28937
  color: "var(--components-menu-item-is-highlighted-content-color)"
@@ -28657,22 +28949,26 @@ var popupTheme = EditorView.theme({
28657
28949
  "@media (pointer: coarse)": { ".cm-tooltip.cm-tooltip-autocomplete > ul > li": { minHeight: "var(--semantics-controls-md-min-size)" } }
28658
28950
  });
28659
28951
  /**
28660
- * @-mention typeahead. `getSource` is called with the text typed after `@` and
28661
- * returns (a promise of) candidates; selecting one inserts the token and calls
28662
- * `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.
28663
28955
  */
28664
- var reopenOnDelete = EditorView.updateListener.of((update) => {
28665
- if (!update.docChanged || completionStatus(update.state) !== null) return;
28666
- if (!update.transactions.some((tr) => tr.isUserEvent("delete"))) return;
28667
- const { head } = update.state.selection.main;
28668
- const before = update.state.sliceDoc(update.state.doc.lineAt(head).from, head);
28669
- if (MENTION_QUERY.test(before)) startCompletion(update.view);
28670
- });
28671
- 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
+ });
28672
28963
  return [
28673
28964
  autocompletion({
28674
- override: [completionSource(getSource, onInsert)],
28675
- icons: false
28965
+ override: [completionSource(getTypeaheads, onChoose)],
28966
+ icons: false,
28967
+ addToOptions: [{
28968
+ position: 20,
28969
+ render: renderLead
28970
+ }],
28971
+ optionClass: rowClass
28676
28972
  }),
28677
28973
  reopenOnDelete,
28678
28974
  popupTheme
@@ -28731,15 +29027,22 @@ var LinkOpenWidget = class extends WidgetType {
28731
29027
  ignoreEvent() {
28732
29028
  return true;
28733
29029
  }
28734
- /** Keep the caret at the badge's right edge text-height, not the badge's own box
28735
- * height (which is taller and would flip with the cursor's arrival direction). */
28736
- 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) {
28737
29039
  const badge = dom.getBoundingClientRect();
28738
29040
  const box = textCaretBox(dom) ?? {
28739
29041
  top: badge.top,
28740
29042
  bottom: badge.bottom
28741
29043
  };
28742
- 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);
28743
29046
  return {
28744
29047
  left: x,
28745
29048
  right: x,
@@ -28859,6 +29162,243 @@ function linkOpenBadge(label) {
28859
29162
  }
28860
29163
  }, { decorations: (plugin) => plugin.decorations });
28861
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
+ })];
28862
29402
  //#endregion
28863
29403
  //#region src/components/inputs/text-editor/text-editor.markdown.ts
28864
29404
  var NODE_CLASS = {
@@ -28991,6 +29531,12 @@ function buildMarkDecorations(view) {
28991
29531
  else if (MARK_NODES.has(node.name)) ranges.push(dimDeco.range(node.from, node.to));
28992
29532
  }
28993
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
+ }
28994
29540
  return Decoration.set(ranges, true);
28995
29541
  }
28996
29542
  var markDecorationPlugin = ViewPlugin.fromClass(class {
@@ -29009,20 +29555,22 @@ function hangingLineDeco(length) {
29009
29555
  }
29010
29556
  /** Whether `pos` sits inside a fenced or indented code block — where a leading
29011
29557
  * `-`/`1.`/`>` is literal code, not a list or quote marker. */
29558
+ var CODE_BLOCK_NODES = /* @__PURE__ */ new Set(["FencedCode", "CodeBlock"]);
29012
29559
  function inCodeBlock(state, pos) {
29013
- for (let n = syntaxTree(state).resolveInner(pos, 1); n; n = n.parent) if (n.name === "FencedCode" || n.name === "CodeBlock") return true;
29014
- return false;
29560
+ return enclosingNamed(state, pos, 1, CODE_BLOCK_NODES) !== null;
29015
29561
  }
29016
29562
  function buildHangingIndent(state) {
29017
29563
  const ranges = [];
29018
- for (let i = 1; i <= state.doc.lines; i++) {
29019
- const line = state.doc.line(i);
29020
- const match = line.text.match(HANGING_RE);
29021
- 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)) {
29022
29569
  const length = match[1].length;
29023
- ranges.push(hangingLineDeco(length).range(line.from));
29024
- ranges.push(prefixMonoDeco.range(line.from, line.from + length));
29570
+ ranges.push(hangingLineDeco(length).range(from));
29571
+ ranges.push(prefixMonoDeco.range(from, from + length));
29025
29572
  }
29573
+ from += text.length + 1;
29026
29574
  }
29027
29575
  return Decoration.set(ranges, true);
29028
29576
  }
@@ -29077,9 +29625,28 @@ function buildMentionChips(state) {
29077
29625
  } });
29078
29626
  return Decoration.set(ranges, true);
29079
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
+ }
29080
29643
  var mentionChipField = StateField.define({
29081
29644
  create: (state) => buildMentionChips(state),
29082
- 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
+ },
29083
29650
  provide: (field) => EditorView.decorations.from(field)
29084
29651
  });
29085
29652
  var mentionAtomicRanges = EditorView.atomicRanges.of((view) => view.state.field(mentionChipField, false) ?? Decoration.none);
@@ -29087,21 +29654,21 @@ var mentionAtomicRanges = EditorView.atomicRanges.of((view) => view.state.field(
29087
29654
  var markdownEditing = [
29088
29655
  markdown({ extensions: GFM }),
29089
29656
  markDecorationPlugin,
29657
+ heldEmphasis,
29090
29658
  hangingIndentField,
29091
29659
  Prec.highest(mentionChipField),
29092
29660
  mentionAtomicRanges
29093
29661
  ];
29094
29662
  function mentionLinkAt(state, pos, side) {
29095
- for (let node = syntaxTree(state).resolveInner(pos, side); node; node = node.parent) {
29096
- if (node.name !== "Link") continue;
29663
+ return enclosingNode(state, pos, side, (node) => {
29664
+ if (node.name !== "Link") return false;
29097
29665
  let url = null;
29098
29666
  for (let child = node.firstChild; child; child = child.nextSibling) if (child.name === "URL") {
29099
29667
  url = child;
29100
29668
  break;
29101
29669
  }
29102
- if (url && state.sliceDoc(url.from, url.to).startsWith("user:")) return node;
29103
- }
29104
- return null;
29670
+ return url !== null && state.sliceDoc(url.from, url.to).startsWith("user:");
29671
+ });
29105
29672
  }
29106
29673
  /** The mention whose collapsed token contains `pos` (used to select it on click). */
29107
29674
  function mentionRangeAt(state, pos) {
@@ -29172,54 +29739,40 @@ function desiredSentinels(groups, cleanText, canPlace = canPlaceSentinel) {
29172
29739
  }
29173
29740
  return out;
29174
29741
  }
29175
- /** Build the desired doc text: `cleanText` with a sentinel inserted at each desired
29176
- * position (multiplicity honoured for touching groups). */
29177
- function buildDesiredDoc(cleanText, desired) {
29742
+ /** How many sentinels each clean position wants (touching groups want two). */
29743
+ function desiredCounts(desired) {
29178
29744
  const counts = /* @__PURE__ */ new Map();
29179
29745
  for (const d of desired) counts.set(d.pos, (counts.get(d.pos) ?? 0) + 1);
29180
- let out = "";
29181
- for (let i = 0; i <= cleanText.length; i++) {
29182
- const n = counts.get(i) ?? 0;
29183
- if (n) out += "⁠".repeat(n);
29184
- if (i < cleanText.length) out += cleanText[i];
29185
- }
29186
- return out;
29746
+ return counts;
29187
29747
  }
29188
29748
  /** Minimal changes (in current-doc coords) to turn `doc` into a document that holds
29189
29749
  * exactly the sentinels the groups want — touching only sentinel characters, never
29190
- * the real text. `doc` and the target share the same clean text, so a two-pointer
29191
- * 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. */
29192
29753
  function reconcileSentinels(doc, groups, canPlace = canPlaceSentinel) {
29193
- const cleanText = stripSentinels(doc);
29194
- const desired = buildDesiredDoc(cleanText, desiredSentinels(groups, cleanText, canPlace));
29754
+ const want = desiredCounts(desiredSentinels(groups, groups.length ? stripSentinels(doc) : "", canPlace));
29195
29755
  const changes = [];
29196
- let i = 0;
29197
- let j = 0;
29198
- while (i < doc.length || j < desired.length) {
29199
- const dc = i < doc.length ? doc[i] : "";
29200
- const ec = j < desired.length ? desired[j] : "";
29201
- if (dc === ec) {
29202
- i++;
29203
- j++;
29204
- } else if (dc === "⁠") {
29205
- changes.push({
29206
- from: i,
29207
- to: i + 1
29208
- });
29209
- i++;
29210
- } else if (ec === "⁠") {
29211
- changes.push({
29212
- from: i,
29213
- to: i,
29214
- insert: "⁠"
29215
- });
29216
- j++;
29217
- } else {
29218
- i++;
29219
- j++;
29220
- }
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++;
29221
29775
  }
29222
- return changes;
29223
29776
  }
29224
29777
  var LINE_START_MARKER = /^(\s*)(#{1,6}\s|[-*+]\s|\d+[.)]\s|>|`{3,}|~{3,})/;
29225
29778
  /** Default placement guard. Blocks the sentinel where U+2060 would break the
@@ -29227,13 +29780,13 @@ var LINE_START_MARKER = /^(\s*)(#{1,6}\s|[-*+]\s|\d+[.)]\s|>|`{3,}|~{3,})/;
29227
29780
  * strikethrough run (`~~...~~`). Blocked edges fall back to a single caret stop. */
29228
29781
  function canPlaceSentinel(cleanText, pos, _kind) {
29229
29782
  const lineStart = cleanText.lastIndexOf("\n", pos - 1) + 1;
29230
- 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);
29231
29786
  if (marker) {
29232
29787
  if (pos < lineStart + marker[0].length) return false;
29233
29788
  }
29234
- const lineText = cleanText.slice(lineStart, cleanText.indexOf("\n", pos) === -1 ? void 0 : cleanText.indexOf("\n", pos));
29235
- const rel = pos - lineStart;
29236
- 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;
29237
29790
  return true;
29238
29791
  }
29239
29792
  //#endregion
@@ -29330,7 +29883,10 @@ var AnnotationBadge = class extends WidgetType {
29330
29883
  top: badge.top,
29331
29884
  bottom: badge.bottom
29332
29885
  };
29333
- 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);
29334
29890
  return {
29335
29891
  left: x,
29336
29892
  right: x,
@@ -29394,7 +29950,12 @@ function resolveGroup(group, doc, sents) {
29394
29950
  endSent: textTo < doc.length && doc[textTo] === "⁠" ? textTo : null
29395
29951
  };
29396
29952
  }
29953
+ var NOTHING = {
29954
+ deco: Decoration.none,
29955
+ atomic: Decoration.none
29956
+ };
29397
29957
  function buildAll(state, label) {
29958
+ if (!state.field(annotationField).length) return NOTHING;
29398
29959
  const doc = state.doc.toString();
29399
29960
  const sel = state.selection.main;
29400
29961
  const sents = sentinelPositions(doc);
@@ -29446,6 +30007,7 @@ function makeAnnotationRender(label) {
29446
30007
  return StateField.define({
29447
30008
  create: (state) => buildAll(state, label),
29448
30009
  update: (value, tr) => {
30010
+ if (value === NOTHING && !tr.state.field(annotationField).length) return value;
29449
30011
  if (tr.docChanged || !tr.startState.selection.eq(tr.state.selection) || tr.effects.some((e) => e.is(setAnnotations))) return buildAll(tr.state, label);
29450
30012
  return value;
29451
30013
  },
@@ -29458,6 +30020,7 @@ function makeAnnotationRender(label) {
29458
30020
  var annotationSentinelFilter = EditorState.transactionFilter.of((tr) => {
29459
30021
  const set = tr.effects.find((e) => e.is(setAnnotations));
29460
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;
29461
30024
  const postUserDoc = tr.newDoc.toString();
29462
30025
  const cleanLen = stripSentinels(postUserDoc).length;
29463
30026
  let anns;
@@ -29584,8 +30147,26 @@ function renumberOrderedLists(doc) {
29584
30147
  }
29585
30148
  /** Renumbers ordered lists in the same transaction as the edit that disturbed them,
29586
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
+ }
29587
30168
  var orderedListRenumber = EditorState.transactionFilter.of((tr) => {
29588
- if (!tr.docChanged) return tr;
30169
+ if (!tr.docChanged || !mayRenumber(tr)) return tr;
29589
30170
  const changes = renumberOrderedLists(tr.newDoc.toString());
29590
30171
  if (changes.length === 0) return tr;
29591
30172
  return [tr, {
@@ -29807,6 +30388,205 @@ var dragToMove = [
29807
30388
  dragMovePlugin
29808
30389
  ];
29809
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
29810
30590
  //#region src/components/inputs/text-editor/text-editor.commands.ts
29811
30591
  var EMPTY_FORMATS = {
29812
30592
  bold: false,
@@ -29817,12 +30597,12 @@ var EMPTY_FORMATS = {
29817
30597
  link: false,
29818
30598
  bulletList: false,
29819
30599
  orderedList: false,
30600
+ taskList: false,
29820
30601
  quote: false,
29821
30602
  heading: 0
29822
30603
  };
29823
30604
  function enclosing(view, pos, nodeName, side = 1) {
29824
- for (let n = syntaxTree(view.state).resolveInner(pos, side); n; n = n.parent) if (n.name === nodeName) return n;
29825
- return null;
30605
+ return enclosingNamed(view.state, pos, side, nodeName);
29826
30606
  }
29827
30607
  /** An inline format is "active" for the caret only when the node encloses it on
29828
30608
  * *both* sides. At the outer edge (just before the opening marker or just after
@@ -29833,31 +30613,67 @@ function enclosingInline(view, pos, nodeName) {
29833
30613
  const right = enclosing(view, pos, nodeName, 1);
29834
30614
  return left && right && left.from === right.from && left.to === right.to ? right : null;
29835
30615
  }
29836
- /** 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. */
29837
30620
  function toggleInlineWrap(view, marker, nodeName) {
29838
30621
  if (view.state.readOnly) return;
30622
+ const { state } = view;
29839
30623
  const len = marker.length;
29840
- view.dispatch(view.state.changeByRange((range) => {
29841
- const wrap = enclosing(view, range.from, nodeName);
29842
- if (wrap) return {
29843
- 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([{
29844
30651
  from: wrap.from,
29845
30652
  to: wrap.from + len
29846
30653
  }, {
29847
30654
  from: wrap.to - len,
29848
30655
  to: wrap.to
29849
- }],
29850
- range: EditorSelection.range(Math.max(wrap.from, range.from - len), Math.max(wrap.from, range.to - len))
29851
- };
30656
+ }]);
30657
+ return {
30658
+ changes,
30659
+ range: EditorSelection.range(changes.mapPos(range.anchor), changes.mapPos(range.head))
30660
+ };
30661
+ }
29852
30662
  return {
29853
30663
  changes: [{
29854
- from: range.from,
30664
+ from,
29855
30665
  insert: marker
29856
30666
  }, {
29857
- from: range.to,
30667
+ from: to,
29858
30668
  insert: marker
29859
30669
  }],
29860
- 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
29861
30677
  };
29862
30678
  }));
29863
30679
  view.focus();
@@ -29879,20 +30695,51 @@ function minimalLineChange(line, next) {
29879
30695
  insert: next.slice(p, next.length - s)
29880
30696
  };
29881
30697
  }
29882
- function mapSelectedLines(view, transform) {
29883
- 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) {
29884
30722
  const { state } = view;
29885
30723
  const { from, to } = state.selection.main;
29886
30724
  const first = state.doc.lineAt(from).number;
29887
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);
29888
30736
  const changes = [];
29889
30737
  for (let i = first; i <= last; i++) {
29890
30738
  const line = state.doc.line(i);
29891
30739
  const change = minimalLineChange(line, transform(line.text));
29892
30740
  if (change) changes.push(change);
29893
30741
  }
29894
- if (changes.length) view.dispatch({ changes });
29895
- view.focus();
30742
+ dispatchLineChanges(view, changes);
29896
30743
  }
29897
30744
  function everySelectedLine(view, predicate) {
29898
30745
  const { state } = view;
@@ -29922,39 +30769,56 @@ function setHeading(view, level) {
29922
30769
  }
29923
30770
  function toggleBulletList(view) {
29924
30771
  const re = /^(\s*)[-*+]\s+/;
29925
- 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() === "");
29926
30774
  mapSelectedLines(view, (t) => {
29927
- if (t.trim() === "") return t;
30775
+ if (t.trim() === "" && !loneBlank) return t;
29928
30776
  if (allBulleted) return t.replace(re, "$1");
29929
30777
  return re.test(t) ? t : t.replace(/^(\s*)/, "$1- ");
29930
30778
  });
29931
30779
  }
29932
- 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+/;
29933
30798
  /** Set the selected lines to a list of `type`, replacing any existing list
29934
30799
  * marker; `'none'` strips it. Ordered items are numbered within the selection.
29935
30800
  * Unlike the toggles, this cleanly switches between list types (for a picker). */
29936
30801
  function setList(view, type) {
29937
30802
  if (view.state.readOnly) return;
29938
30803
  const { state } = view;
29939
- const { from, to } = state.selection.main;
29940
- const first = state.doc.lineAt(from).number;
29941
- const last = state.doc.lineAt(to).number;
30804
+ const { first, last, loneBlank } = selectedLines(view);
29942
30805
  const changes = [];
29943
30806
  let number = 0;
29944
30807
  for (let i = first; i <= last; i++) {
29945
30808
  const line = state.doc.line(i);
29946
- 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;
29947
30812
  let next = stripped;
29948
- if (type !== "none" && stripped.trim() !== "") {
30813
+ if (type !== "none" && (stripped.trim() !== "" || wasItem || loneBlank)) {
29949
30814
  number += 1;
29950
- const marker = type === "bullet" ? "- " : `${number}. `;
30815
+ const marker = type === "bullet" ? "- " : type === "task" ? "- [ ] " : `${number}. `;
29951
30816
  next = stripped.replace(/^(\s*)/, `$1${marker}`);
29952
30817
  }
29953
30818
  const change = minimalLineChange(line, next);
29954
30819
  if (change) changes.push(change);
29955
30820
  }
29956
- if (changes.length) view.dispatch({ changes });
29957
- view.focus();
30821
+ dispatchLineChanges(view, changes);
29958
30822
  }
29959
30823
  /** Nest the selected list item(s) under the nearest preceding item at the same
29960
30824
  * level, indenting only as far as that parent's content column. That keeps the
@@ -30156,7 +31020,8 @@ function toggleCodeBlock(view) {
30156
31020
  function readActiveFormats(view) {
30157
31021
  const { state } = view;
30158
31022
  const pos = state.selection.main.head;
30159
- 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;
30160
31025
  const lineText = state.doc.lineAt(pos).text;
30161
31026
  const lineStart = state.doc.lineAt(pos).from;
30162
31027
  const headingMatch = lineText.match(/^(#{1,6})\s/);
@@ -30170,6 +31035,7 @@ function readActiveFormats(view) {
30170
31035
  link: realLinkAt(view, pos) !== null,
30171
31036
  bulletList: !codeBlock && /^\s*[-*+]\s/.test(lineText),
30172
31037
  orderedList: !codeBlock && /^\s*\d+[.)]\s/.test(lineText),
31038
+ taskList: !codeBlock && TASK_RE.test(lineText),
30173
31039
  quote: enclosing(view, lineStart, "Blockquote") !== null,
30174
31040
  heading: codeBlock || !headingMatch ? 0 : headingMatch[1].length
30175
31041
  };
@@ -30182,13 +31048,16 @@ var textEditorStyles = css`
30182
31048
  /* # Host */
30183
31049
 
30184
31050
  :host {
30185
- --__caret-width: var(--primitives-border-width-regular);
31051
+ --_caret-width: var(--primitives-border-width-regular);
31052
+ --_caret-ring-width: var(--primitives-border-width-thin);
30186
31053
  --_corner-radius: 0;
30187
31054
  --_background-color: transparent;
30188
31055
  --_highlight-border-color: transparent;
30189
31056
  --_highlight-border-shadow: none;
30190
31057
  --_padding-block: 0px;
30191
- --_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));
30192
31061
  --_content-color: var(--semantics-content-color);
30193
31062
  --_text-font: var(--primitives-font-body-md-regular-snug);
30194
31063
  --_code-font: var(--primitives-font-monospace-md-regular-snug);
@@ -30325,16 +31194,14 @@ var textEditorStyles = css`
30325
31194
  surfaces in light and dark (accent-600 fell just short on white), so the
30326
31195
  caret stays clearly visible without washing out. */
30327
31196
  border-left-color: var(--primitives-color-accent-700);
30328
- border-left-width: var(--__caret-width);
30329
- /* 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
30330
31202
  even over a tinted token (annotation, inline code) where the accent alone can
30331
31203
  blend in. Blinks with the caret (opacity covers the shadow too). */
30332
- box-shadow: 0 0 0 var(--primitives-border-width-thin) var(--semantics-surfaces-base-background-color);
30333
- /* CodeMirror draws the caret ~0.6px left of the text position. At a line start
30334
- with no inline padding (the simple variant) that overhangs the scroller's
30335
- overflow edge, so the left of the 2px gets clipped and the caret looks thinner
30336
- there than mid-line. Nudge it right so the full width stays inside the content. */
30337
- transform: translateX(0.6px);
31204
+ box-shadow: 0 0 0 var(--_caret-ring-width) var(--semantics-surfaces-base-background-color);
30338
31205
  }
30339
31206
 
30340
31207
  :host .cm-scroller {
@@ -30798,12 +31665,20 @@ var nlddTextEditorTranslations = {
30798
31665
  *
30799
31666
  * Headless: there is no built-in toolbar. A consumer drives formatting via the
30800
31667
  * command methods (toggleBold/toggleItalic/toggleInlineCode/toggleStrikethrough/
30801
- * toggleHeading/toggleBulletList/toggleQuote/toggleLink/runCommand to toggle, and
31668
+ * toggleHeading/toggleBulletList/toggleTaskList/toggleQuote/toggleLink/runCommand
31669
+ * to toggle, and
30802
31670
  * setHeading/setList for picker-style "set" semantics), reads the active formats
30803
31671
  * with getState(), listens to the nldd-text-editor-state event to render toggle
30804
31672
  * states, and forwards padding clicks with focusFromPoint(). Cmd/Ctrl+B/I/E/K are
30805
- * bound out of the box. Commands keep focus on the editor. An @-mention typeahead
30806
- * (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
30807
31682
  * (annotations) marks ranges with a dashed underline, light tint and a count badge
30808
31683
  * without touching the underlying text.
30809
31684
  *
@@ -30823,6 +31698,7 @@ var nlddTextEditorTranslations = {
30823
31698
  * @attr {string} accessible-label - Accessible label forwarded to the editor. Set automatically by nldd-form-field.
30824
31699
  *
30825
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.
30826
31702
  * @attr {boolean} annotatable - Enable the annotation overlay (off by default). Annotations only render when this is set.
30827
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]`.
30828
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.
@@ -30831,7 +31707,8 @@ var nlddTextEditorTranslations = {
30831
31707
  * @fires input - When the content changes (detail: { value })
30832
31708
  * @fires change - When the content is committed on blur (detail: { value })
30833
31709
  * @fires nldd-text-editor-state - When the selection or content changes (detail: TextEditorState), for toolbar toggle state
30834
- * @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)
30835
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
30836
31713
  */
30837
31714
  var _NLDDTextEditor;
@@ -30853,6 +31730,12 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30853
31730
  this.resize = "auto";
30854
31731
  this.variant = "simple";
30855
31732
  this.accessibleLabel = "";
31733
+ this.typeaheads = [];
31734
+ this._mentionTypeahead = {
31735
+ trigger: "@",
31736
+ source: (query) => this.mentionSource?.(query) ?? [],
31737
+ insert: mentionInsert
31738
+ };
30856
31739
  this.annotatable = false;
30857
31740
  this.annotations = [];
30858
31741
  this.translations = {};
@@ -30863,6 +31746,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30863
31746
  this._historyCompartment = new Compartment();
30864
31747
  this.invalid = false;
30865
31748
  this._cutBuffer = null;
31749
+ this._lastState = null;
30866
31750
  this._onScrollerPointerDown = (event) => {
30867
31751
  if (event.target !== this.view?.scrollDOM) return;
30868
31752
  if (this.view.state.readOnly) return;
@@ -30915,7 +31799,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30915
31799
  return [
30916
31800
  nlddCodeMirrorTheme,
30917
31801
  markdownEditing,
30918
- mentions(() => this.mentionSource, (detail) => this._emitMention(detail)),
31802
+ typeaheads(() => this._typeaheadLists(), (choice) => this._onTypeaheadChosen(choice)),
30919
31803
  Prec.low(orderedListRenumber),
30920
31804
  annotations((count, quote) => this._t("components.text-editor.annotation-count-label", {
30921
31805
  count,
@@ -30925,6 +31809,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30925
31809
  Prec.highest(linkOpenBadge((url) => this._t("components.text-editor.open-in-new-tab-label", { url }))),
30926
31810
  this._historyCompartment.of(history()),
30927
31811
  drawSelection(),
31812
+ stableCursor,
30928
31813
  dropCursor(),
30929
31814
  dragToMove,
30930
31815
  Prec.highest(keymap.of([{
@@ -30968,6 +31853,20 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30968
31853
  key: "Delete",
30969
31854
  run: (view) => this._selectMentionBeforeDelete(view, 1)
30970
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
+ },
30971
31870
  ...defaultKeymap,
30972
31871
  ...historyKeymap
30973
31872
  ]),
@@ -30983,7 +31882,8 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
30983
31882
  focus: () => {
30984
31883
  this._valueAtFocus = this.value;
30985
31884
  },
30986
- blur: () => {
31885
+ blur: (_event, view) => {
31886
+ repairHeldEmphasis(view);
30987
31887
  if (this.value !== this._valueAtFocus) this._emitChange();
30988
31888
  },
30989
31889
  mousedown: (event, view) => {
@@ -31040,7 +31940,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31040
31940
  firstUpdated() {
31041
31941
  this._initialValue = this.value;
31042
31942
  this.style.setProperty("--_rows", String(this.rows));
31043
- this.mountEditor(this.value);
31943
+ this.mountEditor(stripSentinels(this.value));
31044
31944
  this.onEditorMounted();
31045
31945
  }
31046
31946
  onEditorMounted() {
@@ -31056,7 +31956,7 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31056
31956
  if (changed.has("rows")) this.style.setProperty("--_rows", String(this.rows));
31057
31957
  if (this.view) {
31058
31958
  if (changed.has("value")) {
31059
- if (stripSentinels(this.doc) !== this.value) this.setDoc(this.value);
31959
+ if (stripSentinels(this.doc) !== this.value) this.setDoc(stripSentinels(this.value));
31060
31960
  this.commitFormValue();
31061
31961
  }
31062
31962
  if (changed.has("disabled") || changed.has("readonly")) this.reconfigure(this._editableCompartment, this._editableExtension());
@@ -31107,8 +32007,14 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31107
32007
  toggleBulletList() {
31108
32008
  if (this.view) toggleBulletList(this.view);
31109
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
+ }
31110
32015
  /** Set the list type, replacing any existing list ('none' strips it) — for an
31111
- * 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. */
31112
32018
  setList(type) {
31113
32019
  if (this.view) setList(this.view, type);
31114
32020
  }
@@ -31217,6 +32123,45 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31217
32123
  this._pasteText(stripSentinels(raw));
31218
32124
  this.view?.focus();
31219
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
+ }
31220
32165
  /** Insert `text` at the selection, re-attaching the cut annotations when it matches
31221
32166
  * a cut made in this editor (a move, one-shot). Shared by the toolbar's paste()
31222
32167
  * and the native Cmd/Ctrl+V handler. */
@@ -31226,7 +32171,10 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31226
32171
  const sameText = (a, b) => a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
31227
32172
  const carry = !!(buffer && sameText(buffer.text, text) && buffer.anns.length > 0);
31228
32173
  const at = docToClean(this.view.state.doc.toString(), this.view.state.selection.main.from);
31229
- const spec = this.view.state.replaceSelection(text);
32174
+ const spec = {
32175
+ ...this.view.state.replaceSelection(text),
32176
+ userEvent: "input.paste"
32177
+ };
31230
32178
  this.view.dispatch(carry ? {
31231
32179
  ...spec,
31232
32180
  effects: pasteAnnotations.of({
@@ -31236,10 +32184,20 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31236
32184
  } : spec);
31237
32185
  if (carry) this._cutBuffer = null;
31238
32186
  }
31239
- /** Escape hatch: run a command by name (bold, italic, inlineCode,
31240
- * strikethrough, bulletList, quote, heading [payload: level], link
31241
- * [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. */
31242
32199
  runCommand(name, payload) {
32200
+ const level = (fallback) => typeof payload === "number" ? payload : fallback;
31243
32201
  switch (name) {
31244
32202
  case "bold":
31245
32203
  this.toggleBold();
@@ -31256,22 +32214,49 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31256
32214
  case "bulletList":
31257
32215
  this.toggleBulletList();
31258
32216
  break;
32217
+ case "taskList":
32218
+ this.toggleTaskList();
32219
+ break;
31259
32220
  case "quote":
31260
32221
  this.toggleQuote();
31261
32222
  break;
32223
+ case "codeBlock":
32224
+ this.toggleCodeBlock();
32225
+ break;
31262
32226
  case "heading":
31263
- 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");
31264
32234
  break;
31265
32235
  case "link":
31266
32236
  this.toggleLink(typeof payload === "string" ? payload : "");
31267
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;
31268
32250
  case "copy":
31269
32251
  this.copy();
31270
32252
  break;
31271
32253
  case "cut":
31272
32254
  this.cut();
31273
32255
  break;
31274
- 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.`);
31275
32260
  }
31276
32261
  }
31277
32262
  /** The formats active at the current selection (drives a toolbar's state). */
@@ -31337,8 +32322,43 @@ var NLDDTextEditor = (_NLDDTextEditor = class NLDDTextEditor extends DescribedBy
31337
32322
  }));
31338
32323
  }
31339
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;
31340
32329
  this.dispatchEvent(new CustomEvent("nldd-text-editor-state", {
31341
- 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
+ },
31342
32362
  bubbles: true,
31343
32363
  composed: true
31344
32364
  }));
@@ -31438,6 +32458,7 @@ __decorate([property({
31438
32458
  attribute: "accessible-label"
31439
32459
  })], NLDDTextEditor.prototype, "accessibleLabel", void 0);
31440
32460
  __decorate([property({ attribute: false })], NLDDTextEditor.prototype, "mentionSource", void 0);
32461
+ __decorate([property({ attribute: false })], NLDDTextEditor.prototype, "typeaheads", void 0);
31441
32462
  __decorate([property({
31442
32463
  type: Boolean,
31443
32464
  reflect: true
@@ -35580,7 +36601,7 @@ var barSplitViewStyles = css`
35580
36601
  /* Root-scroll mode: the document scrolls (ScrollModeController /
35581
36602
  --context-scroll-mode). Bars become sticky layers that stay put while the
35582
36603
  main content scrolls under them; their measured heights are published into
35583
- --context-layer-top/bottom (see bar-split-view.ts) so a descendant
36604
+ --context-inset-top/bottom (see bar-split-view.ts) so a descendant
35584
36605
  nldd-page's sticky header/footer stack below/above them. The sticky insets
35585
36606
  and those offsets are set by JS; bars paint above the content. */
35586
36607
  :host([data-scroll="root"]) {
@@ -35876,12 +36897,12 @@ var mdMaxPx = parseInt(breakpoints.mdMax);
35876
36897
  var NLDDBarSplitView = (_NLDDBarSplitView = class NLDDBarSplitView extends LitElement {
35877
36898
  constructor(..._args) {
35878
36899
  super(..._args);
35879
- this._scrollMode = new ScrollModeController(this, () => this._updateLayerOffsets());
36900
+ this._scrollMode = new ScrollModeController(this, () => this._updateInsets());
35880
36901
  this.background = "inherit";
35881
36902
  this._currentBreakpoint = null;
35882
36903
  this._observer = null;
35883
36904
  this._resizeObserver = null;
35884
- this._updateLayerOffsets = () => {
36905
+ this._updateInsets = () => {
35885
36906
  const block = this.shadowRoot?.querySelector(".bar-split-view") ?? null;
35886
36907
  const main = block?.querySelector(".bar-split-view__main") ?? null;
35887
36908
  if (!block || !main) return;
@@ -35892,25 +36913,25 @@ var NLDDBarSplitView = (_NLDDBarSplitView = class NLDDBarSplitView extends LitEl
35892
36913
  el.style.top = "";
35893
36914
  el.style.bottom = "";
35894
36915
  });
35895
- main.style.removeProperty("--context-layer-top");
35896
- main.style.removeProperty("--context-layer-bottom");
36916
+ main.style.removeProperty("--context-inset-top");
36917
+ main.style.removeProperty("--context-inset-bottom");
35897
36918
  return;
35898
36919
  }
35899
36920
  const cs = getComputedStyle(this);
35900
- let top = parseFloat(cs.getPropertyValue("--context-layer-top")) || 0;
36921
+ let top = parseFloat(cs.getPropertyValue("--context-inset-top")) || 0;
35901
36922
  for (let i = 0; i < mainIndex; i++) {
35902
36923
  children[i].style.top = `${top}px`;
35903
36924
  children[i].style.bottom = "";
35904
36925
  top += children[i].offsetHeight;
35905
36926
  }
35906
- main.style.setProperty("--context-layer-top", `${top}px`);
35907
- let bottom = parseFloat(cs.getPropertyValue("--context-layer-bottom")) || 0;
36927
+ main.style.setProperty("--context-inset-top", `${top}px`);
36928
+ let bottom = parseFloat(cs.getPropertyValue("--context-inset-bottom")) || 0;
35908
36929
  for (let i = children.length - 1; i > mainIndex; i--) {
35909
36930
  children[i].style.bottom = `${bottom}px`;
35910
36931
  children[i].style.top = "";
35911
36932
  bottom += children[i].offsetHeight;
35912
36933
  }
35913
- main.style.setProperty("--context-layer-bottom", `${bottom}px`);
36934
+ main.style.setProperty("--context-inset-bottom", `${bottom}px`);
35914
36935
  };
35915
36936
  }
35916
36937
  connectedCallback() {
@@ -35936,7 +36957,7 @@ var NLDDBarSplitView = (_NLDDBarSplitView = class NLDDBarSplitView extends LitEl
35936
36957
  this._currentBreakpoint = bp;
35937
36958
  this.requestUpdate();
35938
36959
  }
35939
- this._updateLayerOffsets();
36960
+ this._updateInsets();
35940
36961
  });
35941
36962
  this._resizeObserver.observe(this);
35942
36963
  }
@@ -35981,7 +37002,7 @@ var NLDDBarSplitView = (_NLDDBarSplitView = class NLDDBarSplitView extends LitEl
35981
37002
  });
35982
37003
  }
35983
37004
  updated() {
35984
- this._updateLayerOffsets();
37005
+ this._updateInsets();
35985
37006
  }
35986
37007
  render() {
35987
37008
  return barSplitViewTemplate(this);
@@ -37370,6 +38391,16 @@ var pageStyles = css`
37370
38391
 
37371
38392
  :host {
37372
38393
  --_background-color: var(--context-parent-background-color, var(--semantics-surfaces-base-background-color));
38394
+ /* The insets this page arrived with, parked before it adds its own:
38395
+ republishing --context-inset-top on the element it reads from would be
38396
+ a cycle. */
38397
+ --_outer-inset-top: var(--context-inset-top, 0px);
38398
+ --_outer-inset-bottom: var(--context-inset-bottom, 0px);
38399
+ --_header-height: 0px;
38400
+ --_footer-height: 0px;
38401
+ /* Set from JS while the page owns the scroller. initial, not a length, so
38402
+ the fallback stands when the document scrolls instead. */
38403
+ --_scroll-height: initial;
37373
38404
 
37374
38405
  display: flex;
37375
38406
  background-color: var(--_background-color);
@@ -37408,9 +38439,8 @@ var pageStyles = css`
37408
38439
 
37409
38440
  /* Root-scroll mode: the DOCUMENT scrolls (see nldd-app-view), not the page.
37410
38441
  The page stops owning an inner scroll container and its sticky-header/footer
37411
- become real position:sticky layers against the document, offset by the
37412
- cumulative layer heights above/below (--context-layer-top/bottom, published
37413
- by any bars sitting outside the page). The mode is derived upstream and
38442
+ stick against the document, offset by the insets above and below
38443
+ (--context-inset-top/bottom, published by any bars outside the page). The mode is derived upstream and
37414
38444
  delivered as --context-scroll-mode; nldd-page reflects it to [data-scroll]
37415
38445
  so these (higher-specificity, later) rules win over the nested ones. */
37416
38446
  :host([data-scroll="root"]) {
@@ -37446,7 +38476,7 @@ var pageStyles = css`
37446
38476
  :host([data-scroll="root"]) .page {
37447
38477
  overflow: visible;
37448
38478
  flex-shrink: 0;
37449
- min-height: calc(100dvh - var(--context-layer-top, 0px) - var(--context-layer-bottom, 0px));
38479
+ min-height: calc(100dvh - var(--context-inset-top, 0px) - var(--context-inset-bottom, 0px));
37450
38480
  }
37451
38481
 
37452
38482
 
@@ -37481,7 +38511,7 @@ var pageStyles = css`
37481
38511
  opacity: 0;
37482
38512
  background: linear-gradient(to bottom, color-mix(in srgb, var(--_background-color) 95%, transparent), transparent);
37483
38513
  pointer-events: none;
37484
- height: var(--primitives-space-32);
38514
+ height: var(--primitives-space-24);
37485
38515
  transition: opacity var(--primitives-transition-duration-medium) var(--primitives-transition-easing-default);
37486
38516
  }
37487
38517
 
@@ -37495,18 +38525,34 @@ var pageStyles = css`
37495
38525
  only mattered for the absolute overlay. */
37496
38526
  :host([data-scroll="root"][sticky-header]) .page__header {
37497
38527
  position: sticky;
37498
- top: var(--context-layer-top, 0px);
38528
+ top: var(--context-inset-top, 0px);
37499
38529
  left: auto;
37500
38530
  right: auto;
37501
38531
  }
37502
38532
 
38533
+ /* The page adds its own bars to the insets it inherited, so sticky content
38534
+ inside clears them without knowing a number. The header sits outside this
38535
+ element and keeps the value without itself in it. */
37503
38536
  .page__scroll {
38537
+ --context-inset-top: calc(var(--_outer-inset-top) + var(--_header-height));
38538
+ --context-inset-bottom: calc(var(--_outer-inset-bottom) + var(--_footer-height));
38539
+ /* The height sticky content can cap itself on. Unset while the document
38540
+ scrolls, where the viewport is the whole story. */
38541
+ --context-scroller-height: var(--_scroll-height, 100dvh);
38542
+
37504
38543
  display: flex;
37505
38544
  min-height: 0;
37506
38545
  flex-direction: column;
37507
38546
  flex-grow: 1;
37508
38547
  }
37509
38548
 
38549
+ /* Nested scrolling: a sticky child is measured against this element, so the
38550
+ bars above the page fall outside it and only its own header counts. */
38551
+ :host(:not([data-scroll="root"])) .page__scroll {
38552
+ --context-inset-top: var(--_header-height);
38553
+ --context-inset-bottom: var(--_footer-height);
38554
+ }
38555
+
37510
38556
  :host([sticky-header]) .page__scroll {
37511
38557
  overflow-y: auto;
37512
38558
  overflow-x: hidden;
@@ -37545,12 +38591,12 @@ var pageStyles = css`
37545
38591
  right: 0;
37546
38592
  background: linear-gradient(to top, color-mix(in srgb, var(--_background-color) 95%, transparent), transparent);
37547
38593
  pointer-events: none;
37548
- height: var(--primitives-space-32);
38594
+ height: var(--primitives-space-24);
37549
38595
  }
37550
38596
 
37551
38597
  /* Root-scroll mode: footer sticks to the document bottom, above any bottom bars. */
37552
38598
  :host([data-scroll="root"][sticky-footer]) .page__footer {
37553
- bottom: var(--context-layer-bottom, 0px);
38599
+ bottom: var(--context-inset-bottom, 0px);
37554
38600
  }
37555
38601
  `;
37556
38602
  //#endregion
@@ -37585,10 +38631,18 @@ function pageTemplate(component) {
37585
38631
  *
37586
38632
  * In root-scroll mode (--context-scroll-mode: root, derived upstream by
37587
38633
  * nldd-app-view) the page stops owning a scroller: the document scrolls and the
37588
- * sticky header/footer become position:sticky layers offset by
37589
- * --context-layer-top/bottom. The mode is read on connect/resize and reflected
38634
+ * sticky header/footer stick against the document, offset by
38635
+ * --context-inset-top/bottom. The mode is read on connect/resize and reflected
37590
38636
  * to [data-scroll] so the CSS can branch.
37591
38637
  *
38638
+ * The page passes those insets on to its own content, with its sticky header
38639
+ * and footer added: anything sticky inside (an nldd-sidebar-section, a sticky
38640
+ * table head) reads --context-inset-top / --context-inset-bottom and clears
38641
+ * every bar above and below it without being told a number. The heights are
38642
+ * measured, because a top title bar shrinks as you scroll past its anchor. While
38643
+ * the page owns the scroller, only its own bars count: the chrome above the page
38644
+ * sits outside that scroller and does not push sticky content down.
38645
+ *
37592
38646
  * @element nldd-page
37593
38647
  *
37594
38648
  * @attr {boolean} sticky-header - Sticky header
@@ -37612,6 +38666,7 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37612
38666
  this._scrollTarget = null;
37613
38667
  this._scrollProvider = null;
37614
38668
  this._headerObserver = null;
38669
+ this._insetObserver = null;
37615
38670
  this._mainSlot = null;
37616
38671
  this._resizeRaf = 0;
37617
38672
  this._onResize = () => {
@@ -37660,6 +38715,9 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37660
38715
  get _scrollEl() {
37661
38716
  return this.shadowRoot?.querySelector(".page__scroll") ?? null;
37662
38717
  }
38718
+ get _footerEl() {
38719
+ return this.shadowRoot?.querySelector(".page__footer") ?? null;
38720
+ }
37663
38721
  connectedCallback() {
37664
38722
  super.connectedCallback();
37665
38723
  this.style.containerType = "inline-size";
@@ -37684,6 +38742,7 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37684
38742
  if (this._resizeRaf) cancelAnimationFrame(this._resizeRaf);
37685
38743
  this._teardownScrollListener();
37686
38744
  this._teardownHeaderObserver();
38745
+ this._teardownInsetObserver();
37687
38746
  this._teardownMainSlotListener();
37688
38747
  }
37689
38748
  firstUpdated() {
@@ -37714,8 +38773,41 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
37714
38773
  if (this._scrollEl) this._scrollEl.style.paddingTop = "";
37715
38774
  this._setupScrollListener();
37716
38775
  if (this.stickyHeader && !this._isRoot) this._setupHeaderObserver();
38776
+ this._setupInsetObserver();
37717
38777
  this._onScroll();
37718
38778
  }
38779
+ /**
38780
+ * Publishes the page's own sticky bars as insets, so sticky content inside
38781
+ * the page knows how far to clear.
38782
+ *
38783
+ * Measured rather than assumed: a top title bar shrinks as you scroll past
38784
+ * its anchor, and a consumer that had written the number down would be wrong
38785
+ * from that moment on. A bar that is not sticky scrolls away and adds
38786
+ * nothing, so it stays at zero.
38787
+ */
38788
+ _setupInsetObserver() {
38789
+ this._teardownInsetObserver();
38790
+ const header = this._headerEl;
38791
+ const footer = this._footerEl;
38792
+ if (!header || !footer) return;
38793
+ const publish = () => {
38794
+ this.style.setProperty("--_header-height", `${this.stickyHeader ? header.offsetHeight : 0}px`);
38795
+ this.style.setProperty("--_footer-height", `${this.stickyFooter ? footer.offsetHeight : 0}px`);
38796
+ if (this._isRoot) this.style.removeProperty("--_scroll-height");
38797
+ else this.style.setProperty("--_scroll-height", `${this._scrollEl?.clientHeight ?? 0}px`);
38798
+ };
38799
+ publish();
38800
+ this._insetObserver = new ResizeObserver(publish);
38801
+ this._insetObserver.observe(header);
38802
+ this._insetObserver.observe(footer);
38803
+ if (this._scrollEl) this._insetObserver.observe(this._scrollEl);
38804
+ }
38805
+ _teardownInsetObserver() {
38806
+ if (this._insetObserver) {
38807
+ this._insetObserver.disconnect();
38808
+ this._insetObserver = null;
38809
+ }
38810
+ }
37719
38811
  _setupScrollListener() {
37720
38812
  const target = this.scrollEventTarget;
37721
38813
  target.addEventListener("scroll", this._onScroll, { passive: true });
@@ -39632,10 +40724,10 @@ var sidebarSectionStyles = css`
39632
40724
  --_lg-padding-top: initial;
39633
40725
  --_lg-padding-bottom: initial;
39634
40726
  --_max-width: var(--semantics-page-sections-body-max-width);
39635
- /* Sticky insets default to 16px top and bottom; override via sticky-top /
39636
- sticky-bottom to clear other sticky page elements (e.g. a sticky header). */
39637
- --_sticky-top: var(--primitives-space-16);
39638
- --_sticky-bottom: var(--primitives-space-16);
40727
+ /* 24px clear of the insets nldd-page publishes. 24 is the depth of a
40728
+ sticky header's fade, so the box starts where the fade ends. */
40729
+ --_sticky-top: calc(var(--context-inset-top, 0px) + var(--primitives-space-24));
40730
+ --_sticky-bottom: calc(var(--context-inset-bottom, 0px) + var(--primitives-space-24));
39639
40731
  --_sidebar-width: var(--primitives-area-320);
39640
40732
 
39641
40733
  display: flex;
@@ -39768,7 +40860,9 @@ var sidebarSectionStyles = css`
39768
40860
  position: sticky;
39769
40861
  top: var(--_sticky-top);
39770
40862
  bottom: var(--_sticky-bottom);
39771
- max-height: calc(100dvh - var(--_sticky-top) - var(--_sticky-bottom));
40863
+ /* What the scroller shows, not what the window is: a viewport-tall cap
40864
+ hangs out the bottom by the height of the chrome around the page. */
40865
+ max-height: calc(var(--context-scroller-height, 100dvh) - var(--_sticky-top) - var(--_sticky-bottom));
39772
40866
  overflow-y: auto;
39773
40867
  }
39774
40868
  }
@@ -39908,8 +41002,8 @@ var sheetStyles = css`
39908
41002
  sticky header sticks to that edge instead of to a bar that stands
39909
41003
  outside. See findScrollModeProvider for the other half. */
39910
41004
  --context-scroll-mode: nested;
39911
- --context-layer-top: 0px;
39912
- --context-layer-bottom: 0px;
41005
+ --context-inset-top: 0px;
41006
+ --context-inset-bottom: 0px;
39913
41007
 
39914
41008
  display: flex;
39915
41009
  position: fixed;
@@ -39973,10 +41067,6 @@ var sheetStyles = css`
39973
41067
  background: var(--semantics-overlays-backdrop-color);
39974
41068
  }
39975
41069
 
39976
- :host([modeless]) .sheet::backdrop {
39977
- background: transparent;
39978
- }
39979
-
39980
41070
  :host([placement="left"]) .sheet {
39981
41071
  @media (min-width: ${mdMin$4}) {
39982
41072
  inset: var(--semantics-overlays-inset) auto var(--semantics-overlays-inset) var(--semantics-overlays-inset);
@@ -40056,7 +41146,7 @@ function sheetTemplate(component) {
40056
41146
  return html`
40057
41147
  <dialog class="sheet"
40058
41148
  aria-label=${component.accessibleLabel}
40059
- aria-modal=${component.modeless ? nothing : "true"}
41149
+ aria-modal="true"
40060
41150
  @pointerdown=${component._handleDialogPointerDown}
40061
41151
  @click=${component._handleDialogClick}
40062
41152
  @cancel=${component._handleCancel}
@@ -40169,7 +41259,6 @@ function openWhenRendered(host, getDialog, open) {
40169
41259
  *
40170
41260
  * @attr {string} placement - Sheet position: 'left' | 'right' | 'bottom' (default: 'right')
40171
41261
  * @attr {string} height - Custom height for bottom sheets (and for any sheet on sm viewports, where all placements collapse to bottom). Accepts: `'full'` (default — viewport minus top-inset, identical to omitting the attribute), `'fit-content'` (collapse to content size), or any CSS length/percentage (e.g. `'50dvh'`, `'480px'`, `'50%'`). Always clamped to `100dvh - top-inset` so the sheet can't extend past the dismiss-tap area. No effect on side sheets at md+.
40172
- * @attr {boolean} modeless - Non-modal (no backdrop or focus lock); the sheet is modal by default
40173
41262
  * @attr {string} accessible-label - Accessible name for the dialog, forwarded as aria-label (default: 'Venster')
40174
41263
  * @attr {string} width - Custom width for side sheets (left/right) as a CSS length (e.g. '480px', '32rem'). Applied from the md breakpoint up; ignored on sm (bottom sheet) and for `placement="bottom"`. Clamped to `100vw - 2 * inset` so the sheet always fits.
40175
41264
  *
@@ -40192,7 +41281,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40192
41281
  super(..._args);
40193
41282
  this.placement = "right";
40194
41283
  this.height = "";
40195
- this.modeless = false;
40196
41284
  this.accessibleLabel = "Venster";
40197
41285
  this.width = "";
40198
41286
  this._hasWarnedLabel = false;
@@ -40243,8 +41331,7 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40243
41331
  this._cancelPendingOpen?.();
40244
41332
  this._cancelPendingOpen = null;
40245
41333
  this._closeEmitted = false;
40246
- if (this.modeless) dialog.show();
40247
- else dialog.showModal();
41334
+ dialog.showModal();
40248
41335
  this._manageFocus();
40249
41336
  this.dispatchEvent(new CustomEvent("open", {
40250
41337
  bubbles: true,
@@ -40283,7 +41370,6 @@ var NLDDSheet = (_NLDDSheet = class NLDDSheet extends LitElement {
40283
41370
  this._pointerDownOnBackdrop = e.target === this._dialog;
40284
41371
  }
40285
41372
  _handleDialogClick(e) {
40286
- if (this.modeless) return;
40287
41373
  if (e.target === this._dialog && this._pointerDownOnBackdrop) this.hide();
40288
41374
  }
40289
41375
  _emitClose() {
@@ -40316,10 +41402,6 @@ __decorate([property({
40316
41402
  type: String,
40317
41403
  reflect: true
40318
41404
  })], NLDDSheet.prototype, "height", void 0);
40319
- __decorate([property({
40320
- type: Boolean,
40321
- reflect: true
40322
- })], NLDDSheet.prototype, "modeless", void 0);
40323
41405
  __decorate([property({
40324
41406
  type: String,
40325
41407
  attribute: "accessible-label"
@@ -40922,29 +42004,21 @@ var collectionStyles = css`
40922
42004
  --_item-width: var(--primitives-area-280);
40923
42005
  --_focus-ring-z-index: 1;
40924
42006
 
40925
- @media (max-width: ${smMax$4}) {
40926
- --_gap: var(--components-collection-sm-gap);
40927
- }
40928
-
40929
- @media (min-width: ${mdMin$3}) and (max-width: ${mdMax$2}) {
40930
- --_gap: var(--components-collection-md-gap);
40931
- }
40932
-
40933
- @media (min-width: ${lgMin$2}) {
40934
- --_gap: var(--components-collection-lg-gap);
40935
- }
42007
+ /* Two sets, because a collection inside a layout-container follows that
42008
+ container and anywhere else the viewport. The bare --_gap is what
42009
+ stands when neither set matches. */
42010
+ --_sm-gap: var(--components-collection-sm-gap);
42011
+ --_md-gap: var(--components-collection-md-gap);
42012
+ --_lg-gap: var(--components-collection-lg-gap);
42013
+ --_gap: var(--_sm-gap);
40936
42014
 
40937
- @container layout-container (max-width: ${smMax$4}) {
40938
- --_gap: var(--components-collection-sm-gap);
40939
- }
40940
-
40941
- @container layout-container (min-width: ${mdMin$3}) and (max-width: ${mdMax$2}) {
40942
- --_gap: var(--components-collection-md-gap);
40943
- }
42015
+ @media (max-width: ${smMax$4}) { --_gap: var(--_sm-gap); }
42016
+ @media (min-width: ${mdMin$3}) and (max-width: ${mdMax$2}) { --_gap: var(--_md-gap); }
42017
+ @media (min-width: ${lgMin$2}) { --_gap: var(--_lg-gap); }
40944
42018
 
40945
- @container layout-container (min-width: ${lgMin$2}) {
40946
- --_gap: var(--components-collection-lg-gap);
40947
- }
42019
+ @container layout-container (max-width: ${smMax$4}) { --_gap: var(--_sm-gap); }
42020
+ @container layout-container (min-width: ${mdMin$3}) and (max-width: ${mdMax$2}) { --_gap: var(--_md-gap); }
42021
+ @container layout-container (min-width: ${lgMin$2}) { --_gap: var(--_lg-gap); }
40948
42022
 
40949
42023
  display: flex;
40950
42024
  width: 100%;
@@ -40991,6 +42065,27 @@ var collectionStyles = css`
40991
42065
  flex-direction: column;
40992
42066
  }
40993
42067
 
42068
+
42069
+ /* ## Lanes */
42070
+
42071
+ /* Falls back to the grid above, not to the multicol nldd-container uses:
42072
+ this component pages, and multicol redistributes the whole set every time
42073
+ load-more adds to it. */
42074
+ :host([layout="lanes"]) .collection__items {
42075
+ display: grid;
42076
+ grid-template-columns: repeat(auto-fill, minmax(min(var(--_item-width), 100%), 1fr));
42077
+ }
42078
+
42079
+ :host([layout="lanes"]) .collection__items ::slotted(*) {
42080
+ min-width: 0;
42081
+ }
42082
+
42083
+ @supports (display: grid-lanes) {
42084
+ :host([layout="lanes"]) .collection__items {
42085
+ display: grid-lanes;
42086
+ }
42087
+ }
42088
+
40994
42089
  /* ## Horizontal scroll */
40995
42090
 
40996
42091
  :host([layout="horizontal-scroll"]) .collection__items {
@@ -41141,7 +42236,7 @@ var nlddCollectionTranslations = {
41141
42236
  * Nederlandse Digitale Dienst Collection Component (Lit + TypeScript)
41142
42237
  *
41143
42238
  * A container for displaying collections of items.
41144
- * Supports grid, stack, and horizontal scroll layouts.
42239
+ * Supports grid, stack, lanes and horizontal scroll layouts.
41145
42240
  * In grid and stack modes, items are paginated via a load-more button. In
41146
42241
  * horizontal scroll, the prev/next controls and the edge fade appear only when
41147
42242
  * the items overflow the container.
@@ -41150,12 +42245,15 @@ var nlddCollectionTranslations = {
41150
42245
  *
41151
42246
  * @element nldd-collection
41152
42247
  *
41153
- * @attr {string} layout - Layout mode: 'grid' | 'stack' | 'horizontal-scroll' (default: 'grid')
42248
+ * @attr {string} layout - Layout mode: 'grid' | 'stack' | 'lanes' | 'horizontal-scroll' (default: 'grid')
41154
42249
  * @attr {boolean} show-load-more - Show load-more button in grid/stack (default: false)
41155
42250
  * @attr {number} max-items - Number of visible items per page (default: 24)
41156
42251
  * @attr {boolean} lazy-load - Automatically load more items when the button becomes visible
41157
- * @attr {string} item-width - Preferred width for each item (e.g. '280px', '20rem'). In grid layout used as the minimum column width (columns will be at least this wide; 1fr if container allows more). In horizontal scroll used as flex-basis. Never forces horizontal overflow — the value is clamped to container width.
41158
- * @attr {string} gap - Custom gap between items (any CSS length, e.g. '8px'). Overrides the responsive default at every breakpoint; unset keeps the default.
42252
+ * @attr {string} item-width - Preferred width for each item (e.g. '280px', '20rem'). In grid and lanes layouts used as the minimum column width (columns will be at least this wide; 1fr if container allows more). In horizontal scroll used as flex-basis. Never forces horizontal overflow — the value is clamped to container width.
42253
+ * @attr {string} gap - Gap between items, as a step of the spacing scale ('0', '2', '4', '6', '8', '10', '12', '16', '20', '24', '28', '32', '40', '44', '48', '56', '64', '80', '96'). Overrides the responsive default at every breakpoint; unset keeps the default.
42254
+ * @attr {string} sm-gap - Gap at sm, overriding `gap` there
42255
+ * @attr {string} md-gap - Gap at md, overriding `gap` there
42256
+ * @attr {string} lg-gap - Gap at lg, overriding `gap` there
41159
42257
  * @attr {object} translations - Translation overrides; unset keys fall back to Dutch. Available keys: 'components.collection.previous-action', 'components.collection.next-action', 'components.collection.load-more-action'
41160
42258
  *
41161
42259
  * @migration The `load-more-label` attribute has been removed.
@@ -41229,9 +42327,16 @@ var NLDDCollection = (_NLDDCollection = class NLDDCollection extends LitElement
41229
42327
  if (this.itemWidth) this.style.setProperty("--_item-width", this.itemWidth);
41230
42328
  else this.style.removeProperty("--_item-width");
41231
42329
  }
41232
- if (changedProperties.has("gap")) {
41233
- if (this.gap) this.style.setProperty("--_gap", this.gap);
41234
- else this.style.removeProperty("--_gap");
42330
+ if (changedProperties.has("gap") || changedProperties.has("smGap") || changedProperties.has("mdGap") || changedProperties.has("lgGap")) {
42331
+ const plain = spacingToValue(this.gap, "nldd-collection", "gap");
42332
+ const write = (name, size, attribute) => {
42333
+ const value = size === void 0 ? plain : spacingToValue(size, "nldd-collection", attribute) ?? plain;
42334
+ if (value === null) this.style.removeProperty(name);
42335
+ else this.style.setProperty(name, value);
42336
+ };
42337
+ write("--_sm-gap", this.smGap, "sm-gap");
42338
+ write("--_md-gap", this.mdGap, "md-gap");
42339
+ write("--_lg-gap", this.lgGap, "lg-gap");
41235
42340
  }
41236
42341
  if (this.lazyLoad && this._loadMoreBtn && !this._intersectionObserver) {
41237
42342
  this._intersectionObserver = new IntersectionObserver(([entry]) => {
@@ -41334,6 +42439,21 @@ __decorate([property({
41334
42439
  type: String,
41335
42440
  reflect: true
41336
42441
  })], NLDDCollection.prototype, "gap", void 0);
42442
+ __decorate([property({
42443
+ type: String,
42444
+ reflect: true,
42445
+ attribute: "sm-gap"
42446
+ })], NLDDCollection.prototype, "smGap", void 0);
42447
+ __decorate([property({
42448
+ type: String,
42449
+ reflect: true,
42450
+ attribute: "md-gap"
42451
+ })], NLDDCollection.prototype, "mdGap", void 0);
42452
+ __decorate([property({
42453
+ type: String,
42454
+ reflect: true,
42455
+ attribute: "lg-gap"
42456
+ })], NLDDCollection.prototype, "lgGap", void 0);
41337
42457
  __decorate([property({ type: Object })], NLDDCollection.prototype, "translations", void 0);
41338
42458
  __decorate([state()], NLDDCollection.prototype, "_visibleCount", void 0);
41339
42459
  __decorate([state()], NLDDCollection.prototype, "_totalCount", void 0);
@@ -41482,7 +42602,7 @@ function windowTemplate(component) {
41482
42602
  return html`
41483
42603
  <dialog class="window"
41484
42604
  aria-label=${component._resolvedAccessibleLabel}
41485
- aria-modal=${component.modeless ? nothing : "true"}
42605
+ aria-modal="true"
41486
42606
  @pointerdown=${component._handleDialogPointerDown}
41487
42607
  @click=${component._handleDialogClick}
41488
42608
  @cancel=${component._handleCancel}
@@ -41502,15 +42622,14 @@ var nlddWindowTranslations = { "components.window.accessible-label": "Venster" }
41502
42622
  /**
41503
42623
  * Nederlandse Digitale Dienst Window Component (Lit + TypeScript)
41504
42624
  *
41505
- * A floating window based on the native <dialog> element. Can be shown modal or
41506
- * modeless. Positionable through CSS values.
42625
+ * A floating window based on the native <dialog> element, positionable through
42626
+ * CSS values. Always modal.
41507
42627
  *
41508
42628
  * No header of its own: consumers use nldd-page with a sticky header inside for
41509
42629
  * a title bar.
41510
42630
  *
41511
42631
  * @element nldd-window
41512
42632
  *
41513
- * @attr {boolean} modeless - Modeless (no backdrop or focus trap); the window is modal by default
41514
42633
  * @attr {boolean} no-light-dismiss - A click on the backdrop does not close the window. For windows where dismissing by accident costs work: a wizard, a form with filled-in fields. Escape and the dismiss button keep working.
41515
42634
  * @attr {string} accessible-label - (required) Accessible name (aria-label). Falls back to the i18n default ('Venster') when unset. Always pass a unique, descriptive name per window.
41516
42635
  * @attr {object} translations - Override translation keys; unset keys fall back to the Dutch default.
@@ -41535,7 +42654,6 @@ var _NLDDWindow;
41535
42654
  var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41536
42655
  constructor(..._args) {
41537
42656
  super(..._args);
41538
- this.modeless = false;
41539
42657
  this.noLightDismiss = false;
41540
42658
  this.accessibleLabel = "";
41541
42659
  this.translations = {};
@@ -41554,7 +42672,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41554
42672
  this._pointerDownOnBackdrop = this._isOnBackdrop(e);
41555
42673
  };
41556
42674
  this._handleDialogClick = (e) => {
41557
- if (this.modeless || this.noLightDismiss) return;
42675
+ if (this.noLightDismiss) return;
41558
42676
  if (this._isOnBackdrop(e) && this._pointerDownOnBackdrop) this.hide();
41559
42677
  };
41560
42678
  this._handleCancel = (e) => {
@@ -41608,8 +42726,7 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41608
42726
  const dialog = this._dialog;
41609
42727
  if (!dialog) return;
41610
42728
  this._closeEmitted = false;
41611
- if (this.modeless) dialog.show();
41612
- else dialog.showModal();
42729
+ dialog.showModal();
41613
42730
  this._applyPositionStyles();
41614
42731
  this._manageFocus();
41615
42732
  this.dispatchEvent(new CustomEvent("open", {
@@ -41671,10 +42788,6 @@ var NLDDWindow = (_NLDDWindow = class NLDDWindow extends LitElement {
41671
42788
  return windowTemplate(this);
41672
42789
  }
41673
42790
  }, _NLDDWindow.styles = windowStyles, _NLDDWindow);
41674
- __decorate([property({
41675
- type: Boolean,
41676
- reflect: true
41677
- })], NLDDWindow.prototype, "modeless", void 0);
41678
42791
  __decorate([property({
41679
42792
  type: Boolean,
41680
42793
  reflect: true,
@@ -43211,7 +44324,7 @@ var template$8 = function() {
43211
44324
  *
43212
44325
  * @element nldd-title-cell
43213
44326
  * @attr {1|2|3|4|5|6} size - Visual size of the title (default: 5)
43214
- * @attr {'default' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Text color variant (default: 'default'). `secondary` demotes the title to match the muted overline/supporting-text. `accent`, `success`, `warning` and `critical` tint all three regions so the cell reads as a coherent state.
44327
+ * @attr {'content' | 'secondary' | 'accent' | 'success' | 'warning' | 'critical'} color - Text color variant (default: 'content'). `secondary` demotes the title to match the muted overline/supporting-text. `accent`, `success`, `warning` and `critical` tint all three regions so the cell reads as a coherent state.
43215
44328
  * @attr {string} width - 'full' | 'fit-content' | CSS length (e.g. '200px', '20rem'). Default: 'full'
43216
44329
  * @attr {string} min-width - Minimum width as CSS length (e.g. '80px', '5rem')
43217
44330
  * @attr {string} max-width - Maximum width as CSS length (e.g. '300px', '20rem')
@@ -43248,7 +44361,7 @@ var NLDDTitleCell = (_NLDDTitleCell = class NLDDTitleCell extends VisibilityMixi
43248
44361
  constructor(..._args) {
43249
44362
  super(..._args);
43250
44363
  this.size = 5;
43251
- this.color = "default";
44364
+ this.color = "content";
43252
44365
  this.width = "full";
43253
44366
  this.horizontalAlignment = "left";
43254
44367
  this.verticalAlignment = "center";
@@ -43306,7 +44419,7 @@ __decorate([property({
43306
44419
  })], NLDDTitleCell.prototype, "size", void 0);
43307
44420
  __decorate([property({
43308
44421
  reflect: true,
43309
- converter: reflectNonDefault("default")
44422
+ converter: reflectNonDefault("content")
43310
44423
  })], NLDDTitleCell.prototype, "color", void 0);
43311
44424
  __decorate([property({
43312
44425
  reflect: true,
@@ -45212,7 +46325,7 @@ var nlddLinkTranslations = { "components.link.opens-in-new-tab-label": "Opent in
45212
46325
  * @attr {string} href - Link target
45213
46326
  * @attr {string} target - Link target (e.g. '_blank'); adjusts rel automatically. With '_blank' the link adds a visually hidden "Opent in nieuw tabblad" announcement for screen readers (WCAG 2.1 SC 3.2.2).
45214
46327
  * @attr {string} rel - Link rel attribute; defaults to 'noopener noreferrer' with target='_blank'
45215
- * @attr {string} size - Text size: 'xs' | 'sm' | 'md' | 'lg' | 'inherit'. Empty = inherit.
46328
+ * @attr {string} size - Text size: 'inherit' (the default) follows the surrounding text and lays the link out inline, so it wraps in running prose. 'xs' | 'sm' | 'md' | 'lg' pin a size and switch to inline-flex, which baseline-aligns a start or end icon with an explicit gap.
45216
46329
  * @attr {string} text - Link text (alternative to the default slot)
45217
46330
  * @attr {string} start-icon - Icon before the text
45218
46331
  * @attr {string} end-icon - Icon after the text
@@ -45232,6 +46345,7 @@ var NLDDLink = (_NLDDLink = class NLDDLink extends withTranslations(LitElement,
45232
46345
  this.href = void 0;
45233
46346
  this.target = void 0;
45234
46347
  this.rel = void 0;
46348
+ this.size = "inherit";
45235
46349
  this.text = "";
45236
46350
  this.startIcon = "";
45237
46351
  this.endIcon = "";
@@ -45265,8 +46379,8 @@ __decorate([property({
45265
46379
  __decorate([property({ type: String })], NLDDLink.prototype, "target", void 0);
45266
46380
  __decorate([property({ type: String })], NLDDLink.prototype, "rel", void 0);
45267
46381
  __decorate([property({
45268
- type: String,
45269
- reflect: true
46382
+ reflect: true,
46383
+ converter: reflectNonDefault("inherit")
45270
46384
  })], NLDDLink.prototype, "size", void 0);
45271
46385
  __decorate([property({
45272
46386
  reflect: true,
@@ -45475,6 +46589,21 @@ var topNavigationBarStyles = css`
45475
46589
  /* # Block */
45476
46590
 
45477
46591
  .top-navigation-bar {
46592
+ /* The ribbon's width, and with it everything measured against the ribbon:
46593
+ its own height and the wordmark beside it. Here rather than on :host,
46594
+ because a container query cannot measure the container it sits on. */
46595
+ @container (max-width: ${smMax$2}) {
46596
+ --_logo-width: var(--semantics-brand-ribbon-sm-width);
46597
+ }
46598
+
46599
+ @container (min-width: ${mdMin$1}) and (max-width: ${mdMax}) {
46600
+ --_logo-width: var(--semantics-brand-ribbon-md-width);
46601
+ }
46602
+
46603
+ @container (min-width: ${lgMin}) {
46604
+ --_logo-width: var(--semantics-brand-ribbon-lg-width);
46605
+ }
46606
+
45478
46607
  box-sizing: border-box;
45479
46608
  display: flex;
45480
46609
  flex-direction: column;
@@ -45523,18 +46652,6 @@ var topNavigationBarStyles = css`
45523
46652
  align-self: start;
45524
46653
  align-items: center;
45525
46654
  justify-content: center;
45526
-
45527
- @container (max-width: ${smMax$2}) {
45528
- --_logo-width: var(--semantics-brand-ribbon-sm-width);
45529
- }
45530
-
45531
- @container (min-width: ${mdMin$1}) and (max-width: ${mdMax}) {
45532
- --_logo-width: var(--semantics-brand-ribbon-md-width);
45533
- }
45534
-
45535
- @container (min-width: ${lgMin}) {
45536
- --_logo-width: var(--semantics-brand-ribbon-lg-width);
45537
- }
45538
46655
  }
45539
46656
 
45540
46657
  .top-navigation-bar__logo svg {
@@ -45587,17 +46704,37 @@ var topNavigationBarStyles = css`
45587
46704
  /* ## Wordmark */
45588
46705
 
45589
46706
  .top-navigation-bar__wordmark {
46707
+ box-sizing: border-box;
45590
46708
  display: flex;
46709
+ /* A grid item is at least as wide as its longest word unless told
46710
+ otherwise, and the column it sits in is one of the two that keep the
46711
+ ribbon centred. One unbreakable name would push the ribbon off centre
46712
+ and the page past the screen. */
46713
+ min-width: 0;
45591
46714
  min-height: calc(var(--_logo-width) * 2);
45592
46715
  grid-column: 3;
45593
46716
  flex-direction: column;
45594
46717
  color: var(--_wordmark-content-color);
46718
+
46719
+ /* The distance the text keeps from the top edge once it outgrows the
46720
+ ribbon. Only at the top: space under it would raise the bar for
46721
+ nothing. The minimum gives up the same 12, so the text still centres
46722
+ on the middle of the ribbon rather than 6px below it. */
46723
+ @container (max-width: ${smMax$2}) {
46724
+ align-self: start;
46725
+ min-height: calc(var(--_logo-width) * 2 - var(--primitives-space-12));
46726
+ padding-block-start: var(--primitives-space-12);
46727
+ }
45595
46728
  }
45596
46729
 
45597
46730
  .top-navigation-bar__wordmark-spacer {
45598
46731
  height: var(--_logo-width);
45599
46732
  flex-grow: 0;
45600
46733
  flex-shrink: 0;
46734
+
46735
+ @container (max-width: ${smMax$2}) {
46736
+ display: none;
46737
+ }
45601
46738
  }
45602
46739
 
45603
46740
  .top-navigation-bar__wordmark-content {
@@ -45607,6 +46744,21 @@ var topNavigationBarStyles = css`
45607
46744
  flex-shrink: 1;
45608
46745
  flex-basis: 50%;
45609
46746
  max-width: var(--_wordmark-max-width);
46747
+ /* anywhere rather than break-word: only this one takes the break into
46748
+ the min-content width, which is what the track measures. A name that
46749
+ cannot break wraps mid-word here, because a name cut off by an
46750
+ ellipsis cannot be read at all. */
46751
+ overflow-wrap: anywhere;
46752
+
46753
+ /* Centred against the ribbon, and what does not fit grows downward: an
46754
+ auto margin takes positive free space and never negative, so the text
46755
+ cannot ride up past the top. Growing is the flex item's job here, and
46756
+ an item that grows leaves nothing for the margins to centre with. */
46757
+ @container (max-width: ${smMax$2}) {
46758
+ flex-grow: 0;
46759
+ flex-basis: auto;
46760
+ margin-block: auto;
46761
+ }
45610
46762
  }
45611
46763
 
45612
46764
  .top-navigation-bar__wordmark-title {
@@ -46184,7 +47336,7 @@ var NLDDTopNavigationBar = (_NLDDTopNavigationBar = class NLDDTopNavigationBar e
46184
47336
  Promise.resolve().then(() => list_item_exports),
46185
47337
  Promise.resolve().then(() => text_cell_exports),
46186
47338
  Promise.resolve().then(() => icon_cell_exports),
46187
- import("./icon-Cd4BloIM.js").then((n) => n.n)
47339
+ import("./icon-C_zwJZ_g.js").then((n) => n.n)
46188
47340
  ]);
46189
47341
  }
46190
47342
  _createGlobalMenuSheet() {
@@ -47485,7 +48637,7 @@ function documentTabBarItemTemplate(component) {
47485
48637
  @click=${component._handleClick}
47486
48638
  >${tabContent}</button>`;
47487
48639
  return html`
47488
- <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "default" : "never"}>
48640
+ <nldd-tooltip text=${tooltipText} timing=${component._isShort ? "delay" : "never"}>
47489
48641
  <div class="document-tab-bar__item">
47490
48642
  ${tab}
47491
48643
  <button class="document-tab-bar__item-dismiss-button"
@@ -48741,10 +49893,10 @@ var modalDialogStyles = css`
48741
49893
  .modal-dialog {
48742
49894
  /* Where the app's layout context ends, as in nldd-sheet: the dialog scrolls
48743
49895
  itself and starts at its own top edge, so anything inside it stops
48744
- following the app's scroll mode and its layer offsets. */
49896
+ following the app's scroll mode and its insets. */
48745
49897
  --context-scroll-mode: nested;
48746
- --context-layer-top: 0px;
48747
- --context-layer-bottom: 0px;
49898
+ --context-inset-top: 0px;
49899
+ --context-inset-bottom: 0px;
48748
49900
 
48749
49901
  box-sizing: border-box;
48750
49902
  outline: none;
@@ -48834,6 +49986,7 @@ function modalDialogTemplate(component) {
48834
49986
  icon=${component.icon || nothing}
48835
49987
  text=${component.text || nothing}
48836
49988
  supporting-text=${component.supportingText || nothing}
49989
+ horizontal-alignment=${component.horizontalAlignment || nothing}
48837
49990
  heading-level="2"
48838
49991
  >
48839
49992
  <slot></slot>
@@ -48856,6 +50009,7 @@ function modalDialogTemplate(component) {
48856
50009
  * @attr {string} icon - Forwarded to nldd-inline-dialog; absent when not set
48857
50010
  * @attr {string} text - Forwarded to nldd-inline-dialog; main text
48858
50011
  * @attr {string} supporting-text - Forwarded to nldd-inline-dialog; supporting text
50012
+ * @attr {'left'|'center'} horizontal-alignment - Forwarded to nldd-inline-dialog. Unset derives it there: slotted content aligns left, a bare message stays centered.
48859
50013
  * @attr {string} accessible-label - Accessible name for the dialog (aria-label); falls back to text
48860
50014
  *
48861
50015
  * @slot - Optional custom content, forwarded to nldd-inline-dialog
@@ -48879,6 +50033,7 @@ var NLDDModalDialog = (_NLDDModalDialog = class NLDDModalDialog extends LitEleme
48879
50033
  this.icon = "";
48880
50034
  this.text = "";
48881
50035
  this.supportingText = "";
50036
+ this.horizontalAlignment = "";
48882
50037
  this.accessibleLabel = "";
48883
50038
  this._closing = false;
48884
50039
  this._cancelPendingOpen = null;
@@ -48979,6 +50134,11 @@ __decorate([property({
48979
50134
  attribute: "supporting-text",
48980
50135
  converter: reflectNonDefault("")
48981
50136
  })], NLDDModalDialog.prototype, "supportingText", void 0);
50137
+ __decorate([property({
50138
+ reflect: true,
50139
+ attribute: "horizontal-alignment",
50140
+ converter: reflectNonDefault("")
50141
+ })], NLDDModalDialog.prototype, "horizontalAlignment", void 0);
48982
50142
  __decorate([property({
48983
50143
  type: String,
48984
50144
  attribute: "accessible-label"
@@ -51460,6 +52620,21 @@ var progressCircleStyles = css`
51460
52620
  .progress-circle__segment-indicator--mintgroen { stroke: var(--semantics-categories-mintgroen-filled-background-color); }
51461
52621
 
51462
52622
 
52623
+ /* ## Filter flood color
52624
+
52625
+ Here rather than in a style attribute, which a strict style-src drops.
52626
+ The color of an arc varies per segment, so that one is set through the
52627
+ CSSOM instead. */
52628
+
52629
+ .progress-circle__flood--track {
52630
+ flood-color: var(--_track-border-color);
52631
+ }
52632
+
52633
+ .progress-circle__flood--indeterminate {
52634
+ flood-color: var(--_indeterminate-border-color);
52635
+ }
52636
+
52637
+
51463
52638
  /* # Spinner (indeterminate)
51464
52639
  GitHub-style: a fixed-width arc (25 / 100 = quarter turn, 90°) that
51465
52640
  simply rotates around the circle. Calmer than Material's elastic
@@ -51651,7 +52826,7 @@ function progressCircleTemplate(component, onSlotChange) {
51651
52826
  <filter id="progress-circle-border-track-${uid}" filterUnits="userSpaceOnUse" x="-10" y="-10" width="120" height="120" color-interpolation-filters="sRGB">
51652
52827
  <feMorphology in="SourceGraphic" operator="erode" radius=${borderErodeRadius} result="eroded"></feMorphology>
51653
52828
  <feComposite operator="out" in="SourceGraphic" in2="eroded" result="edge"></feComposite>
51654
- <feFlood style="flood-color: var(--_track-border-color)" result="flood"></feFlood>
52829
+ <feFlood class="progress-circle__flood progress-circle__flood--track" result="flood"></feFlood>
51655
52830
  <feComposite operator="in" in="flood" in2="edge" result="colored"></feComposite>
51656
52831
  <feComposite operator="over" in="colored" in2="SourceGraphic"></feComposite>
51657
52832
  </filter>
@@ -51659,7 +52834,7 @@ function progressCircleTemplate(component, onSlotChange) {
51659
52834
  <filter id="progress-circle-border-${color}-${uid}" filterUnits="userSpaceOnUse" x="-10" y="-10" width="120" height="120" color-interpolation-filters="sRGB">
51660
52835
  <feMorphology in="SourceGraphic" operator="erode" radius=${borderErodeRadius} result="eroded"></feMorphology>
51661
52836
  <feComposite operator="out" in="SourceGraphic" in2="eroded" result="edge"></feComposite>
51662
- <feFlood style="flood-color: var(--semantics-categories-${color}-filled-highlight-border-color)" result="flood"></feFlood>
52837
+ <feFlood class="progress-circle__flood" style=${styleMap({ floodColor: `var(--semantics-categories-${color}-filled-highlight-border-color)` })} result="flood"></feFlood>
51663
52838
  <feComposite operator="in" in="flood" in2="edge" result="colored"></feComposite>
51664
52839
  <feComposite operator="over" in="colored" in2="SourceGraphic"></feComposite>
51665
52840
  </filter>
@@ -51668,7 +52843,7 @@ function progressCircleTemplate(component, onSlotChange) {
51668
52843
  <filter id="progress-circle-border-indeterminate-${uid}" filterUnits="userSpaceOnUse" x="-10" y="-10" width="120" height="120" color-interpolation-filters="sRGB">
51669
52844
  <feMorphology in="SourceGraphic" operator="erode" radius=${borderErodeRadius} result="eroded"></feMorphology>
51670
52845
  <feComposite operator="out" in="SourceGraphic" in2="eroded" result="edge"></feComposite>
51671
- <feFlood style="flood-color: var(--_indeterminate-border-color)" result="flood"></feFlood>
52846
+ <feFlood class="progress-circle__flood progress-circle__flood--indeterminate" result="flood"></feFlood>
51672
52847
  <feComposite operator="in" in="flood" in2="edge" result="colored"></feComposite>
51673
52848
  <feComposite operator="over" in="colored" in2="SourceGraphic"></feComposite>
51674
52849
  </filter>