@fusion.dev/helix-headless 0.218.0 → 0.219.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.
- package/api.json +1 -1
- package/custom-elements.json +1 -1
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +13 -2
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +19 -9
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +16 -1
- package/dist/breadcrumbs/root/breadcrumbs-root.js +6 -0
- package/dist/button/root/button-root.d.ts +33 -0
- package/dist/button/root/button-root.js +13 -3
- package/dist/canvas/root/canvas-root.d.ts +11 -0
- package/dist/canvas/root/canvas-root.js +1 -0
- package/dist/chat/composer/chat-composer.d.ts +221 -6
- package/dist/chat/composer/chat-composer.js +249 -19
- package/dist/chat/message/chat-message.d.ts +7 -0
- package/dist/chat/message/chat-message.js +2 -1
- package/dist/chat/root/chat-root.d.ts +1 -0
- package/dist/chat/root/chat-root.js +1 -1
- package/dist/copy-button/root/copy-button-root.js +3 -3
- package/dist/device-screen/root/device-screen-root.d.ts +11 -0
- package/dist/device-screen/root/device-screen-root.js +22 -10
- package/dist/dialog/root/dialog-root.js +6 -1
- package/dist/drilldown/item/drilldown-item.js +1 -1
- package/dist/element-events.generated.js +1 -0
- package/dist/filter-input/root/filter-input-root.d.ts +4 -3
- package/dist/filter-input/root/filter-input-root.js +1 -20
- package/dist/graph/root/graph-root.d.ts +11 -0
- package/dist/graph/root/graph-root.js +4 -1
- package/dist/input/root/input-root.d.ts +6 -0
- package/dist/input/root/input-root.js +7 -0
- package/dist/lightbox/root/lightbox-root.d.ts +4 -1
- package/dist/lightbox/root/lightbox-root.js +10 -8
- package/dist/listbox/root/listbox-root.d.ts +21 -0
- package/dist/listbox/root/listbox-root.js +37 -2
- package/dist/log/group/log-group.d.ts +7 -1
- package/dist/log/group/log-group.js +14 -1
- package/dist/log/root/log-root.d.ts +13 -3
- package/dist/log/root/log-root.js +5 -2
- package/dist/map/root/map-root.d.ts +11 -0
- package/dist/map/root/map-root.js +28 -12
- package/dist/menu/root/menu-root.d.ts +8 -0
- package/dist/menu/root/menu-root.js +22 -1
- package/dist/menu/section/menu-section.d.ts +10 -0
- package/dist/menu/section/menu-section.js +10 -0
- package/dist/meter/index.d.ts +1 -0
- package/dist/meter/index.js +1 -0
- package/dist/meter/root/meter-root.d.ts +16 -1
- package/dist/meter/root/meter-root.js +27 -11
- package/dist/meter/segment/meter-segment.d.ts +74 -0
- package/dist/meter/segment/meter-segment.js +47 -0
- package/dist/scroll-area/root/scroll-area-root.d.ts +20 -3
- package/dist/scroll-area/root/scroll-area-root.js +23 -4
- package/dist/spatial-bar-chart/bars.d.ts +125 -5
- package/dist/spatial-bar-chart/bars.js +194 -17
- package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +148 -6
- package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +285 -16
- package/dist/splitter/root/splitter-root.js +1 -0
- package/dist/tabs/root/tabs-root.d.ts +4 -0
- package/dist/tabs/tab/tab.d.ts +1 -0
- package/dist/tabs/tab/tab.js +2 -2
- package/dist/tag/root/tag-root.d.ts +1 -1
- package/dist/terminal/root/terminal-root.d.ts +11 -0
- package/dist/terminal/root/terminal-root.js +23 -10
- package/dist/toolbar/root/toolbar-root.d.ts +4 -1
- package/dist/tooltip/root/tooltip-root.d.ts +23 -1
- package/dist/tooltip/root/tooltip-root.js +28 -5
- package/dist/tour/root/tour-root.d.ts +1 -0
- package/dist/tour/root/tour-root.js +4 -1
- package/dist/tour/stop/tour-stop.js +1 -0
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1373 -52
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +312 -17
- package/types/words.d.ts +9 -1
|
@@ -5,6 +5,7 @@ import { html, nothing } from 'lit';
|
|
|
5
5
|
import { property, state } from 'lit/decorators.js';
|
|
6
6
|
import { html as staticHtml } from 'lit/static-html.js';
|
|
7
7
|
import '../../icon-button/index.js';
|
|
8
|
+
import '../../tooltip/index.js';
|
|
8
9
|
import { live } from 'lit/directives/live.js';
|
|
9
10
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
10
11
|
const MAP_TYPES = ['standard', 'hybrid', 'satellite'];
|
|
@@ -412,9 +413,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
412
413
|
}
|
|
413
414
|
const entries = this.layerEntries;
|
|
414
415
|
const button = this.controlTag;
|
|
416
|
+
const tip = this.tag('tooltip');
|
|
415
417
|
const { material, raised } = this.controlAxes;
|
|
416
418
|
return staticHtml `<${button}
|
|
417
419
|
part="layers-toggle"
|
|
420
|
+
id="layers-toggle"
|
|
418
421
|
exportparts="button: layers-toggle-button, live: layers-toggle-live, spinner: layers-toggle-spinner, text: layers-toggle-text"
|
|
419
422
|
aria-controls="layers-menu"
|
|
420
423
|
.expanded=${this.layersOpen}
|
|
@@ -436,7 +439,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
436
439
|
${unsafeSVG(stack.content)}
|
|
437
440
|
</svg>
|
|
438
441
|
</slot>
|
|
439
|
-
</${button}>
|
|
442
|
+
</${button}><${tip} for="layers-toggle" content=${this.layersLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
|
|
440
443
|
<div
|
|
441
444
|
part="layers-menu"
|
|
442
445
|
id="layers-menu"
|
|
@@ -494,12 +497,14 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
494
497
|
renderCompass(pan, rotate) {
|
|
495
498
|
const { faceNorthLabel } = this;
|
|
496
499
|
const button = this.controlTag;
|
|
500
|
+
const tip = this.tag('tooltip');
|
|
497
501
|
const { material, raised } = this.controlAxes;
|
|
498
502
|
const ground = material ?? nothing;
|
|
499
503
|
return staticHtml `<div part="compass">
|
|
500
504
|
${rotate
|
|
501
505
|
? staticHtml `<${button}
|
|
502
506
|
part="rotate-left"
|
|
507
|
+
id="rotate-left"
|
|
503
508
|
exportparts="button: rotate-left-button, live: rotate-left-live, spinner: rotate-left-spinner, text: rotate-left-text"
|
|
504
509
|
label=${this.rotateLeftLabel}
|
|
505
510
|
material=${ground}
|
|
@@ -507,11 +512,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
507
512
|
@helix-press=${stopPress}
|
|
508
513
|
>
|
|
509
514
|
<slot name="rotate-left-icon">↺</slot>
|
|
510
|
-
</${button}>`
|
|
515
|
+
</${button}><${tip} for="rotate-left" content=${this.rotateLeftLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
511
516
|
: nothing}
|
|
512
517
|
${pan
|
|
513
518
|
? staticHtml `<${button}
|
|
514
519
|
part="pan-up"
|
|
520
|
+
id="pan-up"
|
|
515
521
|
exportparts="button: pan-up-button, live: pan-up-live, spinner: pan-up-spinner, text: pan-up-text"
|
|
516
522
|
label=${this.panLabel('up')}
|
|
517
523
|
material=${ground}
|
|
@@ -519,11 +525,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
519
525
|
@helix-press=${stopPress}
|
|
520
526
|
>
|
|
521
527
|
<slot name="pan-up-icon">${this.panGlyph('up')}</slot>
|
|
522
|
-
</${button}>`
|
|
528
|
+
</${button}><${tip} for="pan-up" content=${this.panLabel('up')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
523
529
|
: nothing}
|
|
524
530
|
${rotate
|
|
525
531
|
? staticHtml `<${button}
|
|
526
532
|
part="rotate-right"
|
|
533
|
+
id="rotate-right"
|
|
527
534
|
exportparts="button: rotate-right-button, live: rotate-right-live, spinner: rotate-right-spinner, text: rotate-right-text"
|
|
528
535
|
label=${this.rotateRightLabel}
|
|
529
536
|
material=${ground}
|
|
@@ -531,11 +538,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
531
538
|
@helix-press=${stopPress}
|
|
532
539
|
>
|
|
533
540
|
<slot name="rotate-right-icon">↻</slot>
|
|
534
|
-
</${button}>`
|
|
541
|
+
</${button}><${tip} for="rotate-right" content=${this.rotateRightLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
535
542
|
: nothing}
|
|
536
543
|
${pan
|
|
537
544
|
? staticHtml `<${button}
|
|
538
545
|
part="pan-start"
|
|
546
|
+
id="pan-start"
|
|
539
547
|
exportparts="button: pan-start-button, live: pan-start-live, spinner: pan-start-spinner, text: pan-start-text"
|
|
540
548
|
label=${this.panLabel('start')}
|
|
541
549
|
material=${ground}
|
|
@@ -543,10 +551,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
543
551
|
@helix-press=${stopPress}
|
|
544
552
|
>
|
|
545
553
|
<slot name="pan-start-icon">${this.panGlyph('start')}</slot>
|
|
546
|
-
</${button}>`
|
|
554
|
+
</${button}><${tip} for="pan-start" content=${this.panLabel('start')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
547
555
|
: nothing}
|
|
548
556
|
<${button}
|
|
549
557
|
part="face-north"
|
|
558
|
+
id="face-north"
|
|
550
559
|
exportparts="button: face-north-button, live: face-north-live, spinner: face-north-spinner, text: face-north-text"
|
|
551
560
|
label=${faceNorthLabel}
|
|
552
561
|
material=${ground}
|
|
@@ -561,10 +570,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
561
570
|
<path data-end="south" d="M8 14.5 5.5 8h5Z" />
|
|
562
571
|
</svg>
|
|
563
572
|
</slot>
|
|
564
|
-
</${button}>
|
|
573
|
+
</${button}><${tip} for="face-north" content=${faceNorthLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
|
|
565
574
|
${pan
|
|
566
575
|
? staticHtml `<${button}
|
|
567
576
|
part="pan-end"
|
|
577
|
+
id="pan-end"
|
|
568
578
|
exportparts="button: pan-end-button, live: pan-end-live, spinner: pan-end-spinner, text: pan-end-text"
|
|
569
579
|
label=${this.panLabel('end')}
|
|
570
580
|
material=${ground}
|
|
@@ -572,11 +582,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
572
582
|
@helix-press=${stopPress}
|
|
573
583
|
>
|
|
574
584
|
<slot name="pan-end-icon">${this.panGlyph('end')}</slot>
|
|
575
|
-
</${button}>`
|
|
585
|
+
</${button}><${tip} for="pan-end" content=${this.panLabel('end')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
576
586
|
: nothing}
|
|
577
587
|
${pan
|
|
578
588
|
? staticHtml `<${button}
|
|
579
589
|
part="pan-down"
|
|
590
|
+
id="pan-down"
|
|
580
591
|
exportparts="button: pan-down-button, live: pan-down-live, spinner: pan-down-spinner, text: pan-down-text"
|
|
581
592
|
label=${this.panLabel('down')}
|
|
582
593
|
material=${ground}
|
|
@@ -584,7 +595,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
584
595
|
@helix-press=${stopPress}
|
|
585
596
|
>
|
|
586
597
|
<slot name="pan-down-icon">${this.panGlyph('down')}</slot>
|
|
587
|
-
</${button}>`
|
|
598
|
+
</${button}><${tip} for="pan-down" content=${this.panLabel('down')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
588
599
|
: nothing}
|
|
589
600
|
</div>`;
|
|
590
601
|
}
|
|
@@ -599,12 +610,14 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
599
610
|
return nothing;
|
|
600
611
|
}
|
|
601
612
|
const button = this.controlTag;
|
|
613
|
+
const tip = this.tag('tooltip');
|
|
602
614
|
const { material, raised } = this.controlAxes;
|
|
603
615
|
const ground = material ?? nothing;
|
|
604
616
|
return staticHtml `<div part="controls" role="toolbar" aria-label=${this.controlsLabel}>
|
|
605
617
|
${fullscreen
|
|
606
618
|
? staticHtml `<${button}
|
|
607
619
|
part="fullscreen"
|
|
620
|
+
id="fullscreen"
|
|
608
621
|
exportparts="button: fullscreen-button, live: fullscreen-live, spinner: fullscreen-spinner, text: fullscreen-text"
|
|
609
622
|
aria-pressed=${this.filled ? 'true' : 'false'}
|
|
610
623
|
label=${this.fullscreenLabel}
|
|
@@ -614,12 +627,13 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
614
627
|
@helix-press=${stopPress}
|
|
615
628
|
>
|
|
616
629
|
<slot name="fullscreen-icon">⤢</slot>
|
|
617
|
-
</${button}>`
|
|
630
|
+
</${button}><${tip} for="fullscreen" content=${this.fullscreenLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
618
631
|
: nothing}
|
|
619
632
|
${menu}
|
|
620
633
|
${tilt
|
|
621
634
|
? staticHtml `<${button}
|
|
622
635
|
part="tilt"
|
|
636
|
+
id="tilt"
|
|
623
637
|
exportparts="button: tilt-button, live: tilt-live, spinner: tilt-spinner, text: tilt-text"
|
|
624
638
|
aria-pressed=${this.pitch > 0 ? 'true' : 'false'}
|
|
625
639
|
label=${this.tiltLabel}
|
|
@@ -629,11 +643,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
629
643
|
@helix-press=${stopPress}
|
|
630
644
|
>
|
|
631
645
|
<slot name="tilt-icon">3D</slot>
|
|
632
|
-
</${button}>`
|
|
646
|
+
</${button}><${tip} for="tilt" content=${this.tiltLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
633
647
|
: nothing}
|
|
634
648
|
${zoom
|
|
635
649
|
? staticHtml `<${button}
|
|
636
650
|
part="zoom-in"
|
|
651
|
+
id="zoom-in"
|
|
637
652
|
exportparts="button: zoom-in-button, live: zoom-in-live, spinner: zoom-in-spinner, text: zoom-in-text"
|
|
638
653
|
label=${this.zoomInLabel}
|
|
639
654
|
material=${ground}
|
|
@@ -642,9 +657,10 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
642
657
|
@helix-press=${stopPress}
|
|
643
658
|
>
|
|
644
659
|
<slot name="zoom-in-icon">+</slot>
|
|
645
|
-
</${button}>
|
|
660
|
+
</${button}><${tip} for="zoom-in" content=${this.zoomInLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
|
|
646
661
|
<${button}
|
|
647
662
|
part="zoom-out"
|
|
663
|
+
id="zoom-out"
|
|
648
664
|
exportparts="button: zoom-out-button, live: zoom-out-live, spinner: zoom-out-spinner, text: zoom-out-text"
|
|
649
665
|
label=${this.zoomOutLabel}
|
|
650
666
|
material=${ground}
|
|
@@ -653,7 +669,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
653
669
|
@helix-press=${stopPress}
|
|
654
670
|
>
|
|
655
671
|
<slot name="zoom-out-icon">−</slot>
|
|
656
|
-
</${button}>`
|
|
672
|
+
</${button}><${tip} for="zoom-out" content=${this.zoomOutLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
|
|
657
673
|
: nothing}
|
|
658
674
|
${this.drawsCompass ? this.renderCompass(pan, rotate) : nothing}
|
|
659
675
|
</div>`;
|
|
@@ -544,6 +544,14 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
544
544
|
* the filters' column. `Notifications` answers the same question for its media
|
|
545
545
|
* column the same way, and for the same reason.
|
|
546
546
|
*
|
|
547
|
+
* **Per section on a page with sections**, for the same reason one level
|
|
548
|
+
* down: a section has its own heading, where the reader starts reading
|
|
549
|
+
* again, so its rows line up with each other and not with another
|
|
550
|
+
* section's. A section of icons over a section of plain choices indented
|
|
551
|
+
* the plain ones by a column nothing in them filled. The rows outside any
|
|
552
|
+
* section are decided together, as before, and the trailing column stays
|
|
553
|
+
* the page's, since ticks on one edge never set labels on two.
|
|
554
|
+
*
|
|
547
555
|
* Read off the light DOM rather than off the rows, so it does not depend on
|
|
548
556
|
* which children have upgraded by the time this runs.
|
|
549
557
|
*/
|
|
@@ -741,7 +741,28 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
741
741
|
}
|
|
742
742
|
markColumns() {
|
|
743
743
|
const rows = this.flies ? this.topRows : this.drilldown.rows;
|
|
744
|
-
|
|
744
|
+
const sections = new Map();
|
|
745
|
+
for (const child of this.children) {
|
|
746
|
+
if (child.isMenuSection) {
|
|
747
|
+
sections.set(child, false);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
let loose = false;
|
|
751
|
+
for (const row of rows) {
|
|
752
|
+
const marked = row.querySelector(':scope > [slot="start"]') !== null;
|
|
753
|
+
const parent = row.parentElement;
|
|
754
|
+
if (parent && sections.has(parent)) {
|
|
755
|
+
sections.set(parent, sections.get(parent) === true || marked);
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
loose ||= marked;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
this.toggleState('start', loose);
|
|
762
|
+
for (const [section, marked] of sections) {
|
|
763
|
+
;
|
|
764
|
+
section.startColumn = marked;
|
|
765
|
+
}
|
|
745
766
|
this.toggleState('mark', rows.some((row) => row.choice || this.drilldown.hasPage(row)));
|
|
746
767
|
}
|
|
747
768
|
mark(item, onPage) {
|
|
@@ -23,6 +23,10 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
23
23
|
*
|
|
24
24
|
* @csspart heading - The words that name the section.
|
|
25
25
|
* @csspart section - The group that holds the heading and the rows.
|
|
26
|
+
*
|
|
27
|
+
* @cssstate start - A row in the section has content before its label, so
|
|
28
|
+
* every row in it keeps the leading column. The menu decides it for each
|
|
29
|
+
* section, apart from the rows outside one.
|
|
26
30
|
*/
|
|
27
31
|
export declare class HelixHeadlessMenuSection extends HelixElement {
|
|
28
32
|
/**
|
|
@@ -35,6 +39,11 @@ export declare class HelixHeadlessMenuSection extends HelixElement {
|
|
|
35
39
|
* property, because the menu writes it after the section upgrades.
|
|
36
40
|
*/
|
|
37
41
|
onPage: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* @internal Set by the menu: whether a row in the section has content
|
|
44
|
+
* before its label, so the section's rows keep the leading column.
|
|
45
|
+
*/
|
|
46
|
+
startColumn: boolean;
|
|
38
47
|
/** @internal What a menu reads to tell a section from a row. */
|
|
39
48
|
readonly isMenuSection = true;
|
|
40
49
|
/**
|
|
@@ -44,6 +53,7 @@ export declare class HelixHeadlessMenuSection extends HelixElement {
|
|
|
44
53
|
*/
|
|
45
54
|
private heading;
|
|
46
55
|
protected willUpdate(changed: PropertyValues): void;
|
|
56
|
+
protected updated(changed: PropertyValues): void;
|
|
47
57
|
protected firstUpdated(changed: PropertyValues): void;
|
|
48
58
|
private readonly readHeading;
|
|
49
59
|
protected render(): TemplateResult | typeof nothing;
|
|
@@ -7,6 +7,7 @@ let HelixHeadlessMenuSection = class HelixHeadlessMenuSection extends HelixEleme
|
|
|
7
7
|
super(...arguments);
|
|
8
8
|
this.label = '';
|
|
9
9
|
this.onPage = true;
|
|
10
|
+
this.startColumn = false;
|
|
10
11
|
this.isMenuSection = true;
|
|
11
12
|
this.heading = false;
|
|
12
13
|
this.readHeading = (event) => {
|
|
@@ -19,6 +20,12 @@ let HelixHeadlessMenuSection = class HelixHeadlessMenuSection extends HelixEleme
|
|
|
19
20
|
this.heading = this.knownChildren.some((child) => child.slot === 'heading');
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
updated(changed) {
|
|
24
|
+
super.updated(changed);
|
|
25
|
+
if (changed.has('startColumn')) {
|
|
26
|
+
this.toggleState('start', this.startColumn);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
22
29
|
firstUpdated(changed) {
|
|
23
30
|
super.firstUpdated(changed);
|
|
24
31
|
this.finishHydration();
|
|
@@ -46,6 +53,9 @@ __decorate([
|
|
|
46
53
|
__decorate([
|
|
47
54
|
property({ attribute: false })
|
|
48
55
|
], HelixHeadlessMenuSection.prototype, "onPage", void 0);
|
|
56
|
+
__decorate([
|
|
57
|
+
property({ attribute: false })
|
|
58
|
+
], HelixHeadlessMenuSection.prototype, "startColumn", void 0);
|
|
49
59
|
__decorate([
|
|
50
60
|
state()
|
|
51
61
|
], HelixHeadlessMenuSection.prototype, "heading", void 0);
|
package/dist/meter/index.d.ts
CHANGED
package/dist/meter/index.js
CHANGED
|
@@ -6,7 +6,9 @@ export type MeterBand = 'fair' | 'good' | 'poor';
|
|
|
6
6
|
* @summary A measurement in a known range, with optional bands that mark it good, fair, or poor.
|
|
7
7
|
*
|
|
8
8
|
* @description
|
|
9
|
-
* A meter shows a level within a range
|
|
9
|
+
* A meter shows a level within a range, or how the level divides: write
|
|
10
|
+
* `Meter.Segment` inside it for each part of the whole, and the meter measures
|
|
11
|
+
* their sum. Use `Progress` to show how far along a
|
|
10
12
|
* task is. A progress bar starts empty and ends full, and full is always the
|
|
11
13
|
* good end. For a meter, which end is good depends on what is measured. There
|
|
12
14
|
* is no `indeterminate`, because a meter always has a value.
|
|
@@ -31,6 +33,7 @@ export type MeterBand = 'fair' | 'good' | 'poor';
|
|
|
31
33
|
* @aliases Gauge, Level, Capacity
|
|
32
34
|
* @documentation https://fusion.dev/helix/components/meter
|
|
33
35
|
*
|
|
36
|
+
* @slot - `Meter.Segment` elements, one for each share of the whole, drawn in the bar.
|
|
34
37
|
* @slot label - A visible label. Its words name the meter in place of `label`.
|
|
35
38
|
*
|
|
36
39
|
* @event {HelixBandEvent} helix-band - Fired when the reading moves into another band, with the band it is in now, or `''` where it is in none. Not sent on the first render, since a meter that arrives in a band has not moved.
|
|
@@ -50,6 +53,7 @@ export type MeterBand = 'fair' | 'good' | 'poor';
|
|
|
50
53
|
* @cssproperty --meter-along - Where a bound sits on the arc, as a length along it.
|
|
51
54
|
* @cssproperty --meter-at - Where a bound sits on a linear track, as a percentage.
|
|
52
55
|
* @cssproperty --meter-fraction - The value as a fraction of the range, 0 to 1.
|
|
56
|
+
* @cssproperty --meter-span - How much the range holds, `max` less `min`, which a segment divides its own value by to find its width.
|
|
53
57
|
* @cssproperty --meter-sweep - How much of the circle the arc covers, as a path length.
|
|
54
58
|
*
|
|
55
59
|
* @cssstate fair - The reading is in a band next to the good band.
|
|
@@ -225,6 +229,17 @@ export declare class HelixHeadlessMeter extends HelixElement {
|
|
|
225
229
|
* an empty string for none, and undefined before the first update.
|
|
226
230
|
*/
|
|
227
231
|
private lastBand;
|
|
232
|
+
/**
|
|
233
|
+
* The segments written inside, where they can be known: none on a server or
|
|
234
|
+
* while hydrating what one sent, so the first render is the server's, and
|
|
235
|
+
* every one once the page has them.
|
|
236
|
+
*/
|
|
237
|
+
private get segments();
|
|
238
|
+
/**
|
|
239
|
+
* What the meter measures: the sum of its segments where it has any, and its
|
|
240
|
+
* own `value` where it has none, so the two can never disagree.
|
|
241
|
+
*/
|
|
242
|
+
private get amount();
|
|
228
243
|
/** The range, clamped so `max` is never at or below `min`. */
|
|
229
244
|
private get span();
|
|
230
245
|
/** The reading, pulled into the range. */
|
|
@@ -21,6 +21,19 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
static { this.words = 'helix-meter'; }
|
|
24
|
+
get segments() {
|
|
25
|
+
return this.knownChildren.filter((child) => child.localName.endsWith('meter-segment'));
|
|
26
|
+
}
|
|
27
|
+
get amount() {
|
|
28
|
+
const segments = this.segments;
|
|
29
|
+
if (segments.length === 0) {
|
|
30
|
+
return this.value;
|
|
31
|
+
}
|
|
32
|
+
return segments.reduce((sum, segment) => {
|
|
33
|
+
const share = Number(segment.value ?? segment.getAttribute('value'));
|
|
34
|
+
return Number.isFinite(share) && share > 0 ? sum + share : sum;
|
|
35
|
+
}, 0);
|
|
36
|
+
}
|
|
24
37
|
get span() {
|
|
25
38
|
const min = Number.isFinite(this.min) ? this.min : 0;
|
|
26
39
|
const max = Number.isFinite(this.max) && this.max > min ? this.max : min + 1;
|
|
@@ -28,7 +41,7 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
28
41
|
}
|
|
29
42
|
get reading() {
|
|
30
43
|
const { max, min } = this.span;
|
|
31
|
-
return Math.min(max, Math.max(min, Number.isFinite(this.
|
|
44
|
+
return Math.min(max, Math.max(min, Number.isFinite(this.amount) ? this.amount : min));
|
|
32
45
|
}
|
|
33
46
|
get fraction() {
|
|
34
47
|
const { max, min } = this.span;
|
|
@@ -36,12 +49,12 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
36
49
|
}
|
|
37
50
|
get ratio() {
|
|
38
51
|
const { max, min } = this.span;
|
|
39
|
-
const raw = Number.isFinite(this.
|
|
52
|
+
const raw = Number.isFinite(this.amount) ? this.amount : min;
|
|
40
53
|
return (raw - min) / (max - min);
|
|
41
54
|
}
|
|
42
55
|
get beyond() {
|
|
43
56
|
const { max, min } = this.span;
|
|
44
|
-
const raw = Number.isFinite(this.
|
|
57
|
+
const raw = Number.isFinite(this.amount) ? this.amount : min;
|
|
45
58
|
return raw > max ? 'over' : raw < min ? 'under' : undefined;
|
|
46
59
|
}
|
|
47
60
|
get thresholds() {
|
|
@@ -69,7 +82,7 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
69
82
|
}
|
|
70
83
|
get text() {
|
|
71
84
|
return (this.valueText ||
|
|
72
|
-
readingText(Number.isFinite(this.
|
|
85
|
+
readingText(Number.isFinite(this.amount) ? this.amount : this.span.min, this.ratio, wordsLocale(this), this.formatOptions));
|
|
73
86
|
}
|
|
74
87
|
willUpdate(changed) {
|
|
75
88
|
super.willUpdate(changed);
|
|
@@ -134,17 +147,20 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
134
147
|
const at = (value) => `${((value - min) / (max - min)) * 100}%`;
|
|
135
148
|
return html `<slot name="label" @slotchange=${this.renamed}></slot>${this.shape
|
|
136
149
|
? this.renderRound()
|
|
137
|
-
: html `<div part="track">
|
|
150
|
+
: html `<div part="track" style="--meter-span: ${max - min}">
|
|
151
|
+
<slot @slotchange=${this.renamed}></slot>
|
|
138
152
|
${bounds
|
|
139
153
|
? html `<span part="tick" style="--meter-at: ${at(bounds.low)}"></span>
|
|
140
154
|
<span part="tick" style="--meter-at: ${at(bounds.high)}"></span>`
|
|
141
155
|
: nothing}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
${this.segments.length > 0
|
|
157
|
+
? nothing
|
|
158
|
+
: html `<div
|
|
159
|
+
part="indicator${band ? { fair: ' indicator-fair', good: ' indicator-good', poor: ' indicator-poor' }[band] : ''}${beyond ? { over: ' indicator-over', under: ' indicator-under' }[beyond] : ''}"
|
|
160
|
+
data-band=${band ?? nothing}
|
|
161
|
+
data-beyond=${beyond ?? nothing}
|
|
162
|
+
style="--meter-fraction: ${this.fraction}"
|
|
163
|
+
></div>`}
|
|
148
164
|
</div>`}
|
|
149
165
|
${this.showValue ? html `<span part="value">${this.text}</span>` : nothing}${drawStatus(this.status)}`;
|
|
150
166
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
/**
|
|
4
|
+
* @summary One share of a meter's whole, drawn as a slice of its bar.
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* A segment says how much of the range one part of the whole takes, such as the
|
|
8
|
+
* tokens a model's context holds for its tools, against the tokens it holds for
|
|
9
|
+
* the conversation. Write one for each part inside a `Meter`.
|
|
10
|
+
*
|
|
11
|
+
* **The meter's reading is the sum of its segments.** `value` on the meter is
|
|
12
|
+
* ignored while it has any, so the two cannot disagree, and the bands, the ticks
|
|
13
|
+
* and the reading answer to the total. A segment is drawn as wide as its `value`
|
|
14
|
+
* is of the meter's range, so segments that add up to less than the range leave
|
|
15
|
+
* the rest of the track empty, and ones that add up to more are cut at the end.
|
|
16
|
+
*
|
|
17
|
+
* **A meter's children are presentational to a screen reader**, so a segment
|
|
18
|
+
* reaches no one that cannot see the bar. Say the breakdown in text beside it,
|
|
19
|
+
* such as a list with the name and the amount of each part, which is also where a
|
|
20
|
+
* reader finds the exact figures.
|
|
21
|
+
*
|
|
22
|
+
* A segment draws only in the bar. A meter in `ring` or `arc` draws its total.
|
|
23
|
+
*
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
* @status experimental
|
|
26
|
+
* @category feedback
|
|
27
|
+
* @documentation https://fusion.dev/helix/components/meter
|
|
28
|
+
*
|
|
29
|
+
* @slot - Nothing. A segment draws its share and has no content.
|
|
30
|
+
*
|
|
31
|
+
* @csspart fill - The slice of the bar the segment takes.
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty --meter-segment-value - How much of the range the segment takes, in the meter's own units.
|
|
34
|
+
*/
|
|
35
|
+
export declare class HelixHeadlessMeterSegment extends HelixElement {
|
|
36
|
+
/**
|
|
37
|
+
* What the segment is, such as "Tools", which names the slice when a pointer
|
|
38
|
+
* rests on it.
|
|
39
|
+
*
|
|
40
|
+
* Not announced: see the note on a meter's children above.
|
|
41
|
+
*/
|
|
42
|
+
label: string;
|
|
43
|
+
/**
|
|
44
|
+
* What the segment means, as it does on a callout. Takes the tone's color in
|
|
45
|
+
* place of the next color in the series.
|
|
46
|
+
*/
|
|
47
|
+
tone?: 'accent' | 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning';
|
|
48
|
+
/**
|
|
49
|
+
* How much of the meter's range the segment takes, in the units of the meter's
|
|
50
|
+
* `min` and `max`.
|
|
51
|
+
*
|
|
52
|
+
* A value that is not a number, or is below zero, draws nothing.
|
|
53
|
+
*/
|
|
54
|
+
value: number;
|
|
55
|
+
/** The meter this is a segment of, which reads its sum. */
|
|
56
|
+
private get meter();
|
|
57
|
+
/** The amount drawn: never below zero, and never a number that is not one. */
|
|
58
|
+
private get amount();
|
|
59
|
+
/**
|
|
60
|
+
* The meter sums its segments, so it hears when one arrives or changes. The
|
|
61
|
+
* segment is the one that knows, so it reaches up by identity and the meter
|
|
62
|
+
* does not have to ask. A meter's own update never updates a segment, so this
|
|
63
|
+
* cannot loop.
|
|
64
|
+
*/
|
|
65
|
+
protected updated(changed: PropertyValues): void;
|
|
66
|
+
/** A segment taken out leaves the meter with a smaller sum, which it reads again. */
|
|
67
|
+
disconnectedCallback(): void;
|
|
68
|
+
protected render(): TemplateResult;
|
|
69
|
+
}
|
|
70
|
+
declare global {
|
|
71
|
+
interface HTMLElementTagNameMap {
|
|
72
|
+
'helix-headless-meter-segment': HelixHeadlessMeterSegment;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { closestInstance, customElement, HelixElement, numberConverter, } from '@fusion.dev/helix-core';
|
|
3
|
+
import { html, nothing } from 'lit';
|
|
4
|
+
import { property } from 'lit/decorators.js';
|
|
5
|
+
import { HelixHeadlessMeter } from '../root/meter-root.js';
|
|
6
|
+
let HelixHeadlessMeterSegment = class HelixHeadlessMeterSegment extends HelixElement {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.label = '';
|
|
10
|
+
this.value = 0;
|
|
11
|
+
}
|
|
12
|
+
get meter() {
|
|
13
|
+
return closestInstance(this, HelixHeadlessMeter);
|
|
14
|
+
}
|
|
15
|
+
get amount() {
|
|
16
|
+
return Number.isFinite(this.value) && this.value > 0 ? this.value : 0;
|
|
17
|
+
}
|
|
18
|
+
updated(changed) {
|
|
19
|
+
super.updated(changed);
|
|
20
|
+
this.meter?.requestUpdate();
|
|
21
|
+
}
|
|
22
|
+
disconnectedCallback() {
|
|
23
|
+
const meter = this.meter;
|
|
24
|
+
super.disconnectedCallback();
|
|
25
|
+
queueMicrotask(() => meter?.requestUpdate());
|
|
26
|
+
}
|
|
27
|
+
render() {
|
|
28
|
+
return html `<span
|
|
29
|
+
part="fill"
|
|
30
|
+
title=${this.label || nothing}
|
|
31
|
+
style="--meter-segment-value: ${this.amount}"
|
|
32
|
+
></span>`;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
__decorate([
|
|
36
|
+
property()
|
|
37
|
+
], HelixHeadlessMeterSegment.prototype, "label", void 0);
|
|
38
|
+
__decorate([
|
|
39
|
+
property({ reflect: true })
|
|
40
|
+
], HelixHeadlessMeterSegment.prototype, "tone", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
property({ converter: numberConverter('value', 0) })
|
|
43
|
+
], HelixHeadlessMeterSegment.prototype, "value", void 0);
|
|
44
|
+
HelixHeadlessMeterSegment = __decorate([
|
|
45
|
+
customElement('helix-headless-meter-segment')
|
|
46
|
+
], HelixHeadlessMeterSegment);
|
|
47
|
+
export { HelixHeadlessMeterSegment };
|
|
@@ -35,6 +35,11 @@ declare const EDGES: readonly ['block-start', 'block-end', 'inline-start', 'inli
|
|
|
35
35
|
* scrollbar, the browser's or the component's, and clips what is past it.
|
|
36
36
|
* Leave it unset to scroll both ways.
|
|
37
37
|
*
|
|
38
|
+
* **A mask on the viewport also masks the browser's scrollbar.** While a
|
|
39
|
+
* scrollbar takes room beside the content, the viewport carries its size in
|
|
40
|
+
* `--scroll-area-gutter-inline-end` or `--scroll-area-gutter-block-end`, so a
|
|
41
|
+
* mask can leave that room opaque. The styled tier's fades use them.
|
|
42
|
+
*
|
|
38
43
|
* The viewport's `scroll` and `scrollend` are sent again from the host, which
|
|
39
44
|
* is the element you hold. A thumb dragged across the content sends one
|
|
40
45
|
* `scrollend`, once it is let go. In React, bind them with `onScroll` and
|
|
@@ -56,7 +61,7 @@ declare const EDGES: readonly ['block-start', 'block-end', 'inline-start', 'inli
|
|
|
56
61
|
* @event {Event} scrollend - The content stopped scrolling. Sent from the host, as `scroll` is, and once for a thumb dragged across the content. Does not bubble.
|
|
57
62
|
*
|
|
58
63
|
* @csspart corner - The square where the two scrollbars meet, under `scrollbars`. Hidden unless both are drawn.
|
|
59
|
-
* @csspart edges - The layer
|
|
64
|
+
* @csspart edges - The layer over the viewport. The styled tier draws the focus ring on it, and the painted fades at the inline start and end.
|
|
60
65
|
* @csspart scrollbar - Each scrollbar, under `scrollbars`. Hidden on an axis that has none.
|
|
61
66
|
* @csspart scrollbar-horizontal - The scrollbar along the inline axis, alongside `scrollbar`.
|
|
62
67
|
* @csspart scrollbar-vertical - The scrollbar along the block axis, alongside `scrollbar`.
|
|
@@ -66,6 +71,8 @@ declare const EDGES: readonly ['block-start', 'block-end', 'inline-start', 'inli
|
|
|
66
71
|
* @csspart thumb-vertical - The thumb of the vertical scrollbar, alongside `thumb`.
|
|
67
72
|
* @csspart viewport - The scrolling box.
|
|
68
73
|
*
|
|
74
|
+
* @cssproperty --scroll-area-gutter-block-end - The room the browser's horizontal scrollbar takes at the block end of the viewport, in pixels. Written on the `viewport` part while that scrollbar takes room, and absent while it draws over the content.
|
|
75
|
+
* @cssproperty --scroll-area-gutter-inline-end - The room the browser's vertical scrollbar takes at the inline end of the viewport, in pixels. Written on the `viewport` part while that scrollbar takes room, and absent while it draws over the content.
|
|
69
76
|
* @cssproperty --scroll-area-scroll-fraction - How far along its axis the content is scrolled, from 0 at the start to 1 at the end. Written on each `scrollbar` part, so its thumb inherits it. Counted from the right on a right-to-left page.
|
|
70
77
|
* @cssproperty --scroll-area-thumb-fraction - The share of the track the thumb covers, from 0 to 1, which is how much of the content is in view. Written on each `scrollbar` part.
|
|
71
78
|
*
|
|
@@ -119,6 +126,12 @@ export declare class HelixHeadlessScrollArea extends HelixElement {
|
|
|
119
126
|
* Whether there is anything to scroll at all, which decides the tab stop.
|
|
120
127
|
*/
|
|
121
128
|
private scrolls;
|
|
129
|
+
/**
|
|
130
|
+
* The room the browser's own scrollbars take, in pixels: the vertical one at
|
|
131
|
+
* the inline end, and the horizontal one at the block end. Zero where they are
|
|
132
|
+
* drawn over the content.
|
|
133
|
+
*/
|
|
134
|
+
private gutter;
|
|
122
135
|
private watcher;
|
|
123
136
|
/**
|
|
124
137
|
* The element that scrolls. Use it to scroll the content from your code.
|
|
@@ -154,8 +167,12 @@ export declare class HelixHeadlessScrollArea extends HelixElement {
|
|
|
154
167
|
private watchContent;
|
|
155
168
|
protected updated(changed: PropertyValues): void;
|
|
156
169
|
private readonly handleSlotChange;
|
|
157
|
-
/**
|
|
158
|
-
|
|
170
|
+
/**
|
|
171
|
+
* The viewport's inline style: the axis `orientation` takes away, clipped
|
|
172
|
+
* where the content would overflow it, and the room each of the browser's
|
|
173
|
+
* scrollbars takes.
|
|
174
|
+
*/
|
|
175
|
+
private get viewportStyle();
|
|
159
176
|
protected render(): TemplateResult;
|
|
160
177
|
/**
|
|
161
178
|
* The scrollbars, the thumbs in them and the corner.
|