@fusion.dev/helix-headless 0.147.0 → 0.148.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 (141) hide show
  1. package/custom-elements.json +4512 -790
  2. package/dist/accordion/item/accordion-item.d.ts +5 -2
  3. package/dist/accordion/item/accordion-item.js +11 -7
  4. package/dist/accordion/root/accordion-root.js +4 -6
  5. package/dist/announcement/root/announcement-root.d.ts +56 -5
  6. package/dist/announcement/root/announcement-root.js +124 -21
  7. package/dist/area-chart/root/area-chart-root.js +3 -4
  8. package/dist/badge/count.d.ts +14 -0
  9. package/dist/badge/count.js +20 -0
  10. package/dist/badge/root/badge-root.js +8 -22
  11. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  12. package/dist/blockquote/root/blockquote-root.js +85 -8
  13. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +2 -2
  14. package/dist/button-group/arrows.d.ts +14 -0
  15. package/dist/button-group/arrows.js +10 -0
  16. package/dist/button-group/root/button-group-root.d.ts +141 -3
  17. package/dist/button-group/root/button-group-root.js +142 -2
  18. package/dist/canvas/edge-pan.d.ts +61 -0
  19. package/dist/canvas/edge-pan.js +63 -0
  20. package/dist/canvas/node/canvas-node.d.ts +12 -2
  21. package/dist/canvas/node/canvas-node.js +6 -2
  22. package/dist/canvas/root/canvas-document.d.ts +10 -0
  23. package/dist/canvas/root/canvas-document.js +1 -0
  24. package/dist/canvas/root/canvas-root.d.ts +107 -5
  25. package/dist/canvas/root/canvas-root.js +140 -36
  26. package/dist/canvas/snap.d.ts +42 -0
  27. package/dist/canvas/snap.js +32 -0
  28. package/dist/carousel/root/carousel-root.d.ts +4 -3
  29. package/dist/carousel/root/carousel-root.js +2 -6
  30. package/dist/chat/root/chat-root.d.ts +13 -3
  31. package/dist/chat/root/chat-root.js +2 -6
  32. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  33. package/dist/checkbox-group/root/checkbox-group-root.js +18 -10
  34. package/dist/checkbox-group/select-all.d.ts +11 -0
  35. package/dist/checkbox-group/select-all.js +5 -0
  36. package/dist/code-block/root/code-block-root.d.ts +8 -6
  37. package/dist/code-block/root/code-block-root.js +4 -3
  38. package/dist/color-picker/root/color-picker-root.js +6 -12
  39. package/dist/color-picker/wheel.d.ts +11 -0
  40. package/dist/color-picker/wheel.js +9 -0
  41. package/dist/combobox/root/combobox-root.d.ts +70 -11
  42. package/dist/combobox/root/combobox-root.js +68 -18
  43. package/dist/command/root/command-root.d.ts +4 -3
  44. package/dist/data-table/root/data-table-root.js +17 -44
  45. package/dist/description-list/item/description-list-item.d.ts +4 -0
  46. package/dist/description-list/item/description-list-item.js +7 -1
  47. package/dist/divider/root/divider-root.d.ts +45 -16
  48. package/dist/divider/root/divider-root.js +28 -4
  49. package/dist/drilldown/item/drilldown-item.js +3 -4
  50. package/dist/element-events.generated.js +1 -0
  51. package/dist/empty/root/empty-root.d.ts +17 -3
  52. package/dist/empty/root/empty-root.js +29 -5
  53. package/dist/file-input/root/file-input-root.js +3 -4
  54. package/dist/file-input/status.js +2 -1
  55. package/dist/filter-input/chip/filter-chip.js +2 -3
  56. package/dist/format-date/fields.d.ts +20 -0
  57. package/dist/format-date/fields.js +14 -0
  58. package/dist/format-date/options.d.ts +2 -1
  59. package/dist/format-date/options.js +9 -38
  60. package/dist/format-date/root/format-date-root.d.ts +35 -3
  61. package/dist/format-date/root/format-date-root.js +24 -32
  62. package/dist/graph/root/graph-root.d.ts +23 -5
  63. package/dist/graph/root/graph-root.js +17 -20
  64. package/dist/heatmap/root/heatmap-root.d.ts +12 -0
  65. package/dist/heatmap/root/heatmap-root.js +26 -11
  66. package/dist/image/root/image-root.d.ts +11 -0
  67. package/dist/kbd/root/kbd-root.js +1 -2
  68. package/dist/legend/root/legend-root.js +4 -17
  69. package/dist/list/root/list-root.js +8 -22
  70. package/dist/listbox/root/listbox-root.d.ts +119 -24
  71. package/dist/listbox/root/listbox-root.js +130 -132
  72. package/dist/map/marker/map-marker.d.ts +2 -1
  73. package/dist/map/root/map-root.d.ts +40 -2
  74. package/dist/map/root/map-root.js +64 -23
  75. package/dist/megamenu/root/megamenu-root.js +4 -11
  76. package/dist/menu/root/menu-root.d.ts +2 -2
  77. package/dist/navigation/root/navigation-root.js +5 -8
  78. package/dist/notifications/group/notification-group.d.ts +8 -1
  79. package/dist/notifications/group/notification-group.js +12 -6
  80. package/dist/notifications/notification/notification.d.ts +12 -1
  81. package/dist/notifications/notification/notification.js +3 -1
  82. package/dist/notifications/root/notifications-root.d.ts +41 -5
  83. package/dist/notifications/root/notifications-root.js +27 -7
  84. package/dist/object-inspector/root/object-inspector-root.d.ts +5 -1
  85. package/dist/object-inspector/root/object-inspector-root.js +21 -11
  86. package/dist/pagination/size/pagination-size.d.ts +1 -0
  87. package/dist/pagination/size/pagination-size.js +1 -1
  88. package/dist/pin-input/code.d.ts +23 -0
  89. package/dist/pin-input/code.js +25 -0
  90. package/dist/pin-input/root/pin-input-root.js +9 -23
  91. package/dist/qr-code/root/qr-code-root.d.ts +14 -3
  92. package/dist/qr-code/root/qr-code-root.js +19 -5
  93. package/dist/scatter-chart/measures.d.ts +22 -0
  94. package/dist/scatter-chart/measures.js +11 -0
  95. package/dist/scatter-chart/root/scatter-chart-root.d.ts +2 -6
  96. package/dist/scatter-chart/root/scatter-chart-root.js +6 -19
  97. package/dist/select/root/select-root.d.ts +56 -8
  98. package/dist/select/root/select-root.js +33 -9
  99. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  100. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  101. package/dist/speed-dial/index.d.ts +1 -0
  102. package/dist/speed-dial/index.js +1 -0
  103. package/dist/speed-dial/item/speed-dial-item.d.ts +26 -1
  104. package/dist/speed-dial/item/speed-dial-item.js +41 -3
  105. package/dist/speed-dial/long-press.d.ts +50 -0
  106. package/dist/speed-dial/long-press.js +48 -0
  107. package/dist/speed-dial/root/speed-dial-root.d.ts +80 -1
  108. package/dist/speed-dial/root/speed-dial-root.js +114 -5
  109. package/dist/splitter/root/splitter-root.d.ts +7 -3
  110. package/dist/splitter/root/splitter-root.js +16 -3
  111. package/dist/table/cell/table-cell.d.ts +8 -1
  112. package/dist/table/cell/table-cell.js +6 -2
  113. package/dist/table/grid-controller.d.ts +117 -0
  114. package/dist/table/grid-controller.js +293 -0
  115. package/dist/table/root/table-root.d.ts +47 -3
  116. package/dist/table/root/table-root.js +81 -24
  117. package/dist/table/row/table-row.d.ts +13 -1
  118. package/dist/table/row/table-row.js +20 -6
  119. package/dist/tag/group/tag-group.js +6 -15
  120. package/dist/time-picker/root/time-picker-root.d.ts +1 -0
  121. package/dist/time-picker/root/time-picker-root.js +3 -0
  122. package/dist/toast/item/toast-item.d.ts +6 -3
  123. package/dist/toast/item/toast-item.js +1 -0
  124. package/dist/toast/root/toast-root.d.ts +13 -1
  125. package/dist/toast/root/toast-root.js +20 -14
  126. package/dist/toggle/group/toggle-group.js +3 -5
  127. package/dist/toolbar/root/toolbar-root.d.ts +31 -0
  128. package/dist/toolbar/root/toolbar-root.js +39 -8
  129. package/dist/tooltip/root/tooltip-root.js +1 -2
  130. package/dist/video/root/video-root.d.ts +11 -0
  131. package/dist/video/root/video-root.js +15 -1
  132. package/dist/virtual-list/root/virtual-list-root.js +6 -18
  133. package/lint.json +1 -1
  134. package/package.json +5 -5
  135. package/types/jsx.d.ts +2005 -266
  136. package/types/svelte.d.ts +1 -0
  137. package/types/vue.d.ts +338 -62
  138. package/dist/kbd/keys.d.ts +0 -29
  139. package/dist/kbd/keys.js +0 -52
  140. package/dist/tooltip/focus-target.d.ts +0 -6
  141. package/dist/tooltip/focus-target.js +0 -9
@@ -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, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, } from '@fusion.dev/helix-core';
7
+ import { customElement, fillTemplate, focusItemOf, formatNumbers, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { buildings, cloudRain, dotsNine, leaf, mountains, path, polygon, stack, target, } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -89,15 +89,13 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
89
89
  };
90
90
  this.stop = 0;
91
91
  this.keys = new KeyboardNavigationController(this, {
92
- items: () => this.markers.map((marker, at) => ({
93
- contains: (node) => node !== null && (marker.contains(node) || marker.shadowRoot?.contains(node) === true),
92
+ items: () => this.markers.map((marker, at) => focusItemOf(marker, {
94
93
  disabled: marker.gatheredBy !== undefined || !placed(marker),
95
- focusItem: () => {
94
+ focus: () => {
96
95
  this.stop = at;
97
96
  marker.focus();
98
97
  },
99
- select: () => { },
100
- selected: false,
98
+ owns: (node) => node !== null && (marker.contains(node) || marker.shadowRoot?.contains(node) === true),
101
99
  })),
102
100
  origin: 'focus',
103
101
  selects: () => false,
@@ -105,10 +103,26 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
105
103
  this.handleFocusIn = (event) => {
106
104
  const marker = this.markerOf(event);
107
105
  const at = marker ? this.markers.indexOf(marker) : -1;
106
+ this.focused = at === -1 ? undefined : marker;
108
107
  if (at !== -1) {
109
108
  this.stop = at;
110
109
  }
111
110
  };
111
+ this.handleFocusOut = (event) => {
112
+ const marker = this.markerOf(event);
113
+ if (!marker || marker !== this.focused) {
114
+ return;
115
+ }
116
+ if (event.relatedTarget) {
117
+ this.focused = undefined;
118
+ return;
119
+ }
120
+ queueMicrotask(() => {
121
+ if (marker.isConnected && this.focused === marker) {
122
+ this.focused = undefined;
123
+ }
124
+ });
125
+ };
112
126
  this.handleClick = (event) => {
113
127
  const marker = this.markerOf(event);
114
128
  if (marker) {
@@ -267,6 +281,23 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
267
281
  .composedPath()
268
282
  .find((one) => one.isMapMarker === true);
269
283
  }
284
+ refocus(stop) {
285
+ const gone = this.focused;
286
+ if (!gone || this.markers.includes(gone)) {
287
+ return;
288
+ }
289
+ this.focused = undefined;
290
+ const active = document.activeElement;
291
+ if (active !== null && active !== document.body) {
292
+ return;
293
+ }
294
+ if (stop) {
295
+ stop.focus();
296
+ }
297
+ else {
298
+ this.partNamed('region')?.focus();
299
+ }
300
+ }
270
301
  activate(marker) {
271
302
  if (marker.gathered.length > 1) {
272
303
  marker.dispatchEvent(new HelixMapClusterEvent({ labels: [...marker.gathered] }));
@@ -290,6 +321,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
290
321
  this.addEventListener(HelixMapPartChangeEvent.eventName, this.handlePartChange);
291
322
  this.addEventListener('click', this.handleClick);
292
323
  this.addEventListener('focusin', this.handleFocusIn);
324
+ this.addEventListener('focusout', this.handleFocusOut);
293
325
  this.addEventListener('keydown', this.handleMarkerKeydown);
294
326
  this.addEventListener('keyup', this.handleMarkerKeyup);
295
327
  this.hiding = new MutationObserver(() => this.requestUpdate());
@@ -304,6 +336,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
304
336
  this.removeEventListener(HelixMapPartChangeEvent.eventName, this.handlePartChange);
305
337
  this.removeEventListener('click', this.handleClick);
306
338
  this.removeEventListener('focusin', this.handleFocusIn);
339
+ this.removeEventListener('focusout', this.handleFocusOut);
307
340
  this.removeEventListener('keydown', this.handleMarkerKeydown);
308
341
  this.removeEventListener('keyup', this.handleMarkerKeyup);
309
342
  }
@@ -331,6 +364,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
331
364
  for (const marker of markers) {
332
365
  marker.current = marker === stop;
333
366
  }
367
+ this.refocus(stop);
334
368
  if (changed.has('cluster')) {
335
369
  this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'place' }));
336
370
  }
@@ -371,9 +405,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
371
405
  }}
372
406
  @keydown=${this.handleMenuKeydown}
373
407
  >
374
- <svg aria-hidden="true" fill="currentColor" height="16" viewBox=${stack.viewBox} width="16">
375
- ${unsafeSVG(stack.content)}
376
- </svg>
408
+ <slot name="layers-icon">
409
+ <svg aria-hidden="true" fill="currentColor" height="16" viewBox=${stack.viewBox} width="16">
410
+ ${unsafeSVG(stack.content)}
411
+ </svg>
412
+ </slot>
377
413
  </button>
378
414
  <div
379
415
  part="layers-menu"
@@ -431,38 +467,40 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
431
467
  return html `<div part="compass">
432
468
  ${rotate
433
469
  ? html `<button part="rotate-left" aria-label=${this.rotateLeftLabel} type="button" @click=${() => this.rotate(-ROTATE_STEP)}>
434
- ↺
470
+ <slot name="rotate-left-icon">↺</slot>
435
471
  </button>`
436
472
  : nothing}
437
473
  ${pan
438
474
  ? html `<button part="pan-up" aria-label=${this.panLabel('up')} type="button" @click=${() => this.pan('up')}>
439
- ${this.panGlyph('up')}
475
+ <slot name="pan-up-icon">${this.panGlyph('up')}</slot>
440
476
  </button>`
441
477
  : nothing}
442
478
  ${rotate
443
479
  ? html `<button part="rotate-right" aria-label=${this.rotateRightLabel} type="button" @click=${() => this.rotate(ROTATE_STEP)}>
444
- ↻
480
+ <slot name="rotate-right-icon">↻</slot>
445
481
  </button>`
446
482
  : nothing}
447
483
  ${pan
448
484
  ? html `<button part="pan-start" aria-label=${this.panLabel('start')} type="button" @click=${() => this.pan('start')}>
449
- ${this.panGlyph('start')}
485
+ <slot name="pan-start-icon">${this.panGlyph('start')}</slot>
450
486
  </button>`
451
487
  : nothing}
452
488
  <button part="face-north" aria-label=${this.faceNorthLabel} type="button" style=${`--map-needle-turn: ${-this.bearing}deg`} @click=${() => this.faceNorth()}>
453
- <svg part="needle" aria-hidden="true" viewBox="0 0 16 16">
454
- <path data-end="north" d="M8 1.5 10.5 8h-5Z" />
455
- <path data-end="south" d="M8 14.5 5.5 8h5Z" />
456
- </svg>
489
+ <slot name="face-north-icon">
490
+ <svg part="needle" aria-hidden="true" viewBox="0 0 16 16">
491
+ <path data-end="north" d="M8 1.5 10.5 8h-5Z" />
492
+ <path data-end="south" d="M8 14.5 5.5 8h5Z" />
493
+ </svg>
494
+ </slot>
457
495
  </button>
458
496
  ${pan
459
497
  ? html `<button part="pan-end" aria-label=${this.panLabel('end')} type="button" @click=${() => this.pan('end')}>
460
- ${this.panGlyph('end')}
498
+ <slot name="pan-end-icon">${this.panGlyph('end')}</slot>
461
499
  </button>`
462
500
  : nothing}
463
501
  ${pan
464
502
  ? html `<button part="pan-down" aria-label=${this.panLabel('down')} type="button" @click=${() => this.pan('down')}>
465
- ${this.panGlyph('down')}
503
+ <slot name="pan-down-icon">${this.panGlyph('down')}</slot>
466
504
  </button>`
467
505
  : nothing}
468
506
  </div>`;
@@ -486,7 +524,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
486
524
  type="button"
487
525
  @click=${this.toggleFullscreen}
488
526
  >
489
- ⤢
527
+ <slot name="fullscreen-icon">⤢</slot>
490
528
  </button>`
491
529
  : nothing}
492
530
  ${menu}
@@ -498,15 +536,15 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
498
536
  type="button"
499
537
  @click=${() => this.tiltTo(this.pitch > 0 ? 0 : TILTED)}
500
538
  >
501
- 3D
539
+ <slot name="tilt-icon">3D</slot>
502
540
  </button>`
503
541
  : nothing}
504
542
  ${zoom
505
543
  ? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
506
- +
544
+ <slot name="zoom-in-icon">+</slot>
507
545
  </button>
508
546
  <button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
509
- −
547
+ <slot name="zoom-out-icon">−</slot>
510
548
  </button>`
511
549
  : nothing}
512
550
  ${this.drawsCompass ? this.renderCompass(pan, rotate) : nothing}
@@ -558,6 +596,9 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
558
596
  hostAria(this.internals, { label: this.label });
559
597
  this.toggleState('fullscreen', this.filled);
560
598
  this.toggleState('idle', this.resting);
599
+ this.toggleState('layers-open', this.layersOpen);
600
+ this.toggleState('rotated', Math.round(this.bearing) % 360 !== 0);
601
+ this.toggleState('tilted', Math.round(this.pitch) !== 0);
561
602
  }
562
603
  renderLayers() {
563
604
  const layers = this.layers.filter((layer) => this.shown(layer));
@@ -5,7 +5,7 @@ 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 HelixHeadlessMegamenu_1;
8
- import { anchorElement, customElement, deepContains, focusableElements, HelixElement, KeyboardNavigationController, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
8
+ import { anchorElement, customElement, deepContains, focusableElements, focusItemOf, HelixElement, KeyboardNavigationController, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { HandOver } from '../hand-over.js';
@@ -30,16 +30,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
30
30
  });
31
31
  this.keys = new KeyboardNavigationController(this, {
32
32
  items: () => !this.parentElement?.shadowRoot
33
- ? HelixHeadlessMegamenu_1.bar.alongside(this).map((megamenu) => ({
34
- contains: (node) => {
35
- const control = megamenu.trigger;
36
- return node === control || control.contains(node);
37
- },
38
- disabled: false,
39
- focusItem: () => megamenu.trigger.focus(),
40
- select: () => { },
41
- selected: false,
42
- }))
33
+ ? HelixHeadlessMegamenu_1.bar
34
+ .alongside(this)
35
+ .map((megamenu) => focusItemOf(megamenu.trigger))
43
36
  : [],
44
37
  origin: 'focus',
45
38
  reserved: () => ['ArrowDown', 'ArrowUp'],
@@ -25,8 +25,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
25
25
  * onto your element after it upgrades causes a hydration mismatch.
26
26
  *
27
27
  * Group rows under a heading with `Menu.Section`, which is a `group` named by
28
- * its heading. Between runs of rows, a `<helix-divider announced>` is a
29
- * separator a screen reader announces.
28
+ * its heading. Between runs of rows, a `<helix-divider>` is a separator a
29
+ * screen reader announces.
30
30
  *
31
31
  * Keys: Enter, Space or Down Arrow on the trigger opens the menu on its first
32
32
  * row, and Up Arrow on its last. In the menu, Up and Down Arrow move between
@@ -4,23 +4,20 @@ 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 { AriaForwardController, customElement, focusableElements, HelixElement, IndicatorController, KeyboardNavigationController, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, focusableElements, focusItemOf, HelixElement, IndicatorController, KeyboardNavigationController, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
11
11
  const navigable = (item) => {
12
12
  const control = () => item.navigationControl ??
13
13
  (item.matches(FOCUSABLE) ? item : focusableElements(item)[0]);
14
- return {
15
- contains: (node) => {
14
+ return focusItemOf(item, {
15
+ focus: () => control()?.focus(),
16
+ owns: (node) => {
16
17
  const target = control();
17
18
  return target !== undefined && node !== null && (node === target || target.contains(node));
18
19
  },
19
- disabled: false,
20
- focusItem: () => control()?.focus(),
21
- select: () => { },
22
- selected: false,
23
- };
20
+ });
24
21
  };
25
22
  let HelixHeadlessNavigation = class HelixHeadlessNavigation extends HelixElement {
26
23
  constructor() {
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import type { HelixHeadlessNotification } from '../notification/notification.js';
4
4
  declare const HelixHeadlessNotificationGroup_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
@@ -134,6 +134,13 @@ export declare class HelixHeadlessNotificationGroup extends HelixHeadlessNotific
134
134
  * is anything behind it. The one row is the evidence.
135
135
  */
136
136
  private assign;
137
+ /**
138
+ * Hears a row arrive after the render. The slot is assigned by hand, so a
139
+ * new row is in no slot and sends no `slotchange` to say it came. The feed
140
+ * the group is in is asked to update as well, since it counts the group's
141
+ * rows and does not watch inside the group.
142
+ */
143
+ protected readonly childWatch: ChildrenController;
137
144
  private readonly handleToggle;
138
145
  protected render(): TemplateResult;
139
146
  }
@@ -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, HelixElement, hostAria, MixinControllerOpen } from '@fusion.dev/helix-core';
7
+ import { assignSlots, ChildrenController, customElement, HelixElement, hostAria, MixinControllerOpen, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  const TRIGGER_ID = 'trigger';
@@ -16,6 +16,10 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
16
16
  this.collapsible = false;
17
17
  this.label = '';
18
18
  this.isNotificationGroup = true;
19
+ this.childWatch = new ChildrenController(this, () => {
20
+ this.requestUpdate();
21
+ this.parentElement?.requestUpdate?.();
22
+ });
19
23
  this.handleToggle = () => {
20
24
  this.toggleOpen();
21
25
  };
@@ -58,11 +62,13 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
58
62
  }
59
63
  return;
60
64
  }
61
- slot.assign(...showing);
62
- const action = this.renderRoot?.querySelector?.('slot[name="actions"]');
63
- action?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === 'actions'));
64
- const label = this.renderRoot?.querySelector?.('slot[name="label"]');
65
- label?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === 'label'));
65
+ assignSlots(this.renderRoot, this.children, (child) => {
66
+ const row = child;
67
+ if (showing.includes(row)) {
68
+ return '';
69
+ }
70
+ return items.includes(row) || !child.hasAttribute('slot') ? 'folded' : undefined;
71
+ });
66
72
  }
67
73
  render() {
68
74
  const counted = this.counted;
@@ -53,6 +53,16 @@ declare const HelixHeadlessNotification_base: (new (...args: any[]) => import("@
53
53
  * @csspart label - What happened, on the first line.
54
54
  * @csspart media - The leading slot's box.
55
55
  * @csspart time - When it happened, formatted for people to read.
56
+ * @csspart time-day - Format Date's `day`, forwarded for a time drawn as fields. An elapsed time draws none.
57
+ * @csspart time-day-period - Format Date's `day-period`, forwarded for a time drawn as fields. An elapsed time draws none.
58
+ * @csspart time-era - Format Date's `era`, forwarded for a time drawn as fields. An elapsed time draws none.
59
+ * @csspart time-hour - Format Date's `hour`, forwarded for a time drawn as fields. An elapsed time draws none.
60
+ * @csspart time-minute - Format Date's `minute`, forwarded for a time drawn as fields. An elapsed time draws none.
61
+ * @csspart time-month - Format Date's `month`, forwarded for a time drawn as fields. An elapsed time draws none.
62
+ * @csspart time-second - Format Date's `second`, forwarded for a time drawn as fields. An elapsed time draws none.
63
+ * @csspart time-time-zone-name - Format Date's `time-zone-name`, forwarded for a time drawn as fields. An elapsed time draws none.
64
+ * @csspart time-weekday - Format Date's `weekday`, forwarded for a time drawn as fields. An elapsed time draws none.
65
+ * @csspart time-year - Format Date's `year`, forwarded for a time drawn as fields. An elapsed time draws none.
56
66
  * @csspart trailing - The column holding the time and the controls.
57
67
  *
58
68
  * @cssstate actions - While the actions slot holds something, so the words above make room for it.
@@ -210,7 +220,8 @@ export declare class HelixHeadlessNotification extends HelixHeadlessNotification
210
220
  * already named for its role: the child publishes `time`, the role here is
211
221
  * `time`, and naming both would leave `::part(time)` matching two elements.
212
222
  * What a consumer wants to reach is the `<time>`, so that is what the name
213
- * reaches.
223
+ * reaches. Format Date's field parts go on under `time-`, as every composed
224
+ * child's do, though an elapsed time draws no fields.
214
225
  */
215
226
  private renderTime;
216
227
  protected render(): TemplateResult;
@@ -103,7 +103,9 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
103
103
  renderTime() {
104
104
  const stamp = this.tag('format-date');
105
105
  return html `<${stamp}
106
- exportparts="time"
106
+ exportparts="time, day: time-day, day-period: time-day-period, era: time-era, hour: time-hour,
107
+ minute: time-minute, month: time-month, second: time-second,
108
+ time-zone-name: time-time-zone-name, weekday: time-weekday, year: time-year"
107
109
  elapsed
108
110
  date=${this.at}
109
111
  locale=${this.locale}
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import type { HelixHeadlessNotificationGroup } from '../group/notification-group.js';
4
4
  import type { HelixHeadlessNotification } from '../notification/notification.js';
@@ -35,6 +35,11 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
35
35
  * nothing more. While the feed is loading the button stays, so focus stays on
36
36
  * it, and it is `aria-disabled` and sends nothing.
37
37
  *
38
+ * **`load-error` says a page could not be fetched**, such as "Could not load
39
+ * notifications": it shows under the rows there are, in the `error` part, in
40
+ * place of the empty and loading lines, and a screen reader hears it. Clear it
41
+ * when you ask again.
42
+ *
38
43
  * @since 0.1.0
39
44
  * @status stable
40
45
  * @category feedback
@@ -43,6 +48,7 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
43
48
  *
44
49
  * @slot - The notifications.
45
50
  * @slot empty - Replaces the words of `empty-text` in the `empty` part, such as a picture and a line of your own. The live region still says `empty-text`.
51
+ * @slot error - Replaces the words of `load-error` in the `error` part, such as a line with a retry button. The live region still says `load-error`.
46
52
  * @slot footer - Below the list, for a link to everything.
47
53
  * @slot header - Above the list, for a title and the mark-all control.
48
54
  * @slot loading - Replaces the words of `loading-text` in the `loading` part, such as a loader. The live region still says `loading-text`.
@@ -52,15 +58,17 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
52
58
  * @event {HelixReadEvent} helix-read - Fired by an item when it is marked read or unread, and by the feed when everything is marked read. Cancelable.
53
59
  *
54
60
  * @csspart empty - Shown when there is nothing in the feed.
61
+ * @csspart error - Shown while `load-error` is set, under any rows there are, in place of the empty and loading lines.
55
62
  * @csspart footer - The region below the list.
56
63
  * @csspart header - The region above the list.
57
64
  * @csspart list - The list itself.
58
- * @csspart live - A copy of the empty and loading messages, in a live region that screen readers announce when they change. Visually hidden in the styled tier.
65
+ * @csspart live - A copy of the empty, loading and error messages, in a live region that screen readers announce when they change. Visually hidden in the styled tier.
59
66
  * @csspart loading - Shown while the feed is loading.
60
67
  * @csspart mark-all - The control that marks everything read.
61
68
  * @csspart more - The button after the list that asks for more, while `load-more` is set. It is `aria-disabled` while the feed is loading.
62
69
  *
63
- * @cssstate empty - While the feed holds no notification and is not loading. Never on the server, which cannot count the rows.
70
+ * @cssstate empty - While the feed holds no notification and is not loading, and `load-error` is not set. Never on the server, which cannot count the rows.
71
+ * @cssstate error - While `load-error` is set.
64
72
  * @cssstate loading - While `loading` is set.
65
73
  * @cssstate media - While a notification in the list holds something in its media slot, so every row keeps that column.
66
74
  */
@@ -79,6 +87,15 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
79
87
  * The feed's name, read by screen readers.
80
88
  */
81
89
  label: string;
90
+ /**
91
+ * What the feed says when a page of it could not be loaded, such as after a
92
+ * request to a server fails. Empty, null or no attribute is no error, so
93
+ * removing the attribute clears it.
94
+ *
95
+ * Set, it shows under the rows there are, in place of `empty-text` and
96
+ * `loading-text`, and a screen reader hears it. Clear it when you ask again.
97
+ */
98
+ loadError: string | null;
82
99
  /**
83
100
  * Whether to draw a button after the list that asks for the next page, by
84
101
  * sending `helix-load-more`. Take it off once there is nothing more.
@@ -186,6 +203,13 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
186
203
  markEveryoneRead(): number;
187
204
  /** Items arrive from the light DOM, which changes no property here. */
188
205
  private readonly handleSlotChange;
206
+ /**
207
+ * Hears a row arrive after the render. The list slot is assigned by hand, so
208
+ * a new row is in no slot and sends no `slotchange`: without this it was
209
+ * drawn nowhere and an emptied feed kept saying it was empty. A row added to
210
+ * a group is the group's to hear, and the group asks the feed to count again.
211
+ */
212
+ protected readonly childWatch: ChildrenController;
189
213
  /**
190
214
  * An item's own read event, heard on the way past, so the count follows.
191
215
  *
@@ -225,10 +249,22 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
225
249
  disconnectedCallback(): void;
226
250
  protected willUpdate(changed: PropertyValues): void;
227
251
  /**
228
- * The empty or the loading line, each around a slot of the same name whose
229
- * fallback is the words, so a page can put its own in.
252
+ * The error, the empty or the loading line, each around a slot of the same
253
+ * name whose fallback is the words, so a page can put its own in. The error
254
+ * shows under the rows there are, since a page that failed is news even to
255
+ * a feed that holds some.
230
256
  */
231
257
  protected renderStatus(hasContent: boolean): TemplateResult | typeof nothing;
258
+ /**
259
+ * The error before the shared words, which it wins over.
260
+ *
261
+ * **One region whose words change**, as the shared one is. The error was a
262
+ * second template, so setting it put a new region in the tree with its
263
+ * words already in it, which a reader may not hear.
264
+ */
265
+ protected renderLive(hasContent: boolean): TemplateResult;
266
+ /** Whether `load-error` holds words, which a removed attribute does not. */
267
+ private get failed();
232
268
  /**
233
269
  * Says whether anything in the feed has media, so every item can hold the
234
270
  * column open whether or not it has one of its own.
@@ -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, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { assignSlots, ChildrenController, customElement, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { feedStep } from '../feed-keys.js';
@@ -12,6 +12,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
12
12
  constructor() {
13
13
  super(...arguments);
14
14
  this.label = '';
15
+ this.loadError = '';
15
16
  this.loadMore = false;
16
17
  this.loadMoreLabel = 'Load more';
17
18
  this.markAll = false;
@@ -20,6 +21,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
20
21
  this.assign();
21
22
  this.requestUpdate();
22
23
  };
24
+ this.childWatch = new ChildrenController(this, this.handleSlotChange);
23
25
  this.handleRead = () => {
24
26
  queueMicrotask(() => this.requestUpdate());
25
27
  };
@@ -138,11 +140,8 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
138
140
  }
139
141
  return;
140
142
  }
141
- list.assign(...this.rows);
142
- for (const name of ['header', 'footer', 'empty', 'loading']) {
143
- const slot = root?.querySelector?.(`slot[name="${name}"]`);
144
- slot?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === name));
145
- }
143
+ const rows = this.rows;
144
+ assignSlots(root, this.children, (child) => rows.includes(child) ? 'items' : child.getAttribute('slot') === 'items' ? '' : undefined);
146
145
  }
147
146
  get reachable() {
148
147
  return this.items.filter((row) => !('assignedSlot' in row) || row.assignedSlot !== null);
@@ -178,10 +177,15 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
178
177
  willUpdate(changed) {
179
178
  super.willUpdate(changed);
180
179
  hostAria(this.internals, { label: this.label || 'Notifications', role: 'region' });
180
+ const failed = this.failed;
181
+ this.toggleState('error', failed);
181
182
  this.toggleState('loading', this.loading);
182
- this.toggleState('empty', !this.loading && this.childrenKnown && this.items.length === 0);
183
+ this.toggleState('empty', !this.loading && !failed && this.childrenKnown && this.items.length === 0);
183
184
  }
184
185
  renderStatus(hasContent) {
186
+ if (this.failed) {
187
+ return html `<p part="error"><slot name="error">${this.loadError}</slot></p>`;
188
+ }
185
189
  if (!this.loading && hasContent) {
186
190
  return nothing;
187
191
  }
@@ -189,6 +193,19 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
189
193
  ? html `<p part="loading"><slot name="loading">${this.loadingText}</slot></p>`
190
194
  : html `<p part="empty"><slot name="empty">${this.emptyText}</slot></p>`;
191
195
  }
196
+ renderLive(hasContent) {
197
+ const said = this.failed
198
+ ? this.loadError
199
+ : this.loading
200
+ ? this.loadingText
201
+ : hasContent
202
+ ? this.announced
203
+ : this.emptyText;
204
+ return html `<span part="live" role="status">${said}</span>`;
205
+ }
206
+ get failed() {
207
+ return Boolean(this.loadError);
208
+ }
192
209
  markMedia() {
193
210
  this.toggleState('media', this.items.some((item) => item.querySelector(':scope > [slot="media"]') !== null));
194
211
  }
@@ -248,6 +265,9 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
248
265
  __decorate([
249
266
  property()
250
267
  ], HelixHeadlessNotifications.prototype, "label", void 0);
268
+ __decorate([
269
+ property({ attribute: 'load-error' })
270
+ ], HelixHeadlessNotifications.prototype, "loadError", void 0);
251
271
  __decorate([
252
272
  property({ attribute: 'load-more', type: Boolean })
253
273
  ], HelixHeadlessNotifications.prototype, "loadMore", void 0);
@@ -351,7 +351,7 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
351
351
  * box, or in the window where the box does not scroll, less one, so the row
352
352
  * at one edge lands at the other.
353
353
  */
354
- private pageRows;
354
+ private pageStep;
355
355
  /** A press opens or closes the row it lands on, once that row is the active one. */
356
356
  private handleRowClick;
357
357
  /** Text typed in the filter field, asked for before the tree narrows to it. */
@@ -380,6 +380,10 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
380
380
  private renderRow;
381
381
  /** Whether a row of the tree holds focus. */
382
382
  private hasFocusedRow;
383
+ /** Where the row holding focus is among the visible rows, or nothing while none does. */
384
+ private focusedIndex;
385
+ /** Focus on the tree itself, its stop while the active row is not drawn, goes on to that row. */
386
+ private readonly handleTreeFocus;
383
387
  /**
384
388
  * A row that takes focus some other way than the keys, from a script or a
385
389
  * screen reader, becomes the active row, so focus and the keys agree.