@haikit/client 0.23.0 → 0.24.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/README.md CHANGED
@@ -48,7 +48,8 @@ Every surface has a **Dock** button on its bar. Docked, it waits on a shelf
48
48
  above the message box and opens from there as a window, one at a time; **Put
49
49
  back** or **Remove from dock** returns it to its place. It's moved, not
50
50
  mounted again, so your component keeps its state. A docked question stays
51
- amber on the shelf, and when the shelf is full the oldest go into a pile.
51
+ amber on the shelf, when the shelf is full the oldest go into a pile, and
52
+ **Remove all** at the shelf's end puts every docked surface back at once.
52
53
  `dock: false` leaves it out.
53
54
 
54
55
  It is light or dark with the system. To pin it, put `data-hai-theme="dark"` (or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haikit/client",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "The haikit browser runtime: streams tool payloads into interactive UI surfaces. Plain ESM, no build step, no dependencies.",
5
5
  "keywords": [
6
6
  "haikit",
package/src/app.js CHANGED
@@ -436,6 +436,7 @@ export function mountChat({
436
436
  *
437
437
  * When the shelf's row is full, the oldest go into a pile at its end, which
438
438
  * lists them. A surface waiting on the reader, or open, stays in the row.
439
+ * Remove all, at the shelf's end, puts every docked surface back at once.
439
440
  */
440
441
  function createDock({ chat, id, shell, chatPane, transcript, composer, statusEl, wide, wideQuery, drawerOpen, onChange }) {
441
442
  /** Docked surfaces, oldest first. */
@@ -464,7 +465,13 @@ function createDock({ chat, id, shell, chatPane, transcript, composer, statusEl,
464
465
  list.id = `${id}-pile`;
465
466
  list.hidden = true;
466
467
  pile.append(pileBtn, list);
467
- shelf.append(count, row, pile);
468
+ // Remove all, at the shelf's end: every docked surface back in the chat
469
+ const clear = h("button", "hai-dock-clear");
470
+ clear.type = "button";
471
+ clear.title = "Remove all from dock";
472
+ clear.setAttribute("aria-label", "Remove all from the dock");
473
+ clear.append(icon("undockAll"));
474
+ shelf.append(count, row, pile, clear);
468
475
  composer.prepend(shelf);
469
476
 
470
477
  // ── the window ───────────────────────────────────────────────────────
@@ -591,6 +598,19 @@ function createDock({ chat, id, shell, chatPane, transcript, composer, statusEl,
591
598
  remove.onclick = () => open && undock(open, true);
592
599
  minimise.onclick = () => close(true);
593
600
 
601
+ // Every docked surface back in its card, oldest first. The shelf goes with
602
+ // them, so the reader goes to the message box under it, or, once the
603
+ // conversation has expired and the box is disabled, to the button that
604
+ // starts a new one.
605
+ clear.onclick = () => {
606
+ const n = entries.length;
607
+ for (const entry of [...entries]) undock(entry, false);
608
+ const input = composer.querySelector(".hai-input");
609
+ if (input && !input.disabled) input.focus();
610
+ else transcript.querySelector(".hai-expired .hai-new")?.focus();
611
+ live.textContent = n === 1 ? "1 surface is back in the chat" : `${n} surfaces are back in the chat`;
612
+ };
613
+
594
614
  // ── keeping up ───────────────────────────────────────────────────────
595
615
  // After every event the chat renders, and after every change here.
596
616
  function sync() {
@@ -751,7 +771,7 @@ function createDock({ chat, id, shell, chatPane, transcript, composer, statusEl,
751
771
  if (!keys.includes(e.key)) return;
752
772
  const buttons = inList
753
773
  ? [...list.querySelectorAll("button")]
754
- : [...shelf.querySelectorAll(".hai-dock-item")].filter((b) => !b.hidden && !b.closest("[hidden]"));
774
+ : [...shelf.querySelectorAll(".hai-dock-item, .hai-dock-clear")].filter((b) => !b.hidden && !b.closest("[hidden]"));
755
775
  const at = buttons.indexOf(e.target);
756
776
  if (at < 0) return;
757
777
  e.preventDefault();
package/src/hai.css CHANGED
@@ -659,6 +659,8 @@
659
659
  /* The composer card's top row while anything is docked: one card, one
660
660
  shadow. Not positioned, so the box below covers the shelf's own shadow. */
661
661
  .hai-dock {
662
+ /* Remove all's size: the pile's list lines up past it, with the shelf's end */
663
+ --hai-dock-clear: 36px;
662
664
  display: flex;
663
665
  align-items: center;
664
666
  gap: 10px;
@@ -766,10 +768,29 @@
766
768
  transform: translate(8px, -8px);
767
769
  }
768
770
  .hai-dock-pile::after { transform: translate(4px, -4px); }
771
+ /* Remove all, at the shelf's end: a quiet icon until the pointer's on it */
772
+ .hai-dock-clear {
773
+ flex: none;
774
+ display: grid;
775
+ place-items: center;
776
+ width: var(--hai-dock-clear);
777
+ height: var(--hai-dock-clear);
778
+ padding: 0;
779
+ border: 2px solid transparent;
780
+ border-radius: var(--hai-radius);
781
+ background: none;
782
+ color: var(--hai-dim);
783
+ cursor: pointer;
784
+ }
785
+ .hai-dock-clear .hai-icon { width: 16px; height: 16px; stroke-width: 2.5; }
786
+ .hai-dock-clear:hover { border-color: var(--hai-line); background: var(--hai-hover); color: var(--hai-fg); }
769
787
  /* what's in it, oldest first, above the shelf */
788
+ /* Its right edge lines up with the shelf's: past the pile's margin, the gap,
789
+ Remove all, the shelf's padding and its border. So it opens leftward from
790
+ the shelf's end, and fits whatever the screen fits. */
770
791
  .hai-pile-list {
771
792
  position: absolute;
772
- right: -8px;
793
+ right: calc(-1 * (8px + 10px + var(--hai-dock-clear, 36px) + 14px + 3px));
773
794
  bottom: calc(100% + 18px);
774
795
  z-index: 6;
775
796
  width: 280px;
@@ -993,6 +1014,7 @@
993
1014
  /* a finger's worth, wherever the pointer is coarse */
994
1015
  @media (pointer: coarse) {
995
1016
  .hai-dock-item { height: 44px; }
1017
+ .hai-dock { --hai-dock-clear: 44px; }
996
1018
  .hai-surface-bar { min-height: 44px; }
997
1019
  .hai-docked { min-height: 44px; }
998
1020
  .hai-window-bar { min-height: 44px; }
@@ -1014,7 +1036,8 @@
1014
1036
  .hai-tool summary:focus-visible { outline: 2px solid currentColor; outline-offset: -5px; }
1015
1037
  /* the dock's items and its pile take the buttons' ring; a bar's cells and the
1016
1038
  pile's rows, where a card would clip it, a ring inside */
1017
- .hai-dock-item:focus-visible { outline: 3px solid var(--hai-fg); outline-offset: 2px; }
1039
+ .hai-dock-item:focus-visible,
1040
+ .hai-dock-clear:focus-visible { outline: 3px solid var(--hai-fg); outline-offset: 2px; }
1018
1041
  .hai-bar-btn:focus-visible,
1019
1042
  .hai-pile-row:focus-visible { outline: 2px solid currentColor; outline-offset: -6px; }
1020
1043
 
package/src/icons.js CHANGED
@@ -21,6 +21,7 @@ const PATHS = {
21
21
  window: "M4 5h16v14H4zM4 9h16",
22
22
  dock: "M12 4v10M7.5 9.5 12 14l4.5-4.5M4 20h16",
23
23
  undock: "M12 16V4M7.5 8.5 12 4l4.5 4.5M4 20h16",
24
+ undockAll: "M12 13V3M7.5 7.5 12 3l4.5 4.5M4 17h16M4 21h16",
24
25
  stack: "M3 8h13v13H3zM8 8V3h13v13h-5",
25
26
  };
26
27