@fusion.dev/helix-headless 0.145.0 → 0.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/custom-elements.json +7373 -3849
  2. package/dist/accordion/item/accordion-item.d.ts +14 -0
  3. package/dist/accordion/item/accordion-item.js +16 -6
  4. package/dist/accordion/root/accordion-root.d.ts +8 -5
  5. package/dist/announcement/root/announcement-root.d.ts +3 -2
  6. package/dist/announcement/root/announcement-root.js +3 -1
  7. package/dist/app-header/root/app-header-root.d.ts +7 -2
  8. package/dist/app-header/root/app-header-root.js +9 -1
  9. package/dist/app-header/sectioning.d.ts +3 -1
  10. package/dist/app-header/sectioning.js +3 -2
  11. package/dist/avatar/group/avatar-group.d.ts +20 -14
  12. package/dist/avatar/group/avatar-group.js +17 -19
  13. package/dist/badge/root/badge-root.d.ts +3 -1
  14. package/dist/badge/root/badge-root.js +2 -2
  15. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  16. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  17. package/dist/bar-chart/stack.d.ts +15 -0
  18. package/dist/bar-chart/stack.js +19 -0
  19. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +25 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.js +50 -3
  21. package/dist/button/root/button-root.d.ts +26 -0
  22. package/dist/button/root/button-root.js +38 -18
  23. package/dist/button-group/root/button-group-root.d.ts +2 -1
  24. package/dist/button-group/root/button-group-root.js +5 -1
  25. package/dist/canvas/node/canvas-node.d.ts +4 -3
  26. package/dist/canvas/node/canvas-node.js +3 -2
  27. package/dist/canvas/root/canvas-root.js +2 -2
  28. package/dist/card/root/card-root.d.ts +24 -3
  29. package/dist/card/root/card-root.js +20 -8
  30. package/dist/carousel/root/carousel-root.d.ts +43 -4
  31. package/dist/carousel/root/carousel-root.js +40 -12
  32. package/dist/carousel/stops.d.ts +20 -0
  33. package/dist/carousel/stops.js +30 -0
  34. package/dist/chat/message/chat-message.js +5 -4
  35. package/dist/chat/naming.d.ts +5 -1
  36. package/dist/chat/naming.js +4 -2
  37. package/dist/chat/root/chat-root.d.ts +25 -3
  38. package/dist/chat/root/chat-root.js +22 -5
  39. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  40. package/dist/checkbox/root/checkbox-root.js +14 -1
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  42. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  43. package/dist/checkbox-group/select-all.d.ts +27 -0
  44. package/dist/checkbox-group/select-all.js +12 -0
  45. package/dist/code-block/highlight.d.ts +15 -0
  46. package/dist/code-block/highlight.js +4 -0
  47. package/dist/code-block/root/code-block-root.d.ts +49 -1
  48. package/dist/code-block/root/code-block-root.js +58 -3
  49. package/dist/color-input/root/color-input-root.d.ts +3 -0
  50. package/dist/color-input/root/color-input-root.js +6 -3
  51. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  52. package/dist/color-picker/root/color-picker-root.js +80 -21
  53. package/dist/color-picker/swatches.d.ts +12 -0
  54. package/dist/color-picker/swatches.js +26 -0
  55. package/dist/combobox/root/combobox-root.d.ts +43 -0
  56. package/dist/combobox/root/combobox-root.js +79 -15
  57. package/dist/command/root/command-root.d.ts +16 -1
  58. package/dist/command/root/command-root.js +22 -4
  59. package/dist/copy-button/root/copy-button-root.d.ts +5 -0
  60. package/dist/copy-button/root/copy-button-root.js +10 -3
  61. package/dist/data-table/index.d.ts +1 -1
  62. package/dist/data-table/index.js +1 -1
  63. package/dist/data-table/keys.d.ts +24 -0
  64. package/dist/data-table/keys.js +24 -0
  65. package/dist/data-table/root/data-table-root.d.ts +27 -18
  66. package/dist/data-table/root/data-table-root.js +20 -52
  67. package/dist/data-table/tree.d.ts +16 -1
  68. package/dist/data-table/tree.js +2 -3
  69. package/dist/date-input/root/date-input-root.d.ts +3 -1
  70. package/dist/date-input/root/date-input-root.js +18 -7
  71. package/dist/date-picker/root/date-picker-root.js +12 -6
  72. package/dist/dialog/description.js +15 -1
  73. package/dist/dialog/root/dialog-root.d.ts +8 -2
  74. package/dist/dialog/root/dialog-root.js +16 -1
  75. package/dist/divider/root/divider-root.d.ts +3 -0
  76. package/dist/divider/root/divider-root.js +2 -1
  77. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  78. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  79. package/dist/drawer/reveal.d.ts +8 -0
  80. package/dist/drawer/reveal.js +1 -0
  81. package/dist/drawer/root/drawer-root.d.ts +59 -0
  82. package/dist/drawer/root/drawer-root.js +63 -4
  83. package/dist/drilldown/item/drilldown-item.js +1 -1
  84. package/dist/drilldown/root/drilldown-root.d.ts +21 -3
  85. package/dist/drilldown/root/drilldown-root.js +21 -3
  86. package/dist/element-events.generated.js +31 -5
  87. package/dist/empty/root/empty-root.d.ts +3 -1
  88. package/dist/empty/root/empty-root.js +3 -2
  89. package/dist/file-input/root/file-input-root.d.ts +69 -6
  90. package/dist/file-input/root/file-input-root.js +82 -19
  91. package/dist/file-input/screen.d.ts +2 -1
  92. package/dist/filter-input/menu/filter-menu.d.ts +2 -0
  93. package/dist/filter-input/menu/filter-menu.js +12 -4
  94. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  95. package/dist/filter-input/root/filter-input-root.js +1 -1
  96. package/dist/filter-input/suggest.d.ts +28 -3
  97. package/dist/filter-input/suggest.js +18 -8
  98. package/dist/format-date/root/format-date-root.d.ts +9 -16
  99. package/dist/format-date/root/format-date-root.js +10 -27
  100. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  101. package/dist/heatmap/root/heatmap-root.js +34 -12
  102. package/dist/icon/root/icon-root.d.ts +12 -1
  103. package/dist/icon/root/icon-root.js +21 -10
  104. package/dist/icon/settle.d.ts +7 -0
  105. package/dist/icon/settle.js +10 -0
  106. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  107. package/dist/image/root/image-root.d.ts +5 -1
  108. package/dist/image/root/image-root.js +1 -1
  109. package/dist/input/root/input-root.d.ts +12 -2
  110. package/dist/input/root/input-root.js +11 -2
  111. package/dist/kbd/root/kbd-root.d.ts +35 -1
  112. package/dist/kbd/root/kbd-root.js +23 -4
  113. package/dist/legend/root/legend-root.d.ts +55 -8
  114. package/dist/legend/root/legend-root.js +55 -13
  115. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  116. package/dist/lightbox/root/lightbox-root.js +86 -19
  117. package/dist/lightbox/zoom-controller.d.ts +59 -0
  118. package/dist/lightbox/zoom-controller.js +128 -0
  119. package/dist/lightbox/zoom.d.ts +22 -0
  120. package/dist/lightbox/zoom.js +16 -0
  121. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  122. package/dist/line-chart/root/line-chart-root.js +21 -1
  123. package/dist/list/item/list-item.d.ts +2 -0
  124. package/dist/list/item/list-item.js +11 -2
  125. package/dist/list/root/list-root.d.ts +62 -13
  126. package/dist/list/root/list-root.js +70 -13
  127. package/dist/list/selection.d.ts +37 -7
  128. package/dist/list/selection.js +16 -7
  129. package/dist/listbox/root/listbox-root.d.ts +37 -1
  130. package/dist/listbox/root/listbox-root.js +49 -3
  131. package/dist/loader/busy.d.ts +10 -0
  132. package/dist/loader/busy.js +11 -0
  133. package/dist/loader/root/loader-root.d.ts +9 -2
  134. package/dist/loader/root/loader-root.js +23 -3
  135. package/dist/megamenu/root/megamenu-root.d.ts +51 -10
  136. package/dist/megamenu/root/megamenu-root.js +76 -5
  137. package/dist/menu/item/menu-item.d.ts +51 -2
  138. package/dist/menu/item/menu-item.js +47 -23
  139. package/dist/menu/root/menu-root.d.ts +14 -0
  140. package/dist/menu/root/menu-root.js +2 -2
  141. package/dist/meter/root/meter-root.d.ts +20 -1
  142. package/dist/meter/root/meter-root.js +11 -15
  143. package/dist/navigation/root/navigation-root.d.ts +2 -1
  144. package/dist/navigation/root/navigation-root.js +2 -2
  145. package/dist/notifications/root/notifications-root.d.ts +10 -3
  146. package/dist/notifications/root/notifications-root.js +19 -7
  147. package/dist/number-input/root/number-input-root.d.ts +17 -8
  148. package/dist/number-input/root/number-input-root.js +2 -7
  149. package/dist/object-inspector/root/object-inspector-root.d.ts +75 -27
  150. package/dist/object-inspector/root/object-inspector-root.js +117 -76
  151. package/dist/object-inspector/rows.d.ts +18 -0
  152. package/dist/object-inspector/rows.js +33 -0
  153. package/dist/option/root/option-root.d.ts +5 -3
  154. package/dist/outline/root/outline-root.d.ts +18 -0
  155. package/dist/outline/root/outline-root.js +23 -5
  156. package/dist/pagination/size/pagination-size.d.ts +1 -0
  157. package/dist/pagination/size/pagination-size.js +1 -1
  158. package/dist/pin-input/code.d.ts +10 -0
  159. package/dist/pin-input/code.js +13 -0
  160. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  161. package/dist/pin-input/root/pin-input-root.js +10 -11
  162. package/dist/popover/hide-outside.d.ts +14 -0
  163. package/dist/popover/hide-outside.js +41 -0
  164. package/dist/popover/root/popover-root.d.ts +57 -7
  165. package/dist/popover/root/popover-root.js +58 -3
  166. package/dist/progress/root/progress-root.d.ts +20 -0
  167. package/dist/progress/root/progress-root.js +15 -15
  168. package/dist/qr-code/export.d.ts +26 -1
  169. package/dist/qr-code/export.js +14 -0
  170. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  171. package/dist/qr-code/root/qr-code-root.js +58 -9
  172. package/dist/quote/marks.d.ts +8 -0
  173. package/dist/quote/marks.js +5 -0
  174. package/dist/quote/root/quote-root.d.ts +14 -2
  175. package/dist/quote/root/quote-root.js +23 -1
  176. package/dist/radio/root/radio-root.d.ts +6 -2
  177. package/dist/radio/root/radio-root.js +6 -1
  178. package/dist/rating/root/rating-root.d.ts +9 -4
  179. package/dist/rating/root/rating-root.js +31 -7
  180. package/dist/rating/steps.d.ts +7 -0
  181. package/dist/rating/steps.js +9 -0
  182. package/dist/scatter-chart/fit.d.ts +15 -0
  183. package/dist/scatter-chart/fit.js +35 -0
  184. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  185. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  186. package/dist/scroll-area/root/scroll-area-root.d.ts +22 -51
  187. package/dist/scroll-area/root/scroll-area-root.js +57 -173
  188. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  189. package/dist/scroll-area/scrollbar-controller.js +172 -0
  190. package/dist/scroll-area/scrollbar.d.ts +27 -0
  191. package/dist/scroll-area/scrollbar.js +9 -0
  192. package/dist/select/root/select-root.d.ts +15 -0
  193. package/dist/select/root/select-root.js +18 -6
  194. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  195. package/dist/skeleton/root/skeleton-root.js +4 -2
  196. package/dist/skip-link/root/skip-link-root.js +2 -2
  197. package/dist/slider/root/slider-root.d.ts +41 -4
  198. package/dist/slider/root/slider-root.js +23 -3
  199. package/dist/sparkline/readings.d.ts +18 -0
  200. package/dist/sparkline/readings.js +19 -0
  201. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  202. package/dist/sparkline/root/sparkline-root.js +118 -23
  203. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  204. package/dist/splitter/root/splitter-root.d.ts +7 -12
  205. package/dist/splitter/root/splitter-root.js +209 -204
  206. package/dist/stat/root/stat-root.d.ts +14 -5
  207. package/dist/stat/root/stat-root.js +21 -5
  208. package/dist/steps/reach.d.ts +5 -0
  209. package/dist/steps/reach.js +3 -0
  210. package/dist/steps/root/steps-root.d.ts +49 -3
  211. package/dist/steps/root/steps-root.js +44 -5
  212. package/dist/steps/step/step.d.ts +7 -0
  213. package/dist/steps/step/step.js +7 -2
  214. package/dist/switch/root/switch-root.d.ts +10 -0
  215. package/dist/table/column/table-column.d.ts +0 -10
  216. package/dist/table/column/table-column.js +3 -19
  217. package/dist/table/root/table-root.d.ts +31 -10
  218. package/dist/table/root/table-root.js +2 -13
  219. package/dist/tabs/panel/tab-panel.d.ts +16 -3
  220. package/dist/tabs/panel/tab-panel.js +34 -5
  221. package/dist/tabs/root/tabs-root.d.ts +12 -0
  222. package/dist/tabs/root/tabs-root.js +1 -1
  223. package/dist/tabs/tab/tab.d.ts +5 -0
  224. package/dist/tabs/tab/tab.js +5 -2
  225. package/dist/tag/group/tag-group.d.ts +31 -4
  226. package/dist/tag/group/tag-group.js +46 -7
  227. package/dist/tag/root/tag-root.js +9 -2
  228. package/dist/textarea/root/textarea-root.d.ts +28 -1
  229. package/dist/textarea/root/textarea-root.js +40 -1
  230. package/dist/time-input/root/time-input-root.d.ts +5 -1
  231. package/dist/time-input/root/time-input-root.js +16 -8
  232. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  233. package/dist/time-picker/root/time-picker-root.js +7 -10
  234. package/dist/time-picker/wheel-name.d.ts +9 -0
  235. package/dist/time-picker/wheel-name.js +15 -0
  236. package/dist/timeline/group/timeline-group.js +2 -2
  237. package/dist/toast/item/toast-item.d.ts +14 -1
  238. package/dist/toast/item/toast-item.js +22 -5
  239. package/dist/toast/root/toast-root.d.ts +6 -0
  240. package/dist/toast/root/toast-root.js +16 -1
  241. package/dist/toggle/group/toggle-group.d.ts +24 -3
  242. package/dist/toggle/group/toggle-group.js +34 -5
  243. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  244. package/dist/tooltip/root/tooltip-root.js +9 -1
  245. package/dist/tour/back.d.ts +10 -0
  246. package/dist/tour/back.js +8 -0
  247. package/dist/tour/root/tour-root.d.ts +30 -2
  248. package/dist/tour/root/tour-root.js +34 -8
  249. package/dist/tree/item/tree-item.d.ts +7 -3
  250. package/dist/tree/item/tree-item.js +3 -0
  251. package/dist/tree/root/tree-root.d.ts +24 -3
  252. package/dist/tree/root/tree-root.js +32 -13
  253. package/dist/tree/selection.js +2 -15
  254. package/dist/trend/root/trend-root.d.ts +43 -18
  255. package/dist/trend/root/trend-root.js +31 -6
  256. package/dist/video/media-events.d.ts +13 -18
  257. package/dist/video/media-events.js +1 -5
  258. package/dist/video/root/video-root.d.ts +18 -5
  259. package/dist/video/root/video-root.js +19 -10
  260. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -0
  261. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  262. package/dist/video-playlist/root/video-playlist-root.d.ts +7 -1
  263. package/dist/virtual-list/fill.d.ts +0 -8
  264. package/dist/virtual-list/fill.js +1 -17
  265. package/dist/virtual-list/plan.d.ts +49 -0
  266. package/dist/virtual-list/plan.js +16 -0
  267. package/dist/virtual-list/root/virtual-list-root.d.ts +114 -13
  268. package/dist/virtual-list/root/virtual-list-root.js +183 -40
  269. package/lint.json +1 -1
  270. package/package.json +8 -8
  271. package/types/jsx.d.ts +4577 -835
  272. package/types/vue.d.ts +926 -168
  273. package/dist/chat/relative-clock.d.ts +0 -39
  274. package/dist/chat/relative-clock.js +0 -31
  275. package/dist/data-table/root/order.d.ts +0 -141
  276. package/dist/data-table/root/order.js +0 -76
  277. package/dist/tag/selection.d.ts +0 -10
  278. package/dist/tag/selection.js +0 -12
@@ -5,14 +5,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessObjectInspector_1;
8
- import { AriaForwardController, CopyController, countLabel, customElement, drawStatus, elementById, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeActivateEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, inlineStep, requestChange, StatusController, scrollItemIntoView, searchableById, VirtualizeController, } from '@fusion.dev/helix-core';
8
+ import { AriaForwardController, CopyController, countLabel, customElement, drawStatus, elementById, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixBeforeOpenEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, inlineStep, requestChange, StatusController, scrollItemIntoView, searchableById, treeArrow, VirtualizeController, } from '@fusion.dev/helix-core';
9
9
  import { caretDown, caretRight } from '@fusion.dev/helix-icons';
10
10
  import { html, nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
12
+ import { repeat } from 'lit/directives/repeat.js';
12
13
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
13
14
  import { html as staticHtml } from 'lit/static-html.js';
14
15
  import '../../copy-button/index.js';
15
- import { ancestorsOf, entries, flatten, search, seed } from '../rows.js';
16
+ import { cursorFor, entries, flatten, search, seed, siblingBranches, withAncestors, } from '../rows.js';
16
17
  let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends HelixElement {
17
18
  constructor() {
18
19
  super(...arguments);
@@ -38,6 +39,8 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
38
39
  this.opened = new Set();
39
40
  this.cursor = 0;
40
41
  this.revealing = false;
42
+ this.focusing = false;
43
+ this.hadFocus = false;
41
44
  this.typed = { at: 0, text: '' };
42
45
  this.branches = new Set();
43
46
  this.aria = new AriaForwardController(this, {
@@ -83,22 +86,23 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
83
86
  break;
84
87
  case 'ArrowRight':
85
88
  case 'ArrowLeft':
86
- if (inlineStep(this, event.key) === 1) {
87
- if (row.branch && !row.open) {
88
- this.toggle(row, true);
89
- }
90
- else if (row.open) {
89
+ switch (treeArrow(row, inlineStep(this, event.key) === 1 ? 1 : -1)) {
90
+ case 'open':
91
+ case 'close':
92
+ this.toggle(row, !row.open);
93
+ break;
94
+ case 'child':
91
95
  this.move(this.cursor + 1);
96
+ break;
97
+ case 'parent': {
98
+ const parent = rows.findLastIndex((candidate, at) => at < this.cursor && candidate.depth === row.depth - 1);
99
+ if (parent >= 0) {
100
+ this.move(parent);
101
+ }
102
+ break;
92
103
  }
93
- }
94
- else if (row.open) {
95
- this.toggle(row, false);
96
- }
97
- else {
98
- const parent = rows.findLastIndex((candidate, at) => at < this.cursor && candidate.depth === row.depth - 1);
99
- if (parent >= 0) {
100
- this.move(parent);
101
- }
104
+ default:
105
+ break;
102
106
  }
103
107
  break;
104
108
  case 'Enter':
@@ -108,7 +112,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
108
112
  }
109
113
  break;
110
114
  case '*':
111
- this.openSiblings(row);
115
+ this.requestOpen(siblingBranches(rows, row), true);
112
116
  break;
113
117
  default:
114
118
  if (event.key.length !== 1 || event.altKey || event.ctrlKey || event.metaKey) {
@@ -136,9 +140,22 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
136
140
  }
137
141
  };
138
142
  this.handleFilterKeydown = (event) => {
139
- if (event.key === 'ArrowDown') {
143
+ if (event.key === 'ArrowDown' && this.rows.length > 0) {
140
144
  event.preventDefault();
141
- this.partNamed('tree')?.focus();
145
+ this.revealing = true;
146
+ this.focusing = true;
147
+ this.requestUpdate();
148
+ }
149
+ };
150
+ this.handleFocusIn = (event) => {
151
+ const index = Number(event.target.dataset?.index);
152
+ if (Number.isInteger(index) && index !== this.cursor) {
153
+ this.move(index);
154
+ }
155
+ };
156
+ this.handleFocusOut = () => {
157
+ if (this.windowed) {
158
+ this.requestUpdate();
142
159
  }
143
160
  };
144
161
  }
@@ -173,18 +190,11 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
173
190
  }
174
191
  }
175
192
  openAround(paths) {
176
- const opened = new Set(this.opened);
177
- for (const path of paths) {
178
- for (const above of ancestorsOf(path)) {
179
- if (this.branches.has(above)) {
180
- opened.add(above);
181
- }
182
- }
183
- }
184
- this.opened = opened;
193
+ this.opened = withAncestors(this.opened, this.branches, paths);
185
194
  }
186
195
  willUpdate(changed) {
187
196
  super.willUpdate(changed);
197
+ this.hadFocus = this.hasFocusedRow();
188
198
  if (this.loadedFor !== this.for && searchableById(this) && !this.hydrating) {
189
199
  this.loadedFor = this.for;
190
200
  this.load();
@@ -212,26 +222,12 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
212
222
  wanted !== undefined ||
213
223
  ['active', 'defaultDepth', 'expanded', 'filter', 'opened'].some((name) => changed.has(name))) {
214
224
  this.rows = flatten(this.root, this.opened, this.found);
215
- this.cursor = this.place(wanted ?? was, changed.has('filter'));
225
+ this.cursor = cursorFor(this.rows, wanted ?? was, this.cursor, changed.has('filter'));
216
226
  }
217
227
  if (changed.has('filter') && this.hasUpdated) {
218
228
  this.tell();
219
229
  }
220
230
  }
221
- place(path, filtered) {
222
- const rows = this.rows;
223
- const at = (one) => rows.findIndex((row) => row.path === one);
224
- const own = path === undefined ? -1 : at(path);
225
- if (own >= 0 || path === undefined) {
226
- return own >= 0 ? own : Math.min(this.cursor, Math.max(0, rows.length - 1));
227
- }
228
- const match = filtered ? rows.findIndex((row) => row.match) : -1;
229
- const around = ancestorsOf(path)
230
- .reverse()
231
- .map(at)
232
- .find((one) => one >= 0);
233
- return match >= 0 ? match : (around ?? 0);
234
- }
235
231
  tell() {
236
232
  if (this.found) {
237
233
  this.status.say(countLabel(this.found.size, this.resultLabel, this.resultsLabel, this.lang));
@@ -250,6 +246,19 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
250
246
  this.revealing = false;
251
247
  this.reveal();
252
248
  }
249
+ const lost = this.hadFocus &&
250
+ !this.hasFocusedRow() &&
251
+ this.ownerDocument.activeElement === this.ownerDocument.body;
252
+ if (this.focusing || lost) {
253
+ this.focusing = false;
254
+ this.focusActive();
255
+ }
256
+ }
257
+ focusActive() {
258
+ const row = this.partNamed('tree')?.querySelector(`[data-index="${this.cursor}"]`);
259
+ if (row && this.shadowRoot?.activeElement !== row) {
260
+ row.focus({ preventScroll: true });
261
+ }
253
262
  }
254
263
  load() {
255
264
  const text = this.for === '' ? '' : (elementById(this, this.for)?.textContent ?? '');
@@ -277,7 +286,10 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
277
286
  const from = Number(this.cursor > Number(last.dataset.index) ? last.dataset.index : first.dataset.index);
278
287
  tree.scrollTop += (this.cursor - from) * height;
279
288
  this.virtualize.measure();
280
- void this.updateComplete.then(() => this.reveal(attempts - 1));
289
+ void this.updateComplete.then(() => {
290
+ this.reveal(attempts - 1);
291
+ this.focusActive();
292
+ });
281
293
  }
282
294
  copyable(row) {
283
295
  if (!row) {
@@ -288,15 +300,44 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
288
300
  }
289
301
  return JSON.stringify(row.value, null, 2) ?? String(row.value);
290
302
  }
291
- toggle(row, open = !row.open) {
292
- const opened = new Set(this.opened);
293
- if (open) {
294
- opened.add(row.path);
303
+ requestOpen(paths, open) {
304
+ if (paths.length === 0) {
305
+ return false;
295
306
  }
296
- else {
297
- opened.delete(row.path);
307
+ const applied = requestChange({
308
+ apply: () => {
309
+ const opened = new Set(this.opened);
310
+ for (const path of paths) {
311
+ if (open) {
312
+ opened.add(path);
313
+ }
314
+ else {
315
+ opened.delete(path);
316
+ }
317
+ }
318
+ this.opened = opened;
319
+ },
320
+ event: (next) => (next ? new HelixBeforeOpenEvent() : new HelixBeforeCloseEvent()),
321
+ host: this,
322
+ next: open,
323
+ });
324
+ if (applied) {
325
+ this.announceToggle(open);
298
326
  }
299
- this.opened = opened;
327
+ return applied;
328
+ }
329
+ announceToggle(open) {
330
+ const states = { newState: open ? 'open' : 'closed', oldState: open ? 'closed' : 'open' };
331
+ if (typeof ToggleEvent === 'function') {
332
+ this.dispatchEvent(new ToggleEvent('toggle', states));
333
+ return;
334
+ }
335
+ const event = new Event('toggle', { bubbles: false, cancelable: false, composed: false });
336
+ Object.assign(event, states);
337
+ this.dispatchEvent(event);
338
+ }
339
+ toggle(row, open = !row.open) {
340
+ this.requestOpen([row.path], open);
300
341
  }
301
342
  move(to) {
302
343
  const at = Math.max(0, Math.min(to, this.rows.length - 1));
@@ -319,20 +360,9 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
319
360
  this.dispatchEvent(new HelixActivateEvent({ value: path }));
320
361
  }
321
362
  this.revealing = true;
363
+ this.focusing = true;
322
364
  return true;
323
365
  }
324
- openSiblings(row) {
325
- const parent = row.path.slice(0, row.path.lastIndexOf('/'));
326
- const opened = new Set(this.opened);
327
- for (const one of this.rows) {
328
- if (one.branch &&
329
- one.depth === row.depth &&
330
- one.path.slice(0, one.path.lastIndexOf('/')) === parent) {
331
- opened.add(one.path);
332
- }
333
- }
334
- this.opened = opened;
335
- }
336
366
  typeahead(letter) {
337
367
  const now = Date.now();
338
368
  const text = now - this.typed.at < 500 ? this.typed.text + letter : letter;
@@ -391,18 +421,17 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
391
421
  const active = index === this.cursor;
392
422
  return html `<div
393
423
  part="row${active ? ' row-active' : ''}${row.match ? ' row-match' : ''}"
394
- role="treeitem"
424
+ role="row"
395
425
  id=${`row-${index}`}
396
426
  data-index=${index}
427
+ tabindex=${active ? '0' : '-1'}
397
428
  aria-level=${row.depth + 1}
398
429
  aria-posinset=${row.position}
399
- aria-selected="false"
400
430
  aria-setsize=${row.siblings}
401
431
  aria-expanded=${row.branch ? String(row.open) : nothing}
402
432
  style=${`--object-inspector-level: ${row.depth}`}
403
433
  @click=${() => this.handleRowClick(index, row)}
404
- >
405
- ${row.branch
434
+ ><div role="gridcell" style="display: contents">${row.branch
406
435
  ? html `<svg
407
436
  part="toggle"
408
437
  ?data-mirror=${!row.open}
@@ -412,9 +441,13 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
412
441
  >
413
442
  ${unsafeSVG((row.open ? caretDown : caretRight).content)}
414
443
  </svg>`
415
- : nothing}${row.key === undefined ? nothing : html `<span part="key">${row.key}</span>: `}${this.renderValue(row)}
444
+ : nothing}${row.key === undefined ? nothing : html `<span part="key">${row.key}</span>: `}${this.renderValue(row)}</div>
416
445
  </div>`;
417
446
  }
447
+ hasFocusedRow() {
448
+ const focused = this.shadowRoot?.activeElement;
449
+ return focused?.getAttribute?.('role') === 'row';
450
+ }
418
451
  renderCopyButton(row) {
419
452
  return staticHtml `<${this.tag('copy-button')}
420
453
  part="copy"
@@ -429,10 +462,18 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
429
462
  }
430
463
  render() {
431
464
  const rows = this.rows;
432
- const { after, before, first, last } = this.windowed
433
- ? this.virtualize.window
434
- : { after: 0, before: 0, first: 0, last: rows.length };
435
- const shown = rows.slice(first, last);
465
+ const { first, last } = this.windowed ? this.virtualize.window : { first: 0, last: rows.length };
466
+ const kept = this.windowed && (this.cursor < first || this.cursor >= last) && this.hasFocusedRow()
467
+ ? this.cursor
468
+ : undefined;
469
+ const { after, before } = this.windowed
470
+ ? this.virtualize.spacers(kept)
471
+ : { after: 0, before: 0 };
472
+ const shown = rows.slice(first, last).map((row, at) => [row, first + at]);
473
+ const keptRow = kept === undefined ? undefined : rows[kept];
474
+ if (kept !== undefined && keptRow) {
475
+ shown.splice(kept < first ? 0 : shown.length, 0, [keptRow, kept]);
476
+ }
436
477
  return html `${this.label ? html `<div part="header"><span part="label">${this.label}</span></div>` : nothing}${this.filterable
437
478
  ? html `<input
438
479
  part="search-field"
@@ -450,16 +491,16 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
450
491
  <div
451
492
  part="tree"
452
493
  id="tree"
453
- role="tree"
454
- tabindex="0"
494
+ role="treegrid"
455
495
  ?data-scroller=${this.windowed}
456
496
  aria-label=${this.label || this.treeLabel}
457
- aria-activedescendant=${rows.length > 0 ? `row-${this.cursor}` : nothing}
458
497
  @keydown=${this.handleKeydown}
498
+ @focusin=${this.handleFocusIn}
499
+ @focusout=${this.handleFocusOut}
459
500
  @scroll=${() => this.virtualize.measure()}
460
501
  >
461
502
  ${this.windowed ? html `<div part="before" role="presentation" style="block-size: ${before}px"></div>` : nothing}
462
- ${shown.map((row, at) => this.renderRow(row, first + at))}
503
+ ${repeat(shown, ([row]) => row.path, ([row, at]) => this.renderRow(row, at))}
463
504
  ${this.windowed ? html `<div part="after" role="presentation" style="block-size: ${after}px"></div>` : nothing}
464
505
  </div>
465
506
  ${this.noCopy ? nothing : this.renderCopyButton(rows[this.cursor])}
@@ -55,3 +55,21 @@ export declare const seed: (root: unknown, depth: number, known?: Set<string>, o
55
55
  branches: Set<string>;
56
56
  opened: Set<string>;
57
57
  };
58
+ /**
59
+ * Where the active row goes once the rows have moved, as an index into `rows`.
60
+ *
61
+ * Onto `path` where it is still shown. Where a filter just hid it
62
+ * (`filtered`), onto the first match. Otherwise onto the nearest branch around
63
+ * it that is shown. With no path to keep, `cursor` held inside the rows.
64
+ */
65
+ export declare const cursorFor: (rows: readonly Row[], path: string | undefined, cursor: number, filtered: boolean) => number;
66
+ /**
67
+ * The open branches with every branch around each of `paths` opened too,
68
+ * where the value has that branch (`branches`).
69
+ */
70
+ export declare const withAncestors: (opened: ReadonlySet<string>, branches: ReadonlySet<string>, paths: Iterable<string>) => Set<string>;
71
+ /**
72
+ * The branches `*` opens from `row`: every branch at its depth under the same
73
+ * parent, itself included, that is not open yet.
74
+ */
75
+ export declare const siblingBranches: (rows: readonly Row[], row: Row) => string[];
@@ -84,3 +84,36 @@ export const seed = (root, depth, known = new Set(), opened = new Set()) => {
84
84
  visit(root, '', 0);
85
85
  return { branches, opened: open };
86
86
  };
87
+ export const cursorFor = (rows, path, cursor, filtered) => {
88
+ const at = (one) => rows.findIndex((row) => row.path === one);
89
+ const own = path === undefined ? -1 : at(path);
90
+ if (own >= 0 || path === undefined) {
91
+ return own >= 0 ? own : Math.min(cursor, Math.max(0, rows.length - 1));
92
+ }
93
+ const match = filtered ? rows.findIndex((row) => row.match) : -1;
94
+ const around = ancestorsOf(path)
95
+ .reverse()
96
+ .map(at)
97
+ .find((one) => one >= 0);
98
+ return match >= 0 ? match : (around ?? 0);
99
+ };
100
+ export const withAncestors = (opened, branches, paths) => {
101
+ const next = new Set(opened);
102
+ for (const path of paths) {
103
+ for (const above of ancestorsOf(path)) {
104
+ if (branches.has(above)) {
105
+ next.add(above);
106
+ }
107
+ }
108
+ }
109
+ return next;
110
+ };
111
+ export const siblingBranches = (rows, row) => {
112
+ const parent = row.path.slice(0, row.path.lastIndexOf('/'));
113
+ return rows
114
+ .filter((one) => one.branch &&
115
+ !one.open &&
116
+ one.depth === row.depth &&
117
+ one.path.slice(0, one.path.lastIndexOf('/')) === parent)
118
+ .map((one) => one.path);
119
+ };
@@ -63,9 +63,11 @@ export declare class HelixHeadlessOption extends HelixElement {
63
63
  /**
64
64
  * Whether this is the option the keyboard is on.
65
65
  *
66
- * Written by the listbox, which writes it once a reader moves the highlight.
67
- * It shows as `:state(active)`, and as a `data-active` attribute for the
68
- * stylesheets written before the state existed.
66
+ * Written by the listbox once a reader moves the highlight. Style it with
67
+ * `:state(active)`, or `::part(item-active)` for an option inside another
68
+ * component's shadow root. The `data-active` attribute sets it from markup;
69
+ * it is also written back for stylesheets from before the state, and no
70
+ * stylesheet of the library's own selects it.
69
71
  */
70
72
  active: boolean;
71
73
  /**
@@ -20,6 +20,9 @@ import { type Branch } from '../levels.js';
20
20
  *
21
21
  * A heading slotted into `heading` names the navigation, in place of `label`.
22
22
  *
23
+ * In a contents rail with a scrollbar of its own, `follow` keeps the link of
24
+ * the section in view showing as the reader scrolls the page.
25
+ *
23
26
  * The current section is decided by the headings' positions. An
24
27
  * `IntersectionObserver` with a band near the top fails both ways: when
25
28
  * headings are far apart, none is in the band and no section is marked, and
@@ -73,6 +76,13 @@ export declare class HelixHeadlessOutline extends HelixElement {
73
76
  * JavaScript rather than CSS decides.
74
77
  */
75
78
  behavior: ScrollBehavior;
79
+ /**
80
+ * Keeps the link of the section in view in view itself, in an outline
81
+ * longer than the box it scrolls in, such as a contents rail with a
82
+ * scrollbar of its own. Only that box moves: never the page, and never the
83
+ * box the sections scroll in.
84
+ */
85
+ follow: boolean;
76
86
  /**
77
87
  * The navigation's name, read by screen readers. A heading in the `heading`
78
88
  * slot names it in place of this.
@@ -154,6 +164,14 @@ export declare class HelixHeadlessOutline extends HelixElement {
154
164
  private mark;
155
165
  /** Where the destination is set, whether measured or asked for. */
156
166
  private markTo;
167
+ /**
168
+ * Scrolls the box the links scroll in to show `link`, under `follow`.
169
+ *
170
+ * The nearest box above the link that scrolls, and only where it is the
171
+ * rail's own: the page, the box the sections scroll in, or a box holding it
172
+ * would move the reading under the reader, which is the scroll this marks.
173
+ */
174
+ private reveal;
157
175
  /**
158
176
  * Whether the page has no more scroll left.
159
177
  *
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, elementById, HelixActivateEvent, HelixElement, IndicatorController, reflectElements, } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixActivateEvent, HelixElement, IndicatorController, plainClick, reflectElements, scrollItemIntoView, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { levelOf, nest } from '../levels.js';
@@ -18,6 +18,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
18
18
  surface: () => this.partNamed('nav'),
19
19
  });
20
20
  this.behavior = 'auto';
21
+ this.follow = false;
21
22
  this.label = 'On this page';
22
23
  this.offset = 0;
23
24
  this.reading = '';
@@ -32,10 +33,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
32
33
  });
33
34
  };
34
35
  this.handleClick = (event) => {
35
- if (event.defaultPrevented || event.button !== 0) {
36
- return;
37
- }
38
- if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
36
+ if (event.defaultPrevented || !plainClick(event)) {
39
37
  return;
40
38
  }
41
39
  const link = event.target?.closest?.('a[href^="#"]');
@@ -142,9 +140,26 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
142
140
  if (next !== this.reading) {
143
141
  this.reading = next;
144
142
  this.indicator.place();
143
+ if (this.follow && active) {
144
+ this.reveal(active.link);
145
+ }
145
146
  this.dispatchEvent(new HelixActivateEvent({ value: next }));
146
147
  }
147
148
  }
149
+ reveal(link) {
150
+ const sections = this.scroller;
151
+ const page = this.page;
152
+ for (let node = link.parentElement; node && node !== page; node = node.parentElement) {
153
+ const { overflowY } = getComputedStyle(node);
154
+ if (!/auto|scroll|overlay/.test(overflowY) || node.scrollHeight <= node.clientHeight) {
155
+ continue;
156
+ }
157
+ if (node !== sections && !node.contains(sections)) {
158
+ scrollItemIntoView(link, { scroller: node });
159
+ }
160
+ return;
161
+ }
162
+ }
148
163
  get atEnd() {
149
164
  const doc = this.scroller;
150
165
  return Math.ceil(doc.scrollTop + doc.clientHeight) >= doc.scrollHeight - 1;
@@ -282,6 +297,9 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
282
297
  __decorate([
283
298
  property()
284
299
  ], HelixHeadlessOutline.prototype, "behavior", void 0);
300
+ __decorate([
301
+ property({ type: Boolean })
302
+ ], HelixHeadlessOutline.prototype, "follow", void 0);
285
303
  __decorate([
286
304
  property()
287
305
  ], HelixHeadlessOutline.prototype, "label", void 0);
@@ -49,6 +49,7 @@ import '../../select/index.js';
49
49
  * @csspart control-label - The label beside the number.
50
50
  * @csspart control-listbox - The list inside the panel.
51
51
  * @csspart control-listbox-empty - The text shown when the filter hides every option.
52
+ * @csspart control-listbox-error - The inner list's `error` part, shown while its `load-error` is set.
52
53
  * @csspart control-listbox-feedback - The inner list's message that says what is wrong with the value.
53
54
  * @csspart control-listbox-filter-icon - The icon beside the filter field.
54
55
  * @csspart control-listbox-filter-mark - The default icon beside the filter field, shown when none is slotted.
@@ -93,7 +93,7 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
93
93
  part="control"
94
94
  exportparts="feedback: control-feedback, hint: control-hint, indicator: control-indicator,
95
95
  label: control-label, listbox: control-listbox, listbox-empty: control-listbox-empty,
96
- listbox-feedback: control-listbox-feedback, listbox-filter-icon: control-listbox-filter-icon,
96
+ listbox-error: control-listbox-error, listbox-feedback: control-listbox-feedback, listbox-filter-icon: control-listbox-filter-icon,
97
97
  listbox-filter-mark: control-listbox-filter-mark, listbox-hint: control-listbox-hint,
98
98
  listbox-items: control-listbox-items, listbox-label: control-listbox-label,
99
99
  listbox-live: control-listbox-live, listbox-loading: control-listbox-loading, listbox-search: control-listbox-search,
@@ -0,0 +1,10 @@
1
+ import type { PinCase, PinType } from './root/pin-input-root.js';
2
+ /**
3
+ * Typed or pasted text as the code's characters: trimmed, with what `type`
4
+ * refuses dropped, and its letters cased as `letterCase` asks. Web Awesome's
5
+ * OTP input takes the same `case`.
6
+ *
7
+ * `numeric` keeps digits, `alphanumeric` keeps ASCII letters and digits, and
8
+ * `text` keeps everything.
9
+ */
10
+ export declare const cleanCode: (text: string, type: PinType, letterCase?: PinCase) => string;
@@ -0,0 +1,13 @@
1
+ export const cleanCode = (text, type, letterCase = 'preserve') => {
2
+ const trimmed = text.trim();
3
+ const kept = type === 'numeric'
4
+ ? trimmed.replace(/\D/g, '')
5
+ : type === 'alphanumeric'
6
+ ? trimmed.replace(/[^0-9A-Za-z]/g, '')
7
+ : trimmed;
8
+ return letterCase === 'upper'
9
+ ? kept.toUpperCase()
10
+ : letterCase === 'lower'
11
+ ? kept.toLowerCase()
12
+ : kept;
13
+ };
@@ -2,6 +2,8 @@ import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core'
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /** What a cell accepts, which decides the keyboard a phone offers. */
4
4
  export type PinType = 'alphanumeric' | 'numeric' | 'text';
5
+ /** How typed letters are cased: as typed, or turned upper or lower case. */
6
+ export type PinCase = 'lower' | 'preserve' | 'upper';
5
7
  declare const HelixHeadlessPinInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
6
8
  /**
7
9
  * @summary A field for a short code, with one box for each character.
@@ -33,6 +35,21 @@ declare const HelixHeadlessPinInput_base: (new (...args: any[]) => import("@fusi
33
35
  * and the right arrow moves to the next box. The label, hint, and feedback
34
36
  * follow the page's direction.
35
37
  *
38
+ * **Keyboard.** Each box is a tab stop of its own, as each is a real input.
39
+ *
40
+ * | Key | What it does |
41
+ * |---|---|
42
+ * | A character | Fills the box and moves to the next one. A longer run is read as a paste. |
43
+ * | Left, Right | Moves to the box before or after. |
44
+ * | Home, End | Moves to the first box, or to the box after the last character. |
45
+ * | Backspace | In an empty box, clears the box before it and moves there. |
46
+ * | Delete | Removes the character in the box and pulls the rest of the code back. |
47
+ * | Enter | Submits the form. |
48
+ *
49
+ * The row has `role="group"` and the field's name, and each box is named by
50
+ * `cell-label`, such as "Character 2 of 6". `helix-complete` is a
51
+ * `HelixCompleteEvent` sent once the last box is filled.
52
+ *
36
53
  * @since 0.1.0
37
54
  * @status stable
38
55
  * @category forms
@@ -55,6 +72,7 @@ declare const HelixHeadlessPinInput_base: (new (...args: any[]) => import("@fusi
55
72
  * @csspart label - The label above the boxes.
56
73
  * @csspart separator - The text `format` draws between two boxes.
57
74
  *
75
+ * @cssstate complete - Every box holds a character.
58
76
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
59
77
  */
60
78
  export declare class HelixHeadlessPinInput extends HelixHeadlessPinInput_base {
@@ -78,6 +96,12 @@ export declare class HelixHeadlessPinInput extends HelixHeadlessPinInput_base {
78
96
  * took: a refused last character submits nothing.
79
97
  */
80
98
  autosubmit: boolean;
99
+ /**
100
+ * How typed and pasted letters are cased: `upper`, `lower`, or `preserve`
101
+ * to keep them as typed. Web Awesome's OTP input takes the same `case`, for
102
+ * a code a service reads in one case.
103
+ */
104
+ case: PinCase;
81
105
  /**
82
106
  * Accessible name for one box, with `{index}` and `{length}` filled in.
83
107
  */
@@ -147,7 +171,7 @@ export declare class HelixHeadlessPinInput extends HelixHeadlessPinInput_base {
147
171
  private get cells();
148
172
  /** The code as characters, padded to the row so every box has one to show. */
149
173
  private get characters();
150
- /** Everything this box would accept, with the rest dropped. */
174
+ /** Everything this box would accept, with the rest dropped and cased. */
151
175
  private clean;
152
176
  /**
153
177
  * Writes a run of characters from one box onward.